Room, LiveData, and RecyclerView
Feb 18, 2018 · 4 min read
It has been almost one year since Google announced Android Architecture Components at Google I/O 2017, which is a set of libraries that help Android developers design and develop robust apps by providing new lifecycle-aware components like: LiveData, ViewModel, LifecycleObserver and LifecycleOwner to manage activity and fragment lifecycles. Architecture Components provide also Room, which is a robust, advanced SQLite ORM that provides an abstraction layer over SQLite to simplify database access and CRUD operations without boilerplate code.
In addition to these libraries, the Architecture Components comes also with a guide of best practices for apps development, and a recommended architecture on top of libraries mentioned above for your future apps.
It’s recommended to use these components side by side in your apps, for example: you can inject a ViewModel into a LifecycleOwner (Activity or Fragment) that holds a reference to a LiveData object stored in a Room database. In this tutorial, we will create a RecyclerView adapter that observes a Room database changes (add, update, delete) and updates its data automatically.
NOTE: I discovered while writing this article that the Architecture Components has already an extension to do the same thing as we will be doing in this tutorial, the extension can be found here. The official documentation mention it only in the Pagination Library docs.
Imagine you have a collection of posts(news, articles, social posts …) and you want to save them into a local database so the user can access them offline later. You can imagine the amount of boilerplate code that you will need to implement this database !, what if you forgot a comma in one of your SQL queries !, what if you want to migrate your database schema ! Fortunately, Room is here, it will avoid you a lot of boilerplate code, detects syntax errors in your SQL queries at compile time, and more …. Room uses the DAO (Data Access Objects) pattern to write database entities and CRUD operations, entity classes must be annotated by @Entity annotation, DAO interfaces by @DAO annotation, and the database class must be annotated by @Database annotation and must extends RoomDatabase class. Enough theory ! let’s code.
Post
Represents a Post entity stored in a database that has one primary key (id), title and content.
PostDao
An interface that provides CRUD operations on Post entity
PostDatabase
The database class, provides the PostDao and a singleton instance of itself.
RecyclerView Adapter
Our RecyclerView adapter will look like any other adapter, we need only an additional method called setData(List
newData) to update the adapter with the new updated data from database. This method replaces the old data with the new one and notify listeners about that change.
ViewModel
Now that we have implemented our Room database and RecyclerView adapter, we need to implement a viewholder to manage data access and retrieval from UI components (Activity) in a safe way. ViewModel objects are lifecycle-aware by nature, their data objects will be not lost in configuration changes. To implement a ViewModel object, your class must extends ViewModel or AndroidViewModel class. The difference between these two classes is that the second one holds a reference to the current Application, so you can use this reference to do some application-context stuff, like we are doing in this example. We are using the application context to get an instance of our database.
Keep in mind that this is only for illustration purposes, I recommend you to use a Dependency Injection library (like Dagger) to inject object instances.
Activity
We need now to inject PostViewModel into our Activity, this is can be done by using the ViewModelProviders class as below:
In order to listen to database changes, we need to register an observer on the LiveData object by using the method observe() as below:
Whenever a change occurs on the database (insertion, deletion, update), this method is called with the new data as parameter (represented by posts object). Then, we pass this new data to the adapter which will notify the RecyclerView with the new update using notifyDataSetChanged() method.
Final Result
Nice !! it’s working.
One last thing …
Imagine now that we have a table with more than 1k rows, if we use the same update strategy as above (using notifyDataSetChanged()), some updates will be time and resources-consuming.
Think of a one (1) delete update which will modify only one element in the RecyclerView. However, the adapter will behave as all the data was changed.
We can resolve this issue by using an utility class called DiffUtil. This class calculates the difference between two lists and output the list of modifications to be done(insert, update, delete) to ensure the transition from the old list to the new one. You can find the implementation of this strategy within the project.
Источник
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
Источник