it-swarm.com.ru

Лучшая практика создания нового фрагмента Android

Я видел две общие практики создания нового фрагмента в приложении:

Fragment newFragment = new MyFragment();

а также

Fragment newFragment = MyFragment.newInstance();

Второй вариант использует статический метод newInstance(), а обычно содержит следующий метод.

public static Fragment newInstance() 
{
    MyFragment myFragment = new MyFragment();
    return myFragment;
}

Сначала я подумал, что главное преимущество заключается в том, что я могу перегрузить метод newInstance (), чтобы обеспечить гибкость при создании новых экземпляров фрагмента, но я мог бы также сделать это, создав перегруженный конструктор для фрагмента.

Я что-то пропустил?

Каковы преимущества одного подхода над другим? Или это просто хорошая практика?

653
Graham Smith

Если Android решит воссоздать ваш фрагмент позже, он вызовет конструктор без аргументов вашего фрагмента. Таким образом, перегрузка конструктора не является решением.

С учетом вышесказанного, способ передать вещи в ваш фрагмент, чтобы они были доступны после воссоздания фрагмента Android, - передать пакет методу setArguments.

Так, например, если мы хотим передать целое число во фрагмент, мы будем использовать что-то вроде:

public static MyFragment newInstance(int someInt) {
    MyFragment myFragment = new MyFragment();

    Bundle args = new Bundle();
    args.putInt("someInt", someInt);
    myFragment.setArguments(args);

    return myFragment;
}

А позже в Fragment onCreate() вы можете получить доступ к этому целому числу с помощью:

getArguments().getInt("someInt", 0);

Этот комплект будет доступен, даже если фрагмент каким-то образом воссоздан в Android.

Также обратите внимание: setArguments может быть вызвано только до того, как фрагмент прикреплен к действию.

Этот подход также описан в Android справочнике разработчика: https://developer.Android.com/reference/Android/app/Fragment.html

1052
yydl

Единственное преимущество использования newInstance(), которое я вижу, заключается в следующем:

  1. У вас будет единственное место, где все аргументы, используемые фрагментом, могут быть связаны, и вам не нужно будет писать код ниже каждый раз, когда вы создаете экземпляр фрагмента.

    Bundle args = new Bundle();
    args.putInt("someInt", someInt);
    args.putString("someString", someString);
    // Put any other arguments
    myFragment.setArguments(args);
    
  2. Это хороший способ сообщить другим классам, какие аргументы он ожидает для точной работы (хотя вы должны иметь возможность обрабатывать случаи, если в экземпляре фрагмента не объединены никакие аргументы).

Итак, я предполагаю, что использование статической newInstance() для создания экземпляра фрагмента является хорошей практикой.

94
500865

Есть и другой способ:

Fragment.instantiate(context, MyFragment.class.getName(), myBundle)
61
user1145201

Хотя @yydl дает убедительную причину того, почему метод newInstance лучше:

Если Android решит воссоздать ваш фрагмент позже, он вызовет конструктор без аргументов вашего фрагмента. Таким образом, перегрузка конструктора не является решением.

все еще вполне возможно использовать конструктор . Чтобы понять, почему это так, сначала нам нужно понять, почему вышеупомянутый обходной путь используется Android.

Прежде чем фрагмент может быть использован, необходим экземпляр. Android вызывает YourFragment() (конструктор без аргументов) для создания экземпляра фрагмента. Здесь любой перегруженный конструктор, который вы напишите, будет игнорироваться, поскольку Android не может знать, какой из них использовать.

Во время жизни действия фрагмент создается, как описано выше, и несколько раз уничтожается Android. Это означает, что если вы поместите данные в сам объект фрагмента, они будут потеряны после уничтожения фрагмента.

Чтобы обойти это, Android просит сохранить данные, используя Bundle (вызывая setArguments()), к которому затем можно получить доступ из YourFragment. Аргумент bundles защищен Android, и, следовательно, гарантированно будет постоянный.

Один из способов установить этот пакет - использовать статический метод newInstance:

public static YourFragment newInstance (int data) {
    YourFragment yf = new YourFragment()
    /* See this code gets executed immediately on your object construction */
    Bundle args = new Bundle();
    args.putInt("data", data);
    yf.setArguments(args);
    return yf;
}

Тем не менее, конструктор:

public YourFragment(int data) {
    Bundle args = new Bundle();
    args.putInt("data", data);
    setArguments(args);
}

может делать то же самое, что и метод newInstance.

Естественно, это не получится, и это одна из причин, по которой Android хочет, чтобы вы использовали метод newInstance:

public YourFragment(int data) {
    this.data = data; // Don't do this
}

Как дальнейшее объяснение, вот класс фрагментов Android:

/**
 * Supply the construction arguments for this fragment.  This can only
 * be called before the fragment has been attached to its activity; that
 * is, you should call it immediately after constructing the fragment.  The
 * arguments supplied here will be retained across fragment destroy and
 * creation.
 */
public void setArguments(Bundle args) {
    if (mIndex >= 0) {
        throw new IllegalStateException("Fragment already active");
    }
    mArguments = args;
}

Обратите внимание, что Android просит установить аргументы только при построении и гарантирует, что они будут сохранены.

EDIT: Как указано в комментариях @JHH, если вы предоставляете пользовательский конструктор, требующий некоторых аргументов, то Java не предоставит вашему фрагменту no arg конструктор по умолчанию. Поэтому для этого потребуется определить конструктор no arg , который можно избежать с помощью фабричного метода newInstance.

EDIT: Android больше не позволяет использовать перегруженный конструктор для фрагментов. Вы должны использовать метод newInstance.

40
ps95

Я не согласен с yydi ответ говоря:

Если Android решит воссоздать ваш фрагмент позже, он вызовет конструктор без аргументов вашего фрагмента. Таким образом, перегрузка конструктора не является решением.

Я думаю, что это хорошее решение, именно поэтому оно было разработано базовым языком Java.

Это правда, что Android система может уничтожить и воссоздать ваше Fragment. Так что вы можете сделать это:

public MyFragment() {
//  An empty constructor for Android System to use, otherwise exception may occur.
}

public MyFragment(int someInt) {
    Bundle args = new Bundle();
    args.putInt("someInt", someInt);
    setArguments(args);
}

Это позволит вам извлекать someInt из getArguments() позднее, даже если Fragment была воссоздана системой. Это более элегантное решение, чем конструктор static.

На мой взгляд, конструкторы static бесполезны и не должны использоваться. Также они будут ограничивать вас, если в будущем вы захотите расширить это Fragment и добавить больше функциональности в конструктор. С конструктором static вы не сможете этого сделать.

Update:

Android добавил проверку, которая помечает все конструкторы не по умолчанию с ошибкой.
Я рекомендую отключить его по причинам, указанным выше.

16
Ilya Gazman

Некоторый kotlin код:

companion object {
    fun newInstance(first: String, second: String) : SampleFragment {
        return SampleFragment().apply {
            arguments = Bundle().apply {
                putString("firstString", first)
                putString("secondString", second)
            }
        }
    }
}

И вы можете получить аргументы с этим:

val first: String by lazy { arguments?.getString("firstString") ?: "default"}
val second: String by lazy { arguments?.getString("secondString") ?: "default"}
12
Rafols

Рекомендуется создавать фрагменты с аргументами в Android, чтобы в вашем фрагменте был статический метод фабрики.

public static MyFragment newInstance(String name, int age) {
    Bundle bundle = new Bundle();
    bundle.putString("name", name);
    bundle.putInt("age", age);

    MyFragment fragment = new MyFragment();
    fragment.setArguments(bundle);

    return fragment;
}

Вы должны избегать установки полей с экземпляром фрагмента. Потому что всякий раз, когда Android система воссоздает ваш фрагмент, если она чувствует, что системе требуется больше памяти, она будет воссоздавать ваш фрагмент, используя конструктор без аргументов.

Вы можете найти больше информации о лучшая практика для создания фрагментов с аргументами здесь.

3
Gunhan

Поскольку вопросы о передовой практике, я бы добавил, что очень часто хорошая идея использовать гибридный подход для создания фрагмента при работе с некоторыми веб-сервисами REST

Мы не можем передать сложные объекты, например некоторую пользовательскую модель, в случае отображения пользовательского фрагмента

Но что мы можем сделать, это проверить onCreate, что пользователь! = Null, а если нет - то вывести его из слоя данных, в противном случае - использовать существующий.

Таким образом, мы получаем возможность воссоздания по ИД пользователя в случае воссоздания фрагмента с помощью Android и ​​привязку к действиям пользователя, а также возможность создавать фрагменты, удерживая объект сам или только его идентификатор.

Что-то нравится это:

public class UserFragment extends Fragment {
    public final static String USER_ID="user_id";
    private User user;
    private long userId;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        userId = getArguments().getLong(USER_ID);
        if(user==null){
            //
            // Recreating here user from user id(i.e requesting from your data model,
            // which could be services, direct request to rest, or data layer sitting
            // on application model
            //
             user = bringUser();
        }
    }

    public static UserFragment newInstance(User user, long user_id){
        UserFragment userFragment = new UserFragment();
        Bundle args = new Bundle();
        args.putLong(USER_ID,user_id);
        if(user!=null){
            userFragment.user=user;
        }
        userFragment.setArguments(args);
        return userFragment;

    }

    public static UserFragment newInstance(long user_id){
        return newInstance(null,user_id);
    }

    public static UserFragment newInstance(User user){
        return newInstance(user,user.id);
    }
}
2
Tigra

Лучший способ создания фрагмента - использовать метод default Fragment.instantiate или создать фабричный метод для создания экземпляра фрагмента.
Внимание: всегда создавайте один пустой конструктор во фрагменте, в то время как восстановление памяти фрагмента вызовет исключение во время выполнения.

0
Mahesh

setArguments() бесполезен. Это только приносит беспорядок.

public class MyFragment extends Fragment {

    public String mTitle;
    public String mInitialTitle;

    public static MyFragment newInstance(String param1) {
        MyFragment f = new MyFragment();
        f.mInitialTitle = param1;
        f.mTitle = param1;
        return f;
    }

    @Override
    public void onSaveInstanceState(Bundle state) {
        state.putString("mInitialTitle", mInitialTitle);
        state.putString("mTitle", mTitle);
        super.onSaveInstanceState(state);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle state) {
        if (state != null) {
            mInitialTitle = state.getString("mInitialTitle");
            mTitle = state.getString("mTitle");
        } 
        ...
    }
}
0
Vadim Star