Найкраща практика для створення нового фрагмента Android


706

Я бачив дві загальні практики для створення нового Фрагменту в додатку:

Fragment newFragment = new MyFragment();

і

Fragment newFragment = MyFragment.newInstance();

Другий варіант використовує статичний метод newInstance()і, як правило, містить наступний метод.

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

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

Я щось пропустив?

Які переваги одного підходу над іншим? Або це просто хороша практика?


Коли є параметри, вибору немає, і тут широко відповіді. Все ж залишається питанням без аргументації побудови фрагмента.
rds

1
Дізнавшись про заводські шаблони та про те, як клас виклику, який не створює інстанціювання самого об'єкта, допомагає роз'єднати їх, я подумав, що це буде сильним моментом для методу newInstance (). Я помиляюся на цьому? Я не бачив у цьому конкретному аргументі користі.
Мобільні додатки

Відповіді:


1136

Якщо 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


7
@Vlasto, на жаль, статичні методи не можна перекрити.
AJD

8
@yydl Я думаю, мені тут щось не вистачає, чи не вдалося б ви тут використовувати конструктор, який створює пакет і виклику 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()для екземпляру фрагмента є хорошою практикою.


4
1) Чим це відрізняється від логіки в конструкторі? Обидва є єдиними місцями, де ви включаєте цю логіку. 2) Як параметри статичної фабрики відрізняються від параметрів на конструкторі? Обидва розповідають, які аргументи очікуються. Моя річ, що це інша парадигма, звичайно, але явної користі для цього від використання конструкторів немає.
RJ Cuthbertson

2
Ви не можете використовувати власні конструктори для фрагмента. Framework використовує конструктор без аргументів для відновлення фрагментів.
500865

5
Так, я згоден з вами там. Я кажу, що концептуально немає користі від використання статичної заводської структури замість використання перевантажених конструкторів, і навпаки. Обидва ваші бали дійсні в обох моделях; немає користі від використання одного над іншим. Android змушує вас використовувати статичний заводський візерунок, але використовувати ту чи іншу користь немає.
RJ Cuthbertson

pastebin.com/EYJzES0j
RJ Cuthbertson

@RJCuthbertson Можливою вигодою буде можливість створення та повернення підкласів класу статичного фабричного методу, тобто повернення відповідного підкласу ситуації.
терміново

62

Існує також інший спосіб:

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

Якщо я не помиляюся, це можливо лише тоді, коли ви користуєтеся бібліотекою підтримки 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 ());" називається під аргументами групи
Gökhan Barış Aker

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 просить встановити аргументи лише під час будівництва, і гарантує, що вони будуть збережені.

EDIT : Як зазначалося в коментарях @JHH, якщо ви надаєте користувальницький конструктор, який вимагає певних аргументів, Java не надасть вашому фрагменту конструктор за замовчуванням без аргументів . Тому для цього потрібно визначити конструктор no arg , який є кодом, якого ви можете уникнути newInstanceфабричним методом.

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


Коли можна виправдати використання android: configChanges = "орієнтація | клавіатураHidden | screenSize"?
Люк Еллісон

1
Тепер Android Studio у фрагментах видаляє помилку для всіх конструкторів, що не використовуються за замовчуванням, тому це більше не працює.
Шехаряр

6
Свята людина, мені цікаво, скільки розробників дроїдів коли-небудь писали код поза дроїдом. Це божевільно, що ми не можемо використовувати описаний вами підхід. З жодного коментаря немає переконливих аргументів щодо того, чому ми маємо використовувати статичний заводський метод. Ще більше тривожно, що вони склали б помилку при складанні. Це, безумовно, найкраща відповідь, яка свідчить про те, що sfm не має ніякої користі.
МПавлак

3
Ну, є одна тонка причина. Ви можете створити власний конструктор з аргументами, але все-таки повинен бути і конструктор без аргументів . Оскільки класи завжди мають неявний конструктор без аргументів , якщо конструктор з аргументами явно не визначений , це означає , що ви повинні визначити , як ваш Arg-конструктор і без аргументів конструктора явно, або система не зможе викликати будь-якого no-arg конструктор. Я вважаю, що тому рекомендується використовувати статичний заводський метод - це просто знижує ризик забути визначити конструктор no-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
Ще одна перевага статичного методу, про який я не згадував вище, полягає в тому, що ви не можете випадково встановити з нього властивості.
yydl

4
Крім того, що стосується вашої точки зору "розширити цей фрагмент", цей метод насправді був би дуже поганим, якщо ви коли-небудь розширите клас. Виклик супер призведе до того, що виклик setArguments () буде ефективним лише для дитини або батька, але не для обох!
yydl

2
@yydle Ви можете уникнути цієї ситуації, зателефонувавши отримати аргументи, щоб ініціалізувати дочірню групу. Ява спосіб завжди кращий.
Ілля Газман

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

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

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


2

Оскільки до питань найкращої практики я додам, що дуже часто добре використовувати гібридний підхід для створення фрагмента під час роботи з деякими веб-сервісами 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);
    }
}

3
Ви сказали: "Ми не можемо передавати складні об'єкти, наприклад, якусь модель користувача", - Це неправда, ми можемо. Ось так: User user = /*...*/ покладіть користувача в пакет: Bundle bundle = new Bundle(); bundle.putParcelable("some_user", user); та отримайте користувача від аргументів: User user = getArguments().getParcelable("some_user"); Об'єкт повинен реалізувати інтерфейс Parcelable. посилання
Адам Вархегій

3
Ну, так, але коли клас складний і містить посилань на інші об'єкти ... Я особисто вважаю за краще просто робити це, або у мене є об'єкт, або я не маю, і мені потрібно його отримати
Tigra

1

використовувати цей код на 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");

Значення "must" в першому фрагменті є статичним

щасливий код


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
}

Щасливе кодування.


-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легкою і швидкою і робити всі важкі ініціалізації всередині a, onActivityCreatedбо Fragment.getActivity()іноді люблю повертатися nullі через onAttach()зміни в новій версії API 23.
Vadim Star

Все, що ви тут робили, - це переїзд set і get Argumentsв'їзд saveInstanceState. Ви по суті робите те саме, що робиться "під капотом"
OneCricketeer

1
@ cricket_007, або навпаки . Використання saveInstanceState"під кришкою". А використання - Argumentsце дублювання функціональності, що дозволяє вам подвійно перевірити: спочатку Argumentsзначення, а потім saveInstanceStateзначення. Тому що ви повинні використовувати saveInstanceStateбудь-який спосіб. А як Arguments... вони не потрібні.
Вадим Зірка

Аргументи - еквівалент Намірних додаткових для фрагментів. Вони не марні, містять початкові параметри, які відрізняються від поточного стану.
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). Ви можете зберегти mObjects, надіславши пакет MyFragment як аргументи.
ynnadkrap

1
Крім того, як статичний метод отримує доступ до нестатичних змінних членів?
OrhanC1

2
@ynnadkrap Ви маєте рацію, використовуючи пакет, це спосіб перейти сюди.
Стефан Богаард

2
@ OrhanC1 Відповідно до цього прикладу коду, статичний метод не має доступу до змінних-членів. Екземпляр MyFragment має доступ до своїх членів. Тут немає помилок. Однак я не рекомендую цю відповідь нікому, тому що, коли ваш фрагмент буде вилучений із пам'яті, щоб відкрити деякий простір андроїд ОС, то після перезапуску діяльності, і цей фрагмент буде створений з пустим конструктором за замовчуванням, не призначаючи змінних мурахи.
Гунхань

@Gunhan Ви маєте рацію! Це не. Вибачте за плутанину :)
OrhanC1
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.