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.
Источник
Room — Kotlin, Android Architecture Components
When we talk about storing information in a persistent way on Android, the most important and “easy” option is to use plain SQLite, if you do a good implementation, this implies:
- Creating a contract class which contains the constants for the URIs, tables and columns name (which should extends from BaseColumns ).
- Creating a class that extends SQLiteOpenHelper which will create the database the first time it is called. To do this, you need to manually create a query string indicating the name of the tables and columns you want to create, using the contract defined on the above step. This is difficult to maintain and can introduce a bunch of errors.
- Once you have created your DB, if you want to insert, update or delete information you have to instantiate your subclass of SQLiteOpenHelper , get a writable version of the DB, also you need to create a ContentValues using the contract class created on the first step, to set the keys and the values with the information you need to save. In the case of update and delete operations, you can also define a projection to select specific information base on the value of a column.
- In the case you want to read information, you have to instantiate your subclass of SQLiteOpenHelper , get a readable version of the DB and create a projection. As a result, you will get a Cursor which then you have to go through, column by column, to get the information you need.
It sounds like too much work, doesn’t it? If you want to do this easier, you can use an ORM but you still need to manually defined and create the DB, subclassing SQLiteOpenHelper and creating the contract classes. So, the question is: Is there any way to simplify all this process?, the answer is: Yes, Room is your solution.
What is Room?
The Room persistence library provides an abstraction layer over SQLite to allow for more robust database access while harnessing the full power of SQLite.
Room is part of the Android Architecture Components presented in the Google I/O 2016. It is not an ORM, it is a whole library that allows us to create and manipulate SQLite databases easier, by using annotations we can define our databases, its tables and operations; Room will automatically translate these annotations into SQLite instructions/queries to perform the correspondent operations into the DB engine.
Room architecture looks like follow:
The three major components of Room are:
- Database: It represents the DB, it is an object that holds a connection to the SQLite DB and all the operations are executed through it. It is annotated with @Database .
- Entity: Represents a table within the Room Database. It should be annotated with @Entity .
- DAO: An interface that contains the methods to access the Database. It is annotated with @Dao .
Too much talk, let see an example:
Let’s imagine that we need to create an app to store your gym routine, we are going to have 4 entities in our database as we’ll show below. All the sample codes are written using kotlin, if you don’t know Kotlin or you want to learn more, I would like to invite you to visit this link and read my article about it.
The first thing we need to do is to update our gradle file. It should look like the following
As we mentioned, Room is composed of three major components: Entities, DAO’s and DataBase. Let’s see and analyze each of them:
Entities
For our example we are going to use four entities: Gender, Exercise, Routine and Trainee.
It represents the gender of the trainee.
- All the classes that represent an entity of the database have to be annotated with @Entity .
- With the annotation @PrimaryKey(autoGenerate = true) we are indicating that id is the primary key of the entity and should be autoGenerate by the database engine.
It represents an exercise that it is part of a routine.
Things to notice:
- By default, Room uses the field names as the column names in the database. If you want a column to have a different name, add the @ColumnInfo annotation to a field.
It is basically a container of exercises that together create an exercise routine.
Things to notice:
- When a class is annotated with @Entity the name of the tablet will be the name of the class, if we want to use a different one we have to add the tableName property along with the @Entity annotation.
- @TypeConverters annotation has to be used when we declare a property which type is a custom class, a list, date type or any other type that Room and SQL don’t know how to serialize. In this case, we are using the annotation at the class field level whereby only that field will be able to use it. Depending on where the annotation is placed it will behave differently as explained here.
It represents the owner of the routine.
There are several things to be noticed here:
- The indices property at @Entity annotation is used to index certain fields in the database to speed up the queries. In this case, we are indicating that the columns: name and age have to be indexed.
- The property called foreignKeys is used to indicate a relationship between two or more tables, it receives an array of ForeignKeys . entity property is the name of the father class, parentColumns is the name of the column at the father class and childColumns is the name of the column at the class where it is used.
- We use @Embedded annotation when we want to represent an object that we’d like to decompose into its subfields within a table. We can, then, query the embedded fields just as you would for other individual columns. This allow us to express an entity or plain old Java object (POJO) as a cohesive whole in our database logic, even if the object contains several fields.
Data Access Objects or DAOs are used to access our data when we implement Room. Each DAO have to include a set of methods to manipulate the data (insert, update, delete or get).
A DAO can be implemented as an interface or as an abstract class, in our case we are going to use an interface. Due to all the DAOs are basically identical we will show only one.
Some things to notice:
- All the DAOs have to be annotated with @Dao .
- A function annotated with @Insert , @Update or @Delete have to receive an instance of the desired class as a parameter, which represents the object that we want to insert, update or delete respectively.
- In the case of insert or update operations, we can use the property onConflict to indicate what to do when a conflict performing the operation happens. The strategies available to use are: REPLACE , ABORT , FAIL , IGNORE and ROLLBACK .
- If we want to get specific information from one or more entities we can annotate a function with @Query and provide a SQL script as parameter. Another advantage of use Room is: if the SQL query provided in the annotation is invalid, it will fail on compilation time and not in runtime.
Type Converters
As we said before, Type Converters are used when we declare a property which Room and SQL don’t know how to serialize. Let’s see an example of how to serialize date data type.
DataBase
It represents the DB, it holds a connection to the actual SQLite DB.
Things to notice here:
- This is an abstract class that has to extend from RoomDatabase .
- It has to be annotated with @Database , it receives a list of entities with all the classes that compose the database (all these classes have to be annotated with @Entity ). We also have to provide a database version.
- We have to declare an abstract function for each of the entities included in the @Database annotation, this function has to return the correspondent DAO (A class annotated with @Dao ).
- Finally, we declare a companion object to get static access to the method getAppDataBase which gives us a singleton instance of the database.
Using the Room database
Now let’s see a very simple example of how to use the Room database we just created
What are we doing?
- We’re getting the instance of the database and GenderDao .
- We’re creating two Gender instances: Male and Female.
- We’re inserting the two instances created into de DB.
- We’re querying the DB to get all the genders store on it.
- We’re merging the name of all the genders we got from the DB and we’re setting the text of the TextView with that value.
And that’s it 🙂 This is almost everything you need to know to create and use a Database on Android with Room. The whole code of the project can be gotten from here. Thanks for reading!
Источник