Поддержка диспетчера фрагментов .replace() и ViewModel

Похоже, что всякий раз, когда вы используете метод .replace() в транзакции с диспетчером фрагментов поддержки, ViewModel воссоздается. Это намеренно? Сам экземпляр Fragment не меняется, и ViewModel будет (частично) сохраняться при изменении ротации/конфигурации.

Я вижу следующие сценарии:

  1. Получить ссылку на модель представления (count = 0), обновить count = 1, повернуть, count = 1, onCreate вызвать снова и count = 0 (модель представления воссоздана).
  2. Вызовите .replace(), и модель представления будет воссоздана (экземпляры действий и фрагментов не изменились).

Использование библиотеки поддержки 26.0.0.

ViewModel создается в onCreate моего фрагмента и ограничен Fragment:

viewModel = ViewModelProviders.of(this).get(DashboardViewModel::class.java)

Кто-нибудь подскажет, нормально ли это?


person Luke    schedule 30.07.2017    source источник
comment
Сам экземпляр Fragment не меняется — что вы имеете в виду? ViewModel реализован как дочерний сохраненный фрагмент любых FragmentActivity или Fragment, которые вы передаете в ViewModelProviders.of().   -  person CommonsWare    schedule 30.07.2017
comment
В действии у меня есть массив фрагментов, и я просто переключаюсь между фрагментами в массиве (используя нижнюю навигацию): fragments = mapOf(id to fragment, etc) Затем позже: supportFragmentManager.beginTransaction().replace(R.id.fragmentContainer, fragment).commit() Я вижу, что ссылки на фрагменты одинаковы (не воссоздаются).   -  person Luke    schedule 30.07.2017
comment
Если я перехожу к сфере деятельности, она отлично работает, кстати. viewModel = ViewModelProviders.of(activity).get(DashboardViewModel::class.java)   -  person Luke    schedule 30.07.2017
comment
Итак, просто для ясности: вы создали фрагмент, добавили его через FragmentTransaction, запустили другую транзакцию, которая заменила его, запустили другую транзакцию replace(), которая вернула его... и затем вы потеряли свой ViewModel? Возможно, когда фрагмент снова управляется FragmentManager, его вложенный FragmentManager заменяется. Я избегаю вложенных фрагментов, как чумы, поэтому я не знаю правил для дочерних фрагментов фрагмента, а ViewModel поддерживается сохраненным дочерним фрагментом вашего фрагмента.   -  person CommonsWare    schedule 30.07.2017
comment
Правильно ко всему вышесказанному.   -  person Luke    schedule 30.07.2017
comment
Если вы можете создать воспроизводимый тестовый пример, попробуйте подать заявку. Как минимум, это должно быть задокументировано.   -  person CommonsWare    schedule 30.07.2017


Ответы (2)


Как упоминалось в @CommonsWare, экземпляр viewmodel внутри fragment должен быть таким же в Activity.

Поэтому внутри Activity вы должны сделать что-то вроде этого

MyViewModel vm = ViewModelProviders.of(this).get(MyViewModel.class);

Внутри фрагмента вы должны сделать что-то вроде этого

MyViewModel vm = ViewModelProviders.of(getActivity()).get(MyViewModel.class);

В результате они будут использовать один и тот же экземпляр.

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

MyViewModel vm = ViewModelProviders.of(this).get(MyViewModel.class);

Модель представления будет воссоздана во фрагменте при повороте устройства. Поскольку экземпляр сохраняется внутри фрагмента, а не действия, при повторном создании действия фрагмент также будет воссоздан и экземпляр MyViewModel.

Попробуйте взглянуть на пример фрагмента главной детали (который может быть легко решить вашу проблему) ViewModel в Android Developer

public class MasterFragment extends Fragment {
    private SharedViewModel model;
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        itemSelector.setOnClickListener(item -> {
            model.select(item);
        });
    }
}

public class DetailFragment extends LifecycleFragment {
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        model.getSelected().observe(this, { item ->
           // update UI
        });
    }
}

Я также сделал простой шаблон Master Detail на github.

SimpleDetailActivity.java

SimpleDetailFragment.java

person Long Ranger    schedule 28.09.2017
comment
Я не думаю, что это то, к чему стремились ребята из Android, когда разрабатывали API. Если передача this (фрагмента) в качестве хранилища ViewModel не является тем, что им нужно, они могут легко отрицать это, заставив Activity. Очевидно, что передача Fragment является правильным решением, если вы хотите, чтобы ViewModel было привязано к Fragment. Чтобы обратиться к сообщению о том, почему создается новый экземпляр, это отдельная тема для этого комментария. - person TareK Khoury; 17.05.2020

Предполагается, что replace() вызывается при создании другого фрагмента. Для того же фрагмента вы вызываете update().

Метод replace() означает, что вы можете заменить текущий фрагмент другим фрагментом, который имеет другую компоновку (физическую структуру). Вы даже не можете гарантировать, что он запустится из той же памяти, что и предыдущий фрагмент. ViewModel - это вид макета для всего контейнера. Таким образом, для объекта с другой физической структурой и (возможно, с другой памятью — я пишу возможно, потому что вы также можете заменить его тем же фрагментом), вы должны воссоздать разные ViewModel для определения это контейнер. Это связано с тем, что один объект ViewModel указывает на один ссылочный контейнер, в следующий раз, когда у вас будет другой фрагмент, ваш контейнер фрагмента определяется ViewModel где-то еще, поэтому вам нужен другой объект ViewModel, чтобы указать на этот контейнер фрагмента.

Но когда вы делаете update() или rotate(), вы гарантируете, что пространство памяти обновленного фрагмента может уменьшаться/увеличиваться, но при этом начальная память остается неизменной. Так что нет необходимости создавать файл ViewModel. Это связано с тем, что ваш старый объект ViewModel ссылается на тот же контейнер старого фрагмента.

Когда вы делаете create(), он создает все GUI, поэтому, очевидно, снова происходит ViewModel создание.

Отслеживание ViewModel count основано на приведенном выше объяснении.

ViewModel создается в onCreate моего фрагмента и ограничивается фрагментом. Это своего рода делегирование власти фрагменту.

person Uddhav Gautam    schedule 30.07.2017