Как ViewModel переживает пересоздание фрагмента?

Посмотреть в Telegram: @AndroidSobes/236
Ранние версии библиотеки полагались на механизм setRetainInstanceState(), реализация которого подробно описана в статье. На сегодняшний день setRetainInstanceState() больше не используется

Здесь говорим о библиотеках fragment-ktx версии 1.2.4 и lifecycle-viewmodel-ktx версии 2.2.0. Реализация библиотек может быть изменена в будущих версиях.

Для начала разберемся как создается ViewModel:

1. ViewModel рекомендуется создавать через вызов Delegated Property by viewModel, который принимает две функции: ownerProducer: () -> ViewModelStoreOwner и factoryProducer: (() -> Factory)?. По-умолчанию ownerProducer – это функция, возвращающая this, где this – это фрагмент, на котором вызван by viewModel.

2. by viewModel создает и возвращает объект типа ViewModelLazy. При создании этого объекта одним из параметров передается функция storeProducer: () -> ViewModelStore. Эта функция создается следующим образом: { ownerProducer().viewModelStore }, где ownerProducer – функция, заданная на предыдущем шаге.

3. ViewModelLazy имеет один метод get(), который вызывается при обращении к property, заданной через by viewModel. При первом вызове метода get() создается объект ViewModel и сохраняется в классе ViewModelLazy. При последующих вызовах get() возвращается уже созданный ViewModel.

4. Для создания ViewModel сначала создается объект ViewModelProvider, после этого на нем вызывается get() c классом модели в качестве параметра:
ViewModelProvider(store, factory).get(viewModelClass.java)

5. Метод ViewModelProvider.get() создает объект ViewModel с помощью переданной Factory и сохраняет его во ViewModelStore, который хранит значения в HashMap. При последующих вызовах метода ViewModelProvider.get(), ViewModel достается из ViewModelStore.

При описанной реализации граф ссылок на объект ViewModel во фрагменте выглядит так:

Fragment -> ViewModelLazy -> ViewModelProvider -> ViewModelStore -> ViewModel


Т.е. если Fragment уничтожен, то ViewModel тоже будет удалена.
Как же ViewModel переживает пересоздание фрагмента? Ответ кроется во ViewModelStoreOwner, который использовался в by viewModel на первом шаге.

В следующем посте мы разберем как и где сохраняется ViewModelStoreOwner.
В прошлом посте мы разобрали как создается ViewModel. Теперь рассмотрим как ViewModelStoreOwner переживает пересоздание фрагмента.

Как было упомянуто ранее, by viewModel одним из аргументов принимает функцию ownerProducer: () -> ViewModelStoreOwner. По умолчанию используется функция, возвращающая текущий фрагмент: ownerProducer = { this }.

Класс Fragment из Jetpack реализует интерфейс ViewModelStoreOwner, который имеет только один метод getViewModelStore(): ViewModelStore.
Но сам Fragment не хранит объект ViewModelStore, а делегирует вызов во FragmentManager:

mFragmentManager.getViewModelStore(this), где this – текущий фрагмент


FragmentManager, в свою очередь, делегирует вызов в класс FragmentManagerViewModel. Этот класс хранит HashMap, в котором ключами выступают внутренний uuid фрагмента, а значениями – объекты ViewModelStore. Если HashMap не имеет ViewModelStore для запрашиваемого фрагмента, то создается новый инстанс ViewModelStore и сохраняется в HashMap.

Граф ссылок на ViewModel фрагмента выглядит так:

Fragment (as ViewModelStoreOwner) -> FragmentManager -> FragmentManagerViewModel -> HashMap<String, ViewModelStore> -> ViewModelStore -> ViewModel


FragmentManagerViewModel переживает пересоздание фрагмента, а вместе с ним сохраняется и ViewModel.

Следующий вопрос: как FragmentManagerViewModel переживает пересоздание фрагмента?
Трюк заключается в том, что класс FragmentManagerViewModel – это ViewModel, и он сохраняется также как и любой другой объект ViewModel.
Объектом ViewModelStoreOwner для FragmentManagerViewModel выступает FragmentActivity:

Fragment (as ViewModelStoreOwner) -> FragmentManager -> FragmentManagerViewModel -> FragmentActivity (as ViewModelStore) -> …


В следующем посте разберем, как ViewModel переживает пересоздание активити.