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

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

Fragment newFragment = new MyFragment();

и

Fragment newFragment = MyFragment.newInstance();

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

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

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

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

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

Ответы [ 11 ]

1059 голосов
/ 12 февраля 2012

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

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

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

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

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

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

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

94 голосов
/ 12 февраля 2012

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

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

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

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

61 голосов
/ 09 марта 2013

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

Fragment.instantiate(context, MyFragment.class.getName(), myBundle)
40 голосов
/ 16 июня 2015

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

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

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

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

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

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

Один из способов установить этот пакет - использовать статический метод 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.

16 голосов
/ 23 сентября 2014

Я не согласен с 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 вы не можете этого сделать.

Обновление:

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

12 голосов
/ 17 января 2018

Некоторые 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"}
3 голосов
/ 01 ноября 2015

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

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

2 голосов
/ 04 марта 2015

Поскольку вопросы о передовом опыте, я бы добавил, очень часто хорошая идея использовать гибридный подход для создания фрагмента при работе с некоторыми веб-сервисами 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);
    }
}
0 голосов
/ 06 декабря 2016

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 голосов
/ 30 июня 2015

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...