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

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

Fragment newFragment = new MyFragment();

а также

Fragment newFragment = MyFragment.newInstance();

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

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

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

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

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

17 ответов

Решение

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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"}

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

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

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

  • Родительский фрагмент:
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")
  • Вы можете расширить родительскую операцию инициализации в классе дочернего фрагмента следующим образом:
fun newInstance(): ChildSampleFragment {
    val child = UserProfileFragment.newInstance<ChildSampleFragment>("XYZ")
    // Do anything with the current initialized args bundle here
    // with child.arguments = ....
    return child
}

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

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

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

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

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

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

Таким образом, мы получаем возможность воссоздания по 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);
    }
}

1. В идеале мы не должны ничего передавать в конструктор фрагмента, конструктор фрагмента должен быть пустым или заданным по умолчанию. 2. Теперь второй вопрос: что, если мы хотим передать интерфейсную переменную или параметры - a. мы должны использовать Bundle для передачи данных.

б. Для интерфейса мы можем поместить Parceble в пакет и сделать этот интерфейс реализуемым parceble.

c. Если возможно, мы можем реализовать этот интерфейс в действии, а во фрагменте мы можем инициализировать слушателя в OnAttach, где у нас есть context[(context) Listener].

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

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

введите этот код в firstFragment

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

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

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

Создайте экземпляр фрагмента, используя код kotlin.

Пишите в активности

      val fragment = YourFragment.newInstance(str = "Hello",list = yourList)

Напишите во фрагменте

      fun newInstance(str: String, list: ArrayList<String>): Fragment {
        val fragment = YourFragment()
        fragment.arguments = Bundle().apply {
            putSerializable("KEY_STR", str)
            putSerializable("KEY_LIST", list)
        }
        return fragment
    }

Используя тот же фрагмент, извлеките данные из пакета

      val str = arguments?.getString("KEY_STR") as? String
val list = arguments?.getSerializable("KEY_LIST") as? ArrayList<String>

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

Вы можете использовать что-нибудь так:

val fragment = supportFragmentManager.fragmentFactory.instantiate(classLoader, YourFragment::class.java.name)

потому что этот ответ сейчас устарел

Лучший СПОСОБ , а не ПРАКТИКА - использовать конструктор, поэтому он называется конструктором. Язык C и другие подобные языки разработаны таким образом. Но почему Android использует newInstance? Это потому, что они испортили Android. Сейчас я недолго работаю с Android, и это полная катастрофа! Они пытаются улучшить его с помощью обновленных версий таких компонентов, как PageView2. Что-то должно быть не так с первым, иначе вам не понадобится второй. Так что лучше всего использовать конструкторы с классами, но поскольку Google облажался, вам нужен newInstance. И это правильный ответ :-)

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

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

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;
   }
Другие вопросы по тегам