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

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

Fragment newFragment = new MyFragment();

и

Fragment newFragment = MyFragment.newInstance();

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

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

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

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

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

Ответ 1

Если 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;
}

И позже в фрагменте onCreate() вы можете получить доступ к этому целому числу, используя:

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

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

Также обратите внимание: setArguments может быть вызван только до того, как Фрагмент будет прикреплен к Activity.

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

Ответ 2

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

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

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

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

Ответ 3

Существует и другой способ:

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

Ответ 4

Хотя @yydl дает вескую причину, почему лучше использовать метод newInstance:

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

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

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

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

Чтобы обходной путь, андроид просит сохранить данные с помощью Bundle (вызов setArguments()), к которому затем можно получить доступ из YourFragment. Аргумент Bundle защищен 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 Fragment Class:

/**
     * 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 не предоставит вашему фрагменту конструктор default arg. Таким образом, вам потребуется определить конструктор no arg, который можно избежать с помощью метода newInstance factory.

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

Ответ 5

Я не согласен с 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 добавил проверку, что флаг всех нестандартных конструкторов с ошибкой.
Я рекомендую отключить его по указанным выше причинам.

Ответ 6

Некоторый код 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"}

Ответ 7

Лучшая практика для фрагментов экземпляра с аргументами в android заключается в том, что в вашем фрагменте есть статический метод factory.

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 воссоздает ваш фрагмент, если он чувствует, что системе требуется больше памяти, чем она воссоздает ваш фрагмент, используя конструктор без аргументов.

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

Ответ 8

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

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

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

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

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

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);
    }
}

Ответ 9

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

Ответ 10

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");
        } 
        ...
    }
}

Ответ 11

Я считаю, что у меня есть очень простое решение для этого.

public class MyFragment extends Fragment{

   private String mTitle;
   private List<MyObject> mObjects;

   public static MyFragment newInstance(String title, List<MyObject> objects)
   MyFragment myFrag = new MyFragment();
   myFrag.mTitle = title;
   myFrag.mObjects = objects;
   return myFrag;
   }