Как 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 -> ViewModelStore -> ViewModel
FragmentManagerViewModel переживает пересоздание фрагмента, а вместе с ним сохраняется и ViewModel .
Следующий вопрос: как FragmentManagerViewModel переживает пересоздание фрагмента?
Трюк заключается в том, что класс FragmentManagerViewModel – это ViewModel, и он сохраняется также как и любой другой объект ViewModel .
Объектом ViewModelStoreOwner для FragmentManagerViewModel выступает FragmentActivity :
Fragment (as ViewModelStoreOwner) -> FragmentManager -> FragmentManagerViewModel -> FragmentActivity (as ViewModelStore) -> …
В следующем посте разберем, как ViewModel переживает пересоздание активити.
Как viewmodel переживает пересоздание активити

7 min read
May 4, 2022
Android. Шпаргалки. Как ViewModels выдерживает изменения конфигурации
Класс ViewModel предназначен для хранения и управления данными, связанными с пользовательским интерфейсом, с учетом жизненного цикла. Класс ViewModel позволяет переживать данные при изменении конфигурации, такой, как поворот экрана.
Обычно, первое, что мы узнаем при изучении разработки Android, это то, что activity воссоздаются после изменений конфигурации. Когда это происходит, мы теряем все инициализированные переменные и ui перерисовывается. Мы получаем совершенно новый экземпляр activity.
Поэтому, когда я узнал о классе ViewModel и о том, что он может делать, в моей голове сразу же возникли некоторые вопросы:
- Как это возможно, как ViewModels выживает после изменения конфигурации, учитывая, что activity, которые ее содержат, уничтожаются и создаются снова?
- Как вновь созданный экземпляр activity получает доступ к той же самой ViewModel, которая использовалась предыдущим экземпляром activity?
Давайте начнем с самого начала. Рекомендуемый подход для создания экземпляра класса ViewModel внутри activity заключается в использовании следующего кода:
private val viewModel: MyViewModel by viewModels()
Функция viewModels() возвращает экземпляр Lazy, который служит ленивым делегатом свойства. В основном это означает, что экземпляр MyViewModel будет получен при первом доступе к переменной viewModel (а не при объявлении этой переменной).
Вот как выглядит функция viewModels():
public inline fun reified VM : ViewModel> ComponentActivity.viewModels(
noinline factoryProducer: (() -> Factory)? = null
): Lazy val factoryPromise = factoryProducer ?: defaultViewModelProviderFactory
>
return ViewModelLazy(VM::class, viewModelStore >, factoryPromise)
>
Он принимает единственный параметр — factoryProducer. Если он указан, ViewModelProvider.Factory, возвращаемый этой лямбдой, будет использоваться для создания экземпляра ViewModel. Если нет, будет использоваться значение по умолчанию.
Функция возвращает экземпляр класса ViewModelLazy, который является реализацией интерфейса Lazy, о котором я упоминал ранее.
Конструктор ViewModelLazy принимает три параметра. Первый представляет собой класс ViewModel, экземпляр которого мы хотим создать. Третий — это лямбда, которая возвращает ViewModelProvider.Factory. Это то же самое, что и переданное в функцию viewModels() (или по умолчанию). Второй параметр интересен. Это лямбда, которая возвращает ViewModelStore. Здесь передается лямбда, которая возвращает переменную viewModelStore. Откуда эта переменная?
Как видите, функция viewModels() является функцией расширения класса ComponentActivity. Поэтому, когда мы вызываем viewModelStore в Kotlin, мы фактически вызываем метод getViewModelStore() из ComponentActivity (написанный на Java), который возвращает свою переменную-член с именем mViewModelStore:
public ViewModelStore getViewModelStore() if (getApplication() == null) throw new IllegalStateException("Your activity is not yet attached to the "
+ "Application instance. You can't request ViewModel before onCreate call.");
>
ensureViewModelStore();
return mViewModelStore;
>
Причина, по которой ComponentActivity имеет этот метод, заключается в том, что он реализует интерфейс ViewModelStoreOwner.
/**
* A scope that owns .
*
* A responsibility of an implementation of this interface is to retain owned ViewModelStore
* during the configuration changes and call , when this scope is
* going to be destroyed.
*
*
@see ViewTreeViewModelStoreOwner
*/
@SuppressWarnings("WeakerAccess")
public interface ViewModelStoreOwner /**
* Returns owned
*
* @return a
*/
@NonNull
ViewModelStore getViewModelStore();
>
Теперь вы можете спросить: «Что такое ViewModelStore?» Как следует из названия, класс ViewModelStore отвечает за хранение экземпляров ViewModels. Вот как выглядит этот класс:
public class ViewModelStore
private final HashMap mMap = new HashMap<>();
final void put(String key, ViewModel viewModel) ViewModel oldViewModel = mMap.put(key, viewModel);
if (oldViewModel != null) oldViewModel.onCleared();
>
>
final ViewModel get(String key) return mMap.get(key);
>
Set keys() return new HashSet<>(mMap.keySet());
>
public final void clear() for (ViewModel vm : mMap.values()) vm.clear();
>
mMap.clear();
>
>
Этот относительно простой класс служит оболочкой для HashMap. Это окончательное место, где хранятся все ViewModels, связанные с активити или фрагментом.
Теперь, когда мы знаем, что такое ViewModelStore, становится ясно, для чего используется интерфейс ViewModelStoreOwner. Класс, который его реализует, указывает внешнему миру, что он владеет экземпляром ViewModelStore.
Это список всех классов платформы Android, которые реализуют метод getViewModelStore() из интерфейса ViewModelStoreOwner. По сути, это только действия и фрагменты:
- ComponentActivity
- Fragment
- FragmentViewLifeCycleOwner
- HostCallbacks
Давайте посмотрим на документацию из интерфейса ViewModelStoreOwner, в частности на этот фрагмент:
Ответственность за реализацию этого интерфейса состоит в том, чтобы сохранить принадлежащее ViewModelStore во время изменений конфигурации и вызвать ViewModelStore#clear(), когда эта область будет уничтожена.
Это дает нам ценную подсказку. Это означает, что activity (или фрагмент) несет ответственность за сохранение своего ViewModelStore (вместе со всеми ViewModels) при изменении конфигурации.
Как activity справляются с этим? Мы вернемся к этому вопросу в следующем разделе. А пока давайте снова вернемся к функции viewModels() и посмотрим, как выглядит только что созданный класс ViewModelLazy:
public class ViewModelLazy (
private val viewModelClass: KClass,
private val storeProducer: () -> ViewModelStore,
private val factoryProducer: () -> ViewModelProvider.Factory
) : Lazy private var cached: VM? = null
override val value: VM
get() val viewModel = cached
return if (viewModel == null) val factory = factoryProducer()
val store = storeProducer()
ViewModelProvider(store, factory).get(viewModelClass.java).also cached = it
>
> else viewModel
>
>
override fun isInitialized(): Boolean = cached != null
>
Большая часть кода в этом классе имеет дело только с кэшированием объекта и обеспечением возврата того же экземпляра при последующих вызовах. Самый актуальный фрагмент вот этот:
ViewModelProvider(store, factory).get(viewModelClass.java)
Он создает экземпляр класса ViewModelProvider, передавая необходимые параметры (которые были созданы путем выполнения лямбда-выражений, переданных конструктору) и вызывает get() для получения нашей ViewModel. Вот метод get():
public open operator fun get(modelClass: Class): T val canonicalName = modelClass.canonicalName
?: throw IllegalArgumentException("Local and anonymous classes can not be ViewModels")
return get("$DEFAULT_KEY:$canonicalName", modelClass)
>
Эти методы вызывают другой метод get(), который дополнительно принимает ключ в качестве параметра. В данном случае ключ представляет собой составную строку, состоящую из двух частей, разделенных двоеточием:
- Значение DEFAULT_KEY (которое равно «androidx.lifecycle.ViewModelProvider.DefaultKey»).
- Каноническое имя нашего класса ViewModel.
Этот ключ будет использоваться для идентификации нашего объекта ViewModel в HashMap, который мы видели ранее в классе ViewModelStore. Вот второй метод get():
public open operator fun get(key: String, modelClass: Class): T var viewModel = store[key]
if (modelClass.isInstance(viewModel)) (factory as? OnRequeryFactory)?.onRequery(viewModel)
return viewModel as T
> else @Suppress("ControlFlowWithEmptyBody")
if (viewModel != null) // TODO: log a warning.
>
>
viewModel = if (factory is KeyedFactory) factory.create(key, modelClass)
> else factory.create(modelClass)
>
store.put(key, viewModel)
return viewModel
>
Не вдаваясь в подробности, этот метод в основном возвращает существующую ViewModel, заданную ключом (если он есть), или создает новую нужного типа, используя предоставленную фабрику.
Здесь мы можем увидеть ViewModelStore в действии. Он используется для получения существующего экземпляра ViewModel (var viewModel = store[key]) или для хранения вновь созданного экземпляра (store.put(key, viewModel)).
На данный момент мы, наконец, получили экземпляр ViewModel, который хотели. Это был либо тот, к которому у нас уже был доступ, либо совершенно новый экземпляр.
Хорошо, мы рассмотрели много вопросов. Возможно, стоит сделать паузу на мгновение, чтобы подвести итог тому, что мы нашли до сих пор:
Каждая activity и фрагмент (из пакетов androidx) имеет компонент под названием ViewModelStore. Откуда мы это знаем? Они декларируют этот факт, реализуя интерфейс ViewModelStoreOwner. ViewModelStore содержит ссылки на все ViewModels, используемые этой activity или фрагментом. Этот компонент сохраняется при изменении конфигурации. Позже мы увидим, как это делается.
Когда мы вызываем private val viewModel: MyViewModel с помощью viewModels() в нашей activity (или фрагменте), мы создаем ленивый делегат свойства, который инициализирует нашу ViewModel при первом доступе к переменной viewModel. Внутри код создаст правильный экземпляр и сохранит его в ViewModelStore activity (или фрагмента) или вместо этого вернет предыдущий экземпляр (если он был).
Мы узнали, что это ViewModelStore, в котором хранятся наши ViewModels. Оригинальный вопрос:
Как ViewModels выдерживают изменения конфигурации?
поэтому можно перефразировать так:
Как ViewModelStores выдерживают изменения конфигурации?
Сосредоточимся на activity. Возвращаясь к методу getViewModelStore() из ComponentActivity, мы можем заметить, что он вызывает другой метод с именем sureViewModelStore() перед возвратом своего экземпляра.
Чтобы напомнить, вот метод getViewModelStore():
public ViewModelStore getViewModelStore() if (getApplication() == null) throw new IllegalStateException("Your activity is not yet attached to the "
+ "Application instance. You can't request ViewModel before onCreate call.");
>
ensureViewModelStore();
return mViewModelStore;
>
и это ensureViewModelStore():
void ensureViewModelStore() if (mViewModelStore == null) NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) // Restore the ViewModelStore from NonConfigurationInstances
mViewModelStore = nc.viewModelStore;
>
if (mViewModelStore == null) mViewModelStore = new ViewModelStore();
>
>
>
Вот оно! Кажется, мы нашли то, что искали.
Этот метод сначала проверяет, является ли переменная-член mViewModelStore null. Если да, то восстанавливаем предыдущее ViewModelStore (если оно есть, иначе создает новое) с помощью метода getLastNonConfigurationInstance(). Этот метод возвращает экземпляр класса NonConfigurationInstances, который определен следующим образом:
static final class NonConfigurationInstances Object custom;
ViewModelStore viewModelStore;
>
Как мы видим, у него есть наш объект ViewModelStore.
Теперь мы знаем, как восстанавливаются наши ViewModelStore. Осталась последняя часть головоломки, которую нужно решить. Нам нужно выяснить, как они спасаются. Начнем с изучения документации метода getLastNonConfigurationInstance():
Получить данные экземпляра, не относящиеся к конфигурации, которые ранее были возвращены функцией onRetainNonConfigurationInstance().
Я думаю, что мы довольно близки. Давайте углубимся в метод onRetainNonConfigurationInstance(). Во-первых, вот что об этом говорится в документации:
Вызывается системой в рамках уничтожения activity из-за изменения конфигурации, когда известно, что для новой конфигурации будет немедленно создан новый экземпляр. Здесь вы можете вернуть любой объект, который вам нравится, включая сам экземпляр activity, который впоследствии можно получить, вызвав getLastNonConfigurationInstance() в новом экземпляре activity.
А вот как выглядит метод в ComponentActivity:
public final Object onRetainNonConfigurationInstance() // Skipping the irrelevant parts.
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.custom = custom;
nci.viewModelStore = viewModelStore;
return nci;
>
Как видите, этот метод подготавливает экземпляр класса NonConfigurationInstances, который будет сохраняться при изменении конфигурации. У него есть наше текущее ViewModelStore, что означает, что мы сможем успешно восстановить его впоследствии в getLastNonConfigurationInstance().
И это все! Все, что на первый взгляд кажется таким волшебным в ViewModels, — это просто комбинация использования этой пары методов из класса Activity:
- onRetainNonConfigurationInstance()
- getLastNonConfigurationInstance()
Процесс сохранения и восстановления ViewModelStore фрагментами очень похож. Если вам интересно, я рекомендую вам изучить исходный код, чтобы найти его самостоятельно.
Вот одна вещь, о которой стоит помнить. Как упоминалось во введении, класс ViewModel позволяет данным пережить изменения конфигурации, такие как поворот экрана, включение многооконного режима и т. д.
Однако система может уничтожить процесс вашего приложения, пока пользователь не взаимодействует с другими приложениями. В таком случае экземпляр активности уничтожается вместе со всем хранящимся в нем состоянием. Это называется смертью процесса. ViewModels не переживают инициированную системой смерть процесса.
Вот почему вы должны использовать объекты ViewModel в сочетании с onSaveInstanceState() или какой-либо другой сохраняемостью диска.
Чтобы эффективно использовать ViewModels в наших приложениях для Android, необязательно полностью разбираться в деталях их реализации. Однако многим разработчикам просто любопытно, как некоторые вещи работают под капотом. Знание внутренностей может облегчить выявление потенциальных пограничных случаев или ловушек и упростить отладку в будущем.
Как ViewModel переживает пересоздание активити?
В прошлом посте мы разбирали, как ViewModel переживает пересоздание фрагмента. Закончили на том, что класс FragmentManagerViewModel сохраняет объекты ViewModel для фрагмента.
Сам FragmentManagerViewModel – это ViewModel , для которого объектом ViewModelStoreOwner выступает FragmentActivity .
FragmentActivity наследуется от класса ComponentActivity , который реализует метод getViewModelStore(): ViewModelStore интерфейса ViewModelStoreOwner .
ComponentActivity использует переопределенный метод Activity.onRetainNonConfigurationInstance() для сохранения объекта ViewModelStore . Этот метод вызывается между onStop() и onDestroy() и возвращает произвольный объект, который сохраняется системой во время пересоздания активити.
При вызове getViewModelStore() , ComponentActivity получает сохраненный ViewModelStore с помощью метода getLastNonConfigurationInstance().
Урок 7. ViewModel и LiveData. Сохранение и передача состояния активити при повороте устройства
Продолжаем курс по обучению основам разработки мобильных приложений в Android Studio на языке Kotlin.
Это урок 7, в котором разберемся, зачем сохранять состояние активити при изменениях конфигурации и какие инструменты для этого лучше использовать: savedInstanceState или ViewModel и LiveData.
Предыдущий урок, на котором мы разбирали жизненный цикл активити, здесь.
При пересоздании активити ViewModel остается живым и используется во вновь созданном активити.
Например, если вам нужно отобразить список пользователей в вашем приложении, нужно реализовать получение и сохранение списка пользователей не в коде активити или фрагмента, а во ViewModel.
Приложение с ViewModel
Рассмотрим простой пример приложения, которое использует ViewModel.
За основу был взят этот пример на Github.
В файле сборки build.gradle модуля app добавьте такие зависимости для работы со списком и ViewModel:
dependencies
Поскольку приложение будет работать со списком пользователей, нам понадобится модель, сущность:
data class User ( var name: String = "", var description: String = "" )
Здесь два поля – имя и описание.
Далее создадим файл object, выполняющий роль поставщика данных:
object UserData
В реальном приложении данные поставляются из сети или БД, здесь же для простоты просто создаются два статичных списка пользователей. Первый список, который поменьше, будем отображать при старте приложения. Второй список будем отображать по нажатию кнопки в меню главного экрана.
Для отображения списка нам нужно создать файл макета элемента списка user_item.xml в папке ресурсов res/layout:
Изменим файл макета activity_main.xml для размещения списка на главном экране, добавив виджет списка RecyclerView:
Также нужно создать меню, для этого в папке res создадим папку menu и в ней файл main_menu.xml:
Это меню с одним пунктом Refresh, по нажатию которого будем обновлять список.
Теперь адаптер, который будет создавать список и наполнять его данными:
import android.support.v7.widget.RecyclerView import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import kotlinx.android.synthetic.main.user_item.view.* import java.util.ArrayList class UserAdapter : RecyclerView.Adapter() < private var users: List= ArrayList() //создает ViewHolder и инициализирует views для списка override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserHolder < return UserHolder( LayoutInflater.from(parent.context) .inflate(R.layout.user_item, parent, false) ) >//связывает views с содержимым override fun onBindViewHolder(viewHolder: UserHolder, position: Int) < viewHolder.bind(users[position]) >override fun getItemCount() = users.size //передаем данные и оповещаем адаптер о необходимости обновления списка fun refreshUsers(users: List) < this.users = users notifyDataSetChanged() >//внутренний класс ViewHolder описывает элементы представления списка и привязку их к RecyclerView class UserHolder(itemView: View) : RecyclerView.ViewHolder(itemView) < fun bind(user: User) = with(itemView) < userName.text = user.name userDescription.text = user.description >> >
Унаследуем наш адаптер от RecyclerView.Adapter и указываем наш собственный ViewHolder, который предоставит доступ к View-компонентам. Далее инициализируем список. Функция onCreateViewHolder создает ViewHolder и инициализирует View-компоненты для списка. Функция onBindViewHolder связывает View-компоненты с содержимым. В функции refreshUsers передаем данные и оповещаем адаптер о необходимости обновления списка вызовом notifyDataSetChanged(). Внутренний класс ViewHolder описывает View-компоненты списка и привязку их к RecyclerView.
Теперь мы подходим к самому главному – получению данных для списка.
Этим будет заниматься класс UserViewModel, унаследованный от ViewModel:
import android.arch.lifecycle.MutableLiveData import android.arch.lifecycle.ViewModel class UserViewModel : ViewModel() < var userList : MutableLiveData> = MutableLiveData() //инициализируем список и заполняем его данными пользователей init < userList.value = UserData.getUsers() >fun getListUsers() = userList //для обновления списка передаем второй список пользователей fun updateListUsers() < userList.value = UserData.getAnotherUsers() >>
Для списка пользователей используется объект класса MutableLiveData – это подкласс LiveData, который является частью Архитектурных компонентов, и следует паттерну Observer (наблюдатель). Если вы знакомы с RxJava, класс LiveData похож на Observable. Но если с Observable вы должны удалять связи вручную, то класс LiveData зависит от жизненного цикла и выполняет всю очистку самостоятельно. Подписчиками LiveData являются активити и фрагменты. LiveData принимает подписчика и уведомляет его об изменениях данных, только когда он находится в состоянии STARTED или RESUMED. Состояние подписчиков определяется их объектом LifeCycle. Более подробно LifeCycle и состояния жизненного цикла мы рассматривали на прошлом уроке.
Класс MutableLiveData предоставляет методы setValue и postValue (второй – поточно-безопасный), посредством которых можно получить и отправить данные любым активным подписчикам.
В классе UserViewModel мы инициализируем список и заполняем его данными пользователей. Функция getListUsers() возвращает список, а функция updateListUsers() обновляет список, сохраняя в него второй список пользователей из класса UserData.
Теперь код MainActivity:
import android.arch.lifecycle.Observer import android.arch.lifecycle.ViewModelProviders import android.support.v7.app.AppCompatActivity import android.os.Bundle import android.support.v7.widget.LinearLayoutManager import android.view.Menu import android.view.MenuItem import kotlinx.android.synthetic.main.activity_main.* class MainActivity : AppCompatActivity() < //инициализируем ViewModel ленивым способом private val userViewModel by lazy override fun onCreate(savedInstanceState: Bundle?) < super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) //инициализируем адаптер и присваиваем его списку val adapter = UserAdapter() userList.layoutManager = LinearLayoutManager(this) userList.adapter = adapter //подписываем адаптер на изменения списка userViewModel.getListUsers().observe(this, Observer < it?.let < adapter.refreshUsers(it) >>) > //создаем меню override fun onCreateOptionsMenu(menu: Menu?): Boolean < menuInflater.inflate(R.menu.main_menu, menu) return super.onCreateOptionsMenu(menu) >//при нажатии пункта меню Refresh обновляем список override fun onOptionsItemSelected(item: MenuItem?): Boolean < when(item?.itemId) < R.id.refresh -> < userViewModel.updateListUsers() >> return super.onOptionsItemSelected(item) > >
Инициализируем объект класса UserViewModel так называемым ленивым способом с помощью функции lazy(). Это функция, которая принимает лямбду и возвращает экземпляр класса Lazy, который служит делегатом для реализации ленивого свойства: первый вызов get() запускает лямбда-выражение, переданное lazy() в качестве аргумента, и запоминает полученное значение, а последующие вызовы просто возвращают вычисленное значение. Таким образом, объект UserViewModel инициализируется только при первом вызове, а далее используется уже инициализированный объект.
В теле onCreate() инициализируем адаптер и присваиваем его списку. Далее подписываем адаптер на изменения списка с помощью функции observe(@NonNull LifecycleOwner owner, @NonNull Observer observer), которой на вход передается объект LifecycleOwner (текущее активити) и интерфейс Observer – колбек, уведомляющий об успешном получении данных. При этом вызывается метод обновления списка адаптера и ему передается обновленный список.
Ниже создаем меню и обрабатываем нажатие пункта меню Refresh, по которому обновляем список.
Запуск приложения
Теперь запустим приложение на эмуляторе и проверим его работу.
После запуска открывается экран со списком пользователей. Обновим список из меню. Теперь отображается другой, расширенный список. Но если мы перезапустим приложение, то снова увидим первоначальный список, который открывается по умолчанию.
Снова обновим список. Теперь покрутим устройство. Как мы знаем, при повороте активити уничтожается, однако на экране все еще отображается второй список. Это значит, что, несмотря на уничтожение активити, список сохраняется в объекте ViewModel и новое активити использует его данные. С другой стороны, если данные в списке будут обновлены, то посредством LiveData список также будет обновлен.
Дополнительно о LiveData можно почитать здесь.
Исходный код приложения можно посмотреть здесь.
Kotlin Android Extensions
Внимание! Kotlin Android Extensions теперь deprecated, это значит, что его поддержка не гарантируется. Альтернативные методы описаны здесь: ссылка
Если вы заметили, мы обращаемся к экранным компонентам без вызова метода findViewById, прямо по идентификатору. Это происходит благодаря использованию плагина Kotlin Android Extensions — это плагин для Kotlin, который включён в стандартный пакет. Он позволяет восстанавливать view из Activities, Fragments, и Views таким вот простым способом.
Плагин генерирует дополнительный код, который позволяет получить доступ к view в виде XML, так же, как если бы вы имели дело с properties с именем id, который вы использовали при определении структуры.
Также он создаёт локальный кэш view. При первом использовании свойства, плагин выполнит стандартный findViewById. В последующем, view будет восстановлен из кэша, поэтому доступ к нему будет быстрее.
По умолчанию плагин уже интегрирован в модуль благодаря вот такой строчке в файле сборки модуля:
apply plugin: 'kotlin-android-extensions'
При первом обращении к любому экранному компоненту в MainActivity автоматически добавляется такой импорт:
import kotlinx.android.synthetic.main.activity_main.*
Больше о Kotlin Android Extensions рекомендую почитать в переводе статьи Antonio Leiva на Медиуме.
На этом наш урок подошел к концу. Вопросы задавайте в комментариях. Всем добра!