Android Architecture Components: Room, ViewModel and LiveData
A long time ago, when Android Developers develop apps with some of the core components like Activity, BroadcastReceiver, Services and Content Provider, with lots of hassle in making things work out. All developers are defeating Megatron all alone. But no more, our Android Optimus Prime is back with new powers called Architecture Components .
Why we need these new set of powers?
Let’s say about managing lifecycle, we have to manage the data, orientations or memory leaks all based on lifecycle and we have to do it on our own which is cumbersome sometimes. So, we need these new set of libraries which can do most of our work very easily without going into deep.
In this article, we will be focusing on a subset of these components. We are going to develop an app, which basically takes some input from the user( LiveData), save into the local database( Room) and show it on the screen( ViewModel). Let’s get started!!
Requirements
1. Add Dependencies
We need to add Room and Lifecycle components. Room is basically a Database object mapping library use to access the database. Lifecycle, this has some good set of classes like ViewModel and LiveData which we will use to manage the lifecycle of our app.
Add these libraries to build.gradle (Module: app) file, at the end of the dependencies block.
2. Setup Room
First, the terminologies; there are 3 major annotations for using room:
a. “@Entity”
Representation of table and columns becomes very easy, you just have to annotate “@Entity” to a class and name of the class becomes table name and, data members becomes the name of the columns. “ @Entity” class represent an entity in a table.
Here, we have class Habit, and the name of the table is habitClass. We had made a single column habit, and for that, we used “ @ColumnInfo” annotation and also made this a primary key by annotating “ @PrimaryKey” to it.
b. “@Dao” — Data Access Object
An Interface where we put all our SQL queries. We don’t require to write whole queries now, we just need to make a method and annotate with specific annotations like “ @Insert”, “ @Delete”, “ @Query(SELECT FROM *)”
Here, we have an interface HabitDao and some methods which we will call to perform our queries. To insert the data we annotated “ @Insert” to insert method. Room doesn’t give us annotations which can help us in deleting everything so we have “ @Query” to do some custom queries.
c. “@Database”
We need to create an abstract class ( Room class) which extends RoomDatabase. It is a database layer over the SQLite database; this helps us in all the work which we use to do in SQLiteOpenHelper class. Room helps us with the compile-time checking of SQL statements; we need only a single instance for the whole app.
Here, we have a Room class HabitRoomDatabase in which we had to declare all our entities and version of the database. getDatabase() method will return the room database instance. If you want to modify the database do have a look at Migration.
3. Welcome LiveData
LiveData class is from lifecycle library, for observing the data changes. It’s an observable data holder class, and it is also lifecycle aware which means that this is going to update the component which is in the active lifecycle state.
Here, in our project, where we are getting the list of habits, we are wrapping the list with LiveData.
4. Creating a Repository Class/Presentation Layer
This doesn’t come under the architecture component. This is a class where we will check whether to fetch data from API or local database, or you can say we are putting the logic of database fetching in this class.
Here, we added wrapper for getAllHabits() and insert(). Room run its operations on non-UI thread/background thread, so we used AsyncTask.
5. Here come’s the ViewModel
This is also the part of lifecycle library; this will help you to provide data between repository and UI. This survives the data on configuration changes and gets the existing ViewModel to reconnect with the new instance of the owner.
This is the lifecycle of ViewModel attached to an activity. Activity created and destroyed many times, but ViewModel survives the data. ViewModel is not the replacement of onSavedInstance because does not survive process shutdown although onSavedInstance can restore small data and ViewModel can restore a large amount of data like bitmaps.
Here, we are using AndroidViewModel because we need an application context. We created insert wrapper which will use the repository’s insert method.
6. Time to add RecyclerView
Create a layout file, we have a TextView in a row and also create a file which consists of RecyclerView. We need an adapter class which is responsible to show our data on the screen.
Here, we also checked if the list is empty than to display a proper message to the user.
7. Filling the Database
Here we had populated the data whenever the app starts and before that we also deleted existing data. We had a PopulateDbAsync which is an AsyncTask use to delete and insert the data.
8. Connect UI and Data
To display the data from the database, we need an observer who will observe the data changes, LiveData in the ViewModel. We have ViewModelProvider which is going to create a ViewModel for us. We need to connect our ViewModel with the ViewModelProvider, and then in the onChanged method, we always get our updated data which we can display on the screen.
Here, In MainActivity we had a fab button to open another activity where we can enter the data, and we had created onActivityResult method where we are getting the data which user had entered and inserted into the database.
9. Second Activity
We need an activity where the user can input the data. Here, we had an EditText and Button to submit the string.
That’s all! 😅 Run your app and experiment more around it.
Источник
Android Architecture Components. Часть 3. LiveData
Компонент LiveData — предназначен для хранения объекта и разрешает подписаться на его изменения. Ключевой особенностью является то, что компонент осведомлен о жизненном цикле и позволяет не беспокоится о том, на каком этапе сейчас находиться подписчик, в случае уничтожения подписчика, компонент отпишет его от себя. Для того, чтобы LiveData учитывала жизненный цикл используется компонент Lifecycle, но также есть возможность использовать без привязки к жизненному циклу.
Сам компонент состоит из классов: LiveData, MutableLiveData, MediatorLiveData, LiveDataReactiveStreams, Transformations и интерфейса: Observer.
Класс LiveData, являет собой абстрактный дженериковый класс и инкапсулирует всю логику работы компонента. Соответственно для создания нашего LiveData холдера, необходимо наследовать этот класс, в качестве типизации указать тип, который мы планируем в нем хранить, а также описать логику обновления хранимого объекта.
Для обновления значения мы должны передать его с помощью метода setValue(T), будьте внимательны поскольку этот метод нужно вызывать с main треда, в противном случае мы получим IllegalStateException, если же нам нужно передать значение из другого потока можно использовать postValue(T), этот метод в свою очередь обновит значение в main треде. Интересной особенностью postValue(T) является еще то, что он в случае множественного вызова, не будет создавать очередь вызовов на main тред, а при исполнении кода в main треде возьмет последнее полученное им значение. Также, в классе присутствует два колбека:
onActive() — будет вызван когда количество подписчиков изменит свое значение с 0 на 1.
onInactive() — будет вызван когда количество подписчиков изменит свое значение с 1 на 0.
Их назначение соответственно уведомить наш класс про то, что нужно обновлять даные или нет. По умолчанию они не имеют реализации, и для обработки этих событий мы должны переопределить эти методы.
Давайте рассмотрим, как будет выглядеть наш LiveData класс, который будет хранить wife network name и в случае изменения будет его обновлять, для упрощения он реализован как синглтон.
В целом логика фрагмента следующая, если кто-то подписывается, мы инициализируем BroadcastReceiver, который будет нас уведомлять об изменении сети, после того как отписывается последний подписчик мы перестаем отслеживать изменения сети.
Для того чтобы добавить подписчика есть два метода: observe(LifecycleOwner, Observer ) — для добавления подписчика с учетом жизненного цикла и observeForever(Observer ) — без учета. Уведомления об изменении данных приходят с помощью реализации интерфейса Observer, который имеет один метод onChanged(T).
Выглядит это приблизительно так:
Примечание: Этот фрагмент только для примера, не используйте этот код в реальном проекте. Для работы с LiveData лучше использовать ViewModel(про этот компонент в следующей статье) или позаботиться про отписку обсервера вручную.
В случае использования observe(this,this) при повороте экрана мы будем каждый раз отписываться от нашего компонента и заново подписываться. А в случае использование observeForever(this) мы получим memory leak.
Помимо вышеупомянутых методов в api LiveData также входит getValue(), hasActiveObservers(), hasObservers(), removeObserver(Observer observer), removeObservers(LifecycleOwner owner) в дополнительных комментариях не нуждаются.
Класс MutableLiveData, является расширением LiveData, с отличием в том что это не абстрактный класс и методы setValue(T) и postValue(T) выведены в api, то есть публичные.
По факту класс является хелпером для тех случаев когда мы не хотим помещать логику обновления значения в LiveData, а лишь хотим использовать его как Holder.
Класс MediatorLiveData, как понятно из названия это реализация паттерна медиатор, на всякий случай напомню: поведенческий паттерн, определяет объект, инкапсулирующий способ взаимодействия множества объектов, избавляя их от необходимости явно ссылаться друг на друга. Сам же класс расширяет MutableLiveData и добавляет к его API два метода: addSource(LiveData , Observer ) и removeSource(LiveData ). Принцип работы с классом заключается в том что мы не подписываемся на конкретный источник, а на наш MediatorLiveData, а источники добавляем с помощью addSource(..). MediatorLiveData в свою очередь сам управляет подпиской на источники.
Для примера создадим еще один класс LiveData, который будет хранить название нашей мобильной сети:
И перепишем наше приложение так чтоб оно отображало название wifi сети, а если подключения к wifi нет, тогда название мобильной сети, для этого изменим MainActivity:
Как мы можем заметить, теперь наш UI подписан на MediatorLiveData и абстрагирован от конкретного источника данных. Стоит обратить внимание на то что значения в нашем медиаторе не зависят напрямую от источников и устанавливать его нужно в ручную.
Класс LiveDataReactiveStreams, название ввело меня в заблуждение поначалу, подумал что это расширение LiveData с помощью RX, по факту же, класс являет собой адаптер с двумя static методами: fromPublisher(Publisher publisher), который возвращает объект LiveData и toPublisher(LifecycleOwner lifecycle, LiveData liveData), который возвращает объект Publisher . Для использования этого класса, его нужно импортировать отдельно:
compile «android.arch.lifecycle:reactivestreams:$version»
Класс Transformations, являет собой хелпер для смены типизации LiveData, имеет два static метода:
map(LiveData , Function ) — применяет в main треде реализацию интерфейса Function и возвращает объект LiveData
, где T — это типизация входящей LiveData, а P желаемая типизация исходящей, по факту же каждый раз когда будет происходить изменения в входящей LiveData она будет нотифицировать нашу исходящую, а та в свою очередь будет нотифицировать подписчиков после того как переконвертирует тип с помощью нашей реализации Function. Весь этот механизм работает за счет того что по факту исходящая LiveData, является MediatiorLiveData.
switchMap(LiveData , Function ) — похож к методу map с отличием в том, что вместо смены типа в функции мы возвращаем сформированный объект LiveData.
Базовый пример можно посмотреть в репозитории: git
Источник
Урок 2. LiveData
В этом уроке рассмотрим основные возможности LiveData. Как подписаться на его данные. Как помещать в него данные. Как он взаимодействует со своими подписчиками.
Полный список уроков курса:
Подключение библиотеки
В build.gradle файл проекта добавьте репозитарий google()
В build.gradle файле модуля добавьте dependencies:
Если у вас студия ниже 3.0 и старые версии Gradle и Android Plugin, то подключение будет выглядеть так:
Иногда может возникнуть конфликт с support library. Будет выдавать такую ошибку: Error:Program type already present: android.arch.lifecycle.LiveData
В таком случае попробуйте указать последние версии двух этих библиотек.
Теория
LiveData — хранилище данных, работающее по принципу паттерна Observer (наблюдатель). Это хранилище умеет делать две вещи:
1) В него можно поместить какой-либо объект
2) На него можно подписаться и получать объекты, которые в него помещают.
Т.е. с одной стороны кто-то помещает объект в хранилище, а с другой стороны кто-то подписывается и получает этот объект.
В качестве аналогии можно привести, например, каналы в Telegram. Автор пишет пост и отправляет его в канал, а все подписчики получают этот пост.
Если вы знакомы с RxJava, то LiveData напомнит вам BehaviourSubject. Методом onNext вы передаете ему данные, а он передает эти данные своим подписчикам. Плюс, все новые подписчики сразу получают последнее значение.
Казалось бы, ничего особо в таком хранилище нет, но есть один очень важный нюанс. LiveData умеет определять активен подписчик или нет, и отправлять данные будет только активным подписчикам. Предполагается, что подписчиками LiveData будут Activity и фрагменты. А их состояние активности будет определяться с помощью их Lifecycle объекта, который мы рассмотрели в прошлом уроке.
Получение данных из LiveData
Давайте рассмотрим пример.
Пусть у нас есть некий синглтон класс DataController из которого можно получить LiveData .
DataController периодически что-то там внутри себя делает и обновляет данные в LiveData. Как он это делает, мы посмотрим чуть позже. Сначала посмотрим, как Activity может подписаться на LiveData и получать данные, которые помещает в него DataController.
Код в Activity будет выглядеть так:
Получаем LiveData из DataController, и методом )» target=»_blank» rel=»noopener noreferrer»>observe подписываемся. В метод observe нам необходимо передать два параметра:
Первый — это LifecycleOwner. Напомню, что LifecycleOwner — это интерфейс с методом getLifecycle. Activity и фрагменты в Support Library, начиная с версии 26.1.0 реализуют этот интерфейс, поэтому мы передаем this.
LiveData получит из Activity его Lifecycle и по нему будет определять состояние Activity. Активным считается состояние STARTED или RESUMED. Т.е. если Activity видно на экране, то LiveData считает его активным и будет отправлять данные в его колбэк.
Если предыдущие два абзаца состоят из кучи незнакомых для вас слов, то посмотрите Урок 1. Lifecycle. Там мы подробно разобрали объект Lifecycle и его состояния.
Второй параметр — это непосредственно подписчик, т.е. колбэк, в который LiveData будет отправлять данные. В нем только один метод onChanged. В нашем примере туда будет приходить String.
Теперь, когда DataController поместит какой-либо String объект в LiveData, мы сразу получим этот объект в Activity, если Activity находится в состоянии STARTED или RESUMED.
Нюансы поведения
Распишу сразу несколько важных моментов в поведении LifeData.
Если Activity было не активно во время обновления данных в LiveData, то при возврате в активное состояние, его observer получит последнее актуальное значение данных.
В момент подписки, observer получит последнее актуальное значение из LiveData.
Если Activity будет закрыто, т.е. перейдет в статус DESTROYED, то LiveData автоматически отпишет от себя его observer.
Если Activity в состоянии DESTROYED попробует подписаться, то подписка не будет выполнена.
Если Activity уже подписывало свой observer, и попробует сделать это еще раз, то просто ничего не произойдет.
Вы всегда можете получить последнее значение LiveData с помощью его метода getValue.
Как видите, подписывать Activity на LiveData — это удобно. Поворот экрана и полное закрытие Activity — все это корректно и удобно обрабатывается автоматически без каких-либо усилий с нашей стороны.
Отправка данных в LiveData
Мы разобрались, как получать данные из LiveData, и каким образом при этом учитывается состояние Activity. Теперь давайте посмотрим с другой стороны — как передавать данные в LiveData.
В классе DataController переменная LiveData будет выглядеть так:
Наружу мы передаем LiveData, который позволит внешним объектам только получать данные. Но внутри DataController мы используем объект MutableLiveData, который позволяет помещать в него данные.
Чтобы поместить значение в MutableLiveData, используется метод setValue:
Этот метод обновит значение LiveData, и все его активные подписчики получат это обновление.
Метод setValue должен быть вызван из UI потока. Для обновления данных из других потоков используйте метод postValue. Он перенаправит вызов в UI поток. Соответственно, подписчики всегда будут получать значения в основном потоке.
Чуть более подробный пример с LiveData мы рассмотрим в Уроке 4, когда будем изучать ViewModel.
Присоединяйтесь к нам в Telegram:
— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование
— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня
— новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме
Источник