Совместное использование ViewModel между фрагментами, находящимися в разных действиях

У меня есть ViewModel с именем SharedViewModel:

public class SharedViewModel<T> extends ViewModel {

    private final MutableLiveData<T> selected = new MutableLiveData<>();


    public void select(T item) {
        selected.setValue(item);
    }

    public LiveData<T> getSelected() {
        return selected;
    }
}

Я реализовал его на основе примера SharedViewModel на справочной странице Google Arch ViewModel:

https://developer.android.com/topic/libraries/architecture/viewmodel.html#sharing_data_between_fragments

Очень часто два или более фрагмента в действии должны взаимодействовать друг с другом. Это никогда не бывает тривиальным, поскольку оба фрагмента должны определять некоторое описание интерфейса, а действие владельца должно связывать их вместе. Более того, оба фрагмента должны обрабатывать случай, когда другой фрагмент еще не создан или не отображается.

У меня есть два фрагмента, которые называются ListFragment и DetailFragment.

До сих пор я использовал эти два фрагмента внутри действия под названием MasterActivity, и все работало хорошо.

Я получил ViewModel в ListFragment, выбрал значение, чтобы использовать его на DetailFragment.

mStepSelectorViewModel = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);

Однако теперь в некоторых случаях мне нужно, чтобы ListFragment (макет для другой конфигурации устройства) был добавлен в другое действие, называемое DetailActivity. Есть ли способ сделать это аналогично приведенному выше примеру?


comment
Вы боретесь с фреймворком. Если он переходит к другому действию, называемому DetailActivity, тогда создайте ViewModel для вашего детального действия. ViewModels - это не просто «волшебные объекты, которые могут пережить изменение конфигурации». Они являются связующим звеном между вашими взглядами и вашей моделью. Если у вас другое действие, ваше представление теперь другое, поэтому оно требует новой модели представления. Если вы разделите свои проблемы, как того ожидает Google, то «репозиторий», имеющий доступ к данным, может / будет использоваться этими моделями представления, но это разные сущности.   -  person Martin Marconcini    schedule 20.06.2017
comment
Если вы переходите к новому Activity, не следует ли вам просто использовать Parcelable со своим объектом?   -  person OneCricketeer    schedule 20.06.2017
comment
Реализуйте интерфейс для прослушивания того, какой элемент списка выбран в ваших действиях, включая ListFragment. А затем в ListFragment получите интерфейс в событии onAttach. Передайте выбранный элемент в DetailFragment в MasterActivity с помощью findFragmentByTag и передайте его в DetailActivity в других действиях.   -  person sunghun    schedule 02.10.2017
comment
Похоже, Google работает над решением. github.com/googlesamples/android-architecture-components/issues/ и issueetracker.google.com/issues/64988610   -  person paul    schedule 26.10.2017
comment
См. stackoverflow.com/questions/56521969/   -  person Levon Petrosyan    schedule 10.06.2019


Ответы (6)


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

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

class MyApp: Application(), ViewModelStoreOwner {
    private val appViewModelStore: ViewModelStore by lazy {
        ViewModelStore()
    }

    override fun getViewModelStore(): ViewModelStore {
        return appViewModelStore
    }
}

Затем в тех случаях, когда вы знаете, что вам нужно разделить ViewModels между границами активности, вы делаете что-то вроде этого.

val viewModel = ViewModelProvider(myApp, viewModelFactory).get(CustomViewModel::class.java)

Итак, теперь он будет использовать Магазин, определенный в вашем приложении. Таким образом, вы можете поделиться ViewModels.

Очень важно. Поскольку в этом примере ViewModels живут в экземпляре вашего приложения, они не будут уничтожены, когда фрагмент / действие, которое их использует, будет уничтожено. Таким образом, вам придется связать их с жизненным циклом последнего фрагмента / действия, которое будет их использовать, или вручную уничтожить их.

person mikehc    schedule 19.05.2018
comment
Ух ты! Я никогда не думал об этом. Есть ли что-нибудь, о чем мне следует позаботиться, если я сделаю такой подход? - person Archie G. Quiñones; 27.02.2019
comment
какие ошибки мне нужно знать? - person Archie G. Quiñones; 27.02.2019
comment
Просто в зависимости от того, где вы решите разместить ViewModelStore, вам нужно будет вручную очистить хранилище, чтобы избежать утечек памяти. Если вы последуете примеру (сохраните его в экземпляре приложения) в моем ответе, вам нужно будет это сделать. - person mikehc; 28.02.2019
comment
@mikehc, спасибо. Я помещаю ViewModelStore в экземпляр приложения, как вы отвечаете. У меня есть два действия: A и B. A никогда не уничтожается (если не закрывается приложение), а B часто открывается и закрывается. Итак, как я могу очистить ViewModelStore? Я использовал метод onDestroy для B (application as MyApplication).viewModelStore.clear(), но когда я снова начинаю действие B, viewModel не работает - person leotesta; 14.03.2019
comment
@leotesta похоже, что проблема может быть в вашем ViewModelFactory. Но все зависит от вашей реализации. Я рекомендую вам создать новый вопрос с этой проблемой, когда вы поделитесь небольшой частью своей реализации. - person mikehc; 14.03.2019
comment
@mikehc - как использовать ViewModelStoreOwner фрагмента или активности? Мой класс ViewModel принадлежит к классу Fragment, а не к классу Application. - person Mangesh Kadam; 26.06.2019
comment
@MangeshKadam, в зависимости от того, что вы используете в ViewModelProviders.of(), вы будете использовать фрагмент или хранилище моделей представления активности. Если вы передадите фрагмент в качестве параметра, вы будете использовать хранилище фрагментов и наоборот. Просто примечание, ViewModels (сами по себе) не имеют жестких ограничений относительно того, где они могут быть сохранены. Они могут храниться в любом хранилище ViewModelStore, будь то в Fragment, Activity или любом другом классе, таком как Application. - person mikehc; 27.06.2019
comment
Мне нужно создать экземпляр фрагмента для передачи в качестве параметра. Это хороший вариант? - person Mangesh Kadam; 27.06.2019
comment
Обычно вы передаете экземпляр фрагмента или действия, которые будут использовать ViewModel. Если вы хотите разделить границы, вам нужно использовать что-то еще. - person mikehc; 28.06.2019
comment
@mikehc В качестве подсказки вам нужно очистить ViewModelStore в конце, поэтому ViewModel также будет очищен - person Farshad Tahmasbi; 21.08.2019

вы можете использовать factory для создания модели представления, и этот фактор вернет один объект модели представления .. Как:

class ViewModelFactory() : ViewModelProvider.Factory {

override fun create(modelClass: Class): T {
    if (modelClass.isAssignableFrom(UserProfileViewModel::class.java)) {
    val key = "UserProfileViewModel"
    if(hashMapViewModel.containsKey(key)){
        return getViewModel(key) as T
    } else {
        addViewModel(key, UserProfileViewModel())
        return getViewModel(key) as T
    }
    }
    throw IllegalArgumentException("Unknown ViewModel class")
}

companion object {
    val hashMapViewModel = HashMap<String, ViewModel>()
    fun addViewModel(key: String, viewModel: ViewModel){
        hashMapViewModel.put(key, viewModel)
    }
    fun getViewModel(key: String): ViewModel? {
        return hashMapViewModel[key]
    }
}
}

В действии:

viewModelFactory = Injection.provideViewModelFactory(this)

// Initialize Product View Model
userViewModel = ViewModelProviders.of(this, viewModelFactory).get(
UserProfileViewModel::class.java)`

Это предоставит только один объект UserProfileViewModel, который вы можете использовать между Activity.

person Munish Thakur    schedule 14.12.2017
comment
Что насчет этой строки Injection.provideViewModelFactory (this), как заставить ее работать? - person K.Os; 22.12.2017
comment
Инъекция - это настраиваемый класс, который отвечает за предоставление экземпляра ViewModelFactory. Вот класс: объект Injection {fun provideViewModelFactory (context: Context): ViewModelFactory {return ViewModelFactory ()}} - person Munish Thakur; 23.12.2017
comment
См. Здесь комментарий herriojr github.com/googlesamples/android-architecture-components/ issues / почему это не может быть решением. - person Henry; 10.07.2018

Что ж, я создал для этой цели библиотеку под названием Vita, вы можете делиться ViewModels между действиями и даже фрагментами с различная активность хоста:

val myViewModel = vita.with(VitaOwner.Multiple(this)).getViewModel<MyViewModel>()

Созданные таким образом ViewModel остаются в живых до тех пор, пока последний LifeCycleOwner не будет уничтожен.

Также вы можете создавать ViewModels с областью применения:

val myViewModel = vita.with(VitaOwner.None).getViewModel<MyViewModel>()

И этот тип ViewModel будет очищен, когда пользователь закроет приложение.

Попробуйте и сообщите мне свой отзыв: https://github.com/FarshadTahmasbi/Vita

person Farshad Tahmasbi    schedule 21.08.2019

Думаю, нас все еще путают с фреймворком MVVM на Android. Что касается другого занятия, не путайтесь, потому что оно обязательно должно быть таким же, почему?

Это имеет смысл, если он имеет ту же логику (даже если логика все еще может быть абстрактной в других полезных классах) или если представление в XML почти идентично.

Возьмем небольшой пример:

Я создаю ViewModel с именем vmA и действие с именем A, и мне нужны данные пользователя, я пойду, чтобы вставить репозиторий в vmA пользователя.

Теперь мне нужно другое действие, которое должно считывать данные пользователя. Я создаю еще одну модель представления, называемую vmB, и в ней я вызываю репозиторий пользователей. Как описано, репозиторий всегда один и тот же.

Другой уже предложенный способ - создать N экземпляров одной и той же ViewModel с реализацией Factory.

person AlexPad    schedule 01.11.2018

Если вам нужна ViewModel, которая используется всеми вашими действиями (в отличие от некоторых), то почему бы не сохранить то, что вы хотите сохранить в этой ViewModel, внутри вашего класса Application?

Тенденция, представленная на последнем Google I / O, похоже, состоит в том, чтобы отказаться от концепции Activity в пользу приложений с одним действием, которые имеют много фрагментов. ViewModels - это способ удалить большое количество интерфейсов, которые раньше приходилось реализовывать при работе интерфейса. Таким образом, этот подход больше не ведет к гигантским и недостижимым действиям.

person Marcus Wolschon    schedule 11.01.2019
comment
Я согласен с вами в отношении уменьшения количества действий в приложении, хотя все еще есть сценарии, в которых вам нужно действие. Например, действие настроек (с фрагментом настроек), и вы хотите поделиться моделью просмотра между двумя моделями просмотра после изменения настроек - person Anton Makov; 25.09.2019
comment
Нет, настройки постоянны. ViewModels, используемые вне настроек, должны обновляться по мере изменения базовых данных. Настройки не изменяют временные данные, такие как наполовину набранное сообщение в блоге, которое еще не отправлено или не сохранено. Они меняют постоянные настройки. Это будет работа Livedata, возвращенная Room или оболочкой для набора свойств. - person Marcus Wolschon; 25.09.2019
comment
Я понимаю вашу точку зрения, имеет смысл иметь оболочку вокруг набора свойств. Единственное, чего я до сих пор не понимаю, это как вы соединяетесь между этой оболочкой и моделью представления, которую нужно изменить. В конце вам нужно будет использовать ViewModelProviders.of (‹---------------- что вы здесь пишете ››, viewModelFactory), чтобы получить тот же экземпляр viewmodel. - person Anton Makov; 25.09.2019
comment
Простой. / android / content / - person Marcus Wolschon; 26.09.2019
comment
Спасибо, в конце концов я закончил использовать этот установленный android.jlelse.eu /. Я думаю, что если вам в конечном итоге понадобится использовать разные модели представления из разных действий, в дизайне вашего приложения может быть поток. - person Anton Makov; 26.09.2019

Вот ссылка

Надеюсь, это поможет тебе. О (∩_∩) О ~

Кроме того:

1) Вдохновением для кода послужил smart pointer in c++.

2) Он будет автоматически очищен, если никакие действия или фрагменты не ссылаются на ShareViewModel. Одновременно будет вызвана функция ShareViewModel # onShareCleared()! Их не нужно уничтожать вручную!

3) Если вы используете dagger2 для вставки ViewModelFactory для совместного использования модели просмотра
между двумя действиями (возможно, тремя), вот образец

person hansey li    schedule 31.07.2018