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

706

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

Fragment newFragment = new MyFragment();

а также

Fragment newFragment = MyFragment.newInstance();

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

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

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

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

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

Грэм Смит
источник
Когда есть параметры, выбора нет, и на это подробно ответят здесь. Тем не менее, остается вопрос о том, как построить фрагмент без аргументов.
Rds
1
Узнав о фабричных шаблонах и о том, как вызывающий класс, не создающий экземпляры самого объекта, помогает их разъединить, я подумал, что это станет сильной стороной метода newInstance (). Я ошибаюсь в этом? Я не видел этот конкретный аргумент в пользу.
Мобильные приложения

Ответы:

1137

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

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

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

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

yydl
источник
7
@Vlasto, к сожалению, статические методы не могут быть переопределены.
AJD
8
@yydl Я думаю, что я чего-то здесь упускаю, разве вы не могли бы использовать конструктор здесь, который все равно создает Bundle и вызывает setArguments (), поскольку он будет вызываться только вашим кодом (а не тогда, когда Android повторно создает ваш фрагмент)?
Майк Тунниклифф
9
@mgibson Вы должны использовать пакет, если хотите, чтобы данные были доступны при последующем воссоздании фрагмента.
yydl
114
Быть вынужденным создавать конструктор без аргументов для фрагментов - потенциально самая большая проблема во всем программировании, где угодно. Это вызывает полное изменение парадигмы в создании объекта и инициализации. Если вы новичок в Android и наткнулись на эту тему, прочитайте ответ выше снова и снова.
rmirabelle
9
Я бы поспорил с этим утверждением. Во-первых, безопасность типов - это проблема языка, а не основы. Во-вторых, IMO, фреймворк входит в область «вещей, которые ваш API никогда не должен делать». Если я хочу передать библиотеку конгрессов в свой конструктор фрагментов, мне следует разрешить. Контракт конструктора «без аргументов» в основном убивает использование внедрения зависимостей во фрагментах - мажор.
rmirabelle
95

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

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

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

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

500865
источник
4
1) Чем это отличается от помещения логики в конструктор? Оба являются отдельными местами, где вы включаете эту логику. 2) Чем параметры статической фабрики отличаются от параметров конструктора? Оба рассказывают, какие аргументы ожидаются. Я уверен, что это другая парадигма, но в этом нет явного преимущества по сравнению с использованием конструкторов.
RJ Катбертсон
2
Вы не можете использовать собственные конструкторы для фрагмента. Framework использует конструктор без аргументов для восстановления фрагментов.
500865
5
Да, я согласен с вами там. Я говорю, что концептуально нет смысла использовать статический шаблон фабрики вместо использования перегруженных конструкторов, и наоборот. Обе ваши точки действительны в обоих шаблонах; нет пользы от использования одного над другим. Android вынуждает вас использовать статический шаблон фабрики - но нет никакой выгоды использовать один или другой.
RJ Катбертсон
pastebin.com/EYJzES0j
RJ Катбертсон
@RJCuthbertson Возможное преимущество - возможность создавать и возвращать подклассы класса статического метода фабрики, то есть возвращать соответствующий подкласс для ситуации.
срочное
62

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

Fragment.instantiate(context, MyFragment.class.getName(), myBundle)
user1145201
источник
Если я не ошибаюсь, это возможно только при использовании библиотеки поддержки Android.
Тимо
2
Попробовал это с помощью библиотеки поддержки, но в onCreateView (в моем фрагменте) передаваемый пакет был нулевым, поэтому я выбрал опцию setArguments / getArguments, и она сработала (для всех, кто читает это).
Jrop
1
Интересно, я не видел такого подхода раньше. Есть ли у него какие-либо преимущества перед другими подходами к созданию фрагмента?
Игорь Ганапольский
22
От разработчиков документов ,instantiate() Creates a new instance of a Fragment with the given class name. This is the same as calling its empty constructor.
Брайан Боуман
2
Хотя они упоминали то же самое, что и вызов пустого конструктора. "args.setClassLoader (f.getClass () getClassLoader ().);" называется ниже для аргументов Bundle
Гекхан Барыш Акер
49

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

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

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

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

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

Чтобы обойти эту проблему, 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:

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

РЕДАКТИРОВАТЬ : Как указано в комментариях @JHH, если вы предоставляете пользовательский конструктор, который требует некоторых аргументов, то Java не будет предоставлять ваш фрагмент конструктор по умолчанию без аргументов . Так что для этого потребуется определить конструктор без аргументов , то есть код, который вы могли бы избежать с помощью newInstanceметода фабрики.

РЕДАКТИРОВАТЬ : Android больше не позволяет использовать перегруженный конструктор для фрагментов. Вы должны использовать newInstanceметод.

PS95
источник
Когда можно было бы оправдать использование android: configChanges = "direction | keyboardHidden | screenSize"?
Люк Эллисон
1
Android Studio теперь выдает ошибку для всех нестандартных конструкторов во фрагментах, так что это больше не работает.
Шехарьяр
6
Святой человек, интересно, сколько разработчиков дроидов когда-либо писали код вне дроида. Это безумие, что мы не можем использовать описанный вами подход. Нет НИКАКИХ убедительных аргументов в любом комментарии о том, почему мы ДОЛЖНЫ использовать статический фабричный метод. Еще более тревожно, что они будут вызывать ошибку при компиляции. Это, безусловно, лучший ответ и показывает, что sfm не приносит никакой пользы.
MPavlak
3
Ну, есть одна тонкая причина. Вы можете создать свой собственный конструктор с аргументами, но все еще должен быть конструктор без аргументов . Поскольку классы всегда имеют неявный конструктор без аргументов , если конструктор с аргументами явно не определен , это означает , что вы должны определить , как ваш Arg-конструктор и без аргументов конструктора явно, или система не сможет вызвать любого безошибочный конструктор. Я полагаю, что именно поэтому рекомендуется вместо этого использовать статический метод фабрики - это просто снижает риск того, что вы забудете определить конструктор без аргументов.
JHH
@JHH, который потерпит неудачу во время самой компиляции, так что риск не велик. Однако проблема здесь в том, что перегрузка конструктора, ключевая парадигма программирования, отвергается Android.
PS95
20

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

Илья Газман
источник
4
Еще одно преимущество статического метода, о котором я не упомянул выше, заключается в том, что вы не можете случайно установить свойства из него.
Ыйдл
4
Кроме того, что касается вашей точки зрения о «расширении этого фрагмента», этот метод будет действительно очень плох, если вы когда-нибудь расширите класс. Вызов super приведет к тому, что вызов setArguments () будет эффективен только для дочернего или родительского элемента, но не для обоих!
Ыйдл
2
@yydle, вы можете избежать этой ситуации, вызвав аргументы get для инициализации дочернего пакета. Java всегда лучше.
Илья Газман,
9
Правда, но это еще одна причина, чтобы побудить людей использовать шаблон, предложенный Google. Конечно, мы все согласны с тем, что ваше решение технически осуществимо на 100%. Примерно так же есть много способов сделать много вещей. Вопрос, однако, в том, является ли это лучшим. И я твердо убежден, что использование конструктора не отражает истинной природы того, как это должно работать.
yydl
3
Я согласен с @yydl, что статическое создание лучше. Еще одно преимущество - внедрение зависимостей в будущие новые зависимости - конструктор плохо приспособлен для этого и, вероятно, приведет к большему изменению кода (или добавлению большего количества конструкторов).
Благо
19

Некоторый код котлина :

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"}
Rafols
источник
3

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

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

Gunhan
источник
2

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

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

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

Таким образом, мы получаем возможность воссоздания по userId в случае воссоздания фрагментов 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);
    }
}
Tigra
источник
3
Вы сказали: «Мы не можем передавать сложные объекты, например, некоторые пользовательские модели» - это не так, мы можем. Вот так: User user = /*...*/ поместите пользователя в комплект: Bundle bundle = new Bundle(); bundle.putParcelable("some_user", user); и получите пользователя из аргументов: User user = getArguments().getParcelable("some_user"); объект должен быть реализован интерфейс Parcelable. ссылка
Адам Varhegyi
3
Ну, да, но когда класс сложный и содержит ссылки на другие объекты ... Я лично предпочитаю, чтобы он был простым, либо у меня есть объект, либо нет, и мне нужно его получить
Tigra
1

используйте этот код 100% решить вашу проблему

введите этот код в первый фрагмент

public static yourNameParentFragment newInstance() {

    Bundle args = new Bundle();
    args.putBoolean("yourKey",yourValue);
    YourFragment fragment = new YourFragment();
    fragment.setArguments(args);
    return fragment;
}

этот образец отправляет логические данные

и в SecendFragment

yourNameParentFragment name =yourNameParentFragment.newInstance();
   Bundle bundle;
   bundle=sellDiamondFragments2.getArguments();
  boolean a= bundle.getBoolean("yourKey");

в первом фрагменте значение должно быть статическим

счастливый код

Амин Эмади
источник
0

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

Махеш
источник
0

Я в последнее время здесь. Но кое-что, что я только что знал, могло бы тебе немного помочь.

Если вы используете Java, ничего особенного не изменится. Но для разработчиков kotlin вот следующий фрагмент, который, я думаю, может сделать вас основой для работы:

  • Родительский фрагмент:
inline fun <reified T : SampleFragment> newInstance(text: String): T {
    return T::class.java.newInstance().apply {
        arguments = Bundle().also { it.putString("key_text_arg", text) }
    }
}
  • Нормальный звонок
val f: SampleFragment = SampleFragment.newInstance("ABC")
// or val f = SampleFragment.newInstance<SampleFragment>("ABC")
  • Вы можете расширить родительскую операцию init в классе дочерних фрагментов:
fun newInstance(): ChildSampleFragment {
    val child = UserProfileFragment.newInstance<ChildSampleFragment>("XYZ")
    // Do anything with the current initialized args bundle here
    // with child.arguments = ....
    return child
}

Удачного кодирования.

vhfree
источник
-2

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");
        } 
        ...
    }
}
Вадим Стар
источник
За исключением того, что вы только что были вынуждены переопределить еще один метод и создать поле, которое могло бы быть изолировано для onViewCreatedобласти. Я думаю, это удобно, много способов сделать то же самое. Также это простой способ проверить наличие обновлений, выполненных пользователем (сравнить комплекты getArgumentsи комплекты onSaveInstanceState)
Overclover
@Asagen, мне нравится твой комментарий о сравнении исходных и пользовательских значений. Я отредактировал код и считаю, что он все еще единообразен и понятен без каких-либо getArgumentsвещей. Как насчет onViewCreatedобласти ... Мы можем восстановить состояние пакета там. Но я просто предпочитаю делать onCreateViewлегкие и быстрые и выполнять всю тяжелую инициализацию внутри, onActivityCreatedпотому что Fragment.getActivity()иногда хочется вернуться nullи из-за onAttach()изменений в новой версии API 23.
Вадим Стар,
Все, что вы сделали здесь, были двигаться set и get Argumentsв saveInstanceState. По сути дела, вы делаете то же самое, что и «под капотом»
OneCricketeer
1
@ cricket_007 или прямо напротив . Использование saveInstanceState"под капотом". И использование Arguments- это дублирование функциональности, которое заставляет вас дважды проверять: сначала Argumentsзначения, а затем saveInstanceStateзначения. Потому что вы должны использовать saveInstanceStateлюбой способ. Как насчет Arguments... они не нужны.
Вадим Стар
Аргументы являются эквивалентом дополнений Intent для фрагментов. Они не бесполезны, они содержат исходные параметры, которые отличаются от текущего состояния.
BladeCoder
-12

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

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;
   }
Стефан Богард
источник
12
mObjects будет очищен, если MyFragment будет воссоздан (пользователь переходит на домашний экран устройства, а затем открывает приложение, которое остановилось на MyFragment). Вы можете сохранить объекты mObject, отправив MyFragment в виде аргументов.
Иннадкрап
1
Кроме того, как статический метод обращается к нестатическим переменным-членам?
OrhanC1
2
@ynnadkrap Вы правы, использование связки - путь сюда.
Стефан Богард
2
@ OrhanC1 Согласно этому примеру кода, статический метод не имеет доступа к переменным-членам. Экземпляр MyFragment имеет доступ к своим членам. Здесь нет ошибки. Тем не менее, я не рекомендую этот ответ никому, потому что, когда ваш фрагмент удаляется из памяти, чтобы освободить пространство для Android, после перезапуска действия, и этот фрагмент будет создан с пустым конструктором по умолчанию без назначения переменных муравья.
Гунхан
@ Gunhan Ты прав! Это не. Извините за путаницу :)
OrhanC1