preface

Android Architecture Components (AAC) was first released at GoogleI/O 2017, and after nearly a year of maintenance, the Google team has now released a stable version (V1.1.1). Can better help us to build our own App, if you have not understood ACC now the time is just right, too late to explain, hurry to get on the bus.

ACC is an architecture component, which can help us better manage our App and facilitate our development. It helps our apps better store data, manage life cycles, modularize, avoid common errors, and reduce boilerplate writing.

ACC is mainly composed of four single components, namely, Room, LiveData, Lifecycle and ViewModel. Each of them is a separate component, and we can use a few of them individually, or we can integrate them all together. Therefore, ACC provides better use flexibility and is convenient for us to integrate into our App.

Today, we mainly analyze Room components in ACC. Room is a robust SQL object mapping library designed to help us quickly implement local storage of data. As for the reason of using local database, it is natural that when users have no network or poor network, it can better improve users’ experience of our App.

Add the dependent

Before using Room, we still need to add dependencies to it in our project. Start by adding the Google () library to build.gradle at the root of your project as follows:

allprojects {
    repositories {
        jcenter()
        google()
    }
}
Copy the code

Open the build.gradle file in your App or Module and add the following code to dependencies:

dependencies {
    def room_version = "1.1.0" // or, for latest rc, use "1.1.1 - rc1"
 
    implementation "android.arch.persistence.room:runtime:$room_version"
    annotationProcessor "android.arch.persistence.room:compiler:$room_version"
 
    // optional - RxJava support for Room
    implementation "android.arch.persistence.room:rxjava2:$room_version"
 
    // optional - Guava support for Room, including Optional and ListenableFuture
    implementation "android.arch.persistence.room:guava:$room_version"
 
    // Test helpers
    testImplementation "android.arch.persistence.room:testing:$room_version"
}
Copy the code

Room

With the above dependencies added, we can now officially use Room. SQLite is used to store local data in Android App. When we use native SQLite to write local database, we not only need to define the database structure, but also create SQLiteHelper and write a series of SQL statements. This is an increase in code volume and complexity that we don’t want. Room is a great way to reduce code and simplify.

The use of Room mainly consists of three parts:

  1. Entity: identifies the table structure in the database
  2. DAO: Identifies methods that provide access to data in database tables
  3. Database: Identifies the Database to be created

The above three parts are achieved through annotations in the code, so as to achieve the simplification of the code.

Entity

Entity acts on the Model, the Model class that we match with the fields in the data table. Now let’s create a model related to the contact. For the normal model, it is established as follows:

data class ContactsModel(val id: Int, val name: String, val phone: String)
Copy the code

Now we want to map the ContactsModel to a table in the database by doing the following:

@Entity(tableName = "contacts")
data class ContactsModel(
        @PrimaryKey
        @ColumnInfo(name = "contacts_id")
        val id: Int,
        @ColumnInfo(name = "name")
        val name: String,
        @ColumnInfo(name = "phone")
        val phone: String
)
Copy the code

First we add the @Entity annotation to the ContactsModel to indicate that it will map to a table. You can name the table in Entity by using tableName, which is not named contacts.

In addition, @columninfo is used to identify the fields in the table and @primarykey is used to identify the PrimaryKey of the table. ColumnInfo can also be named by (name = “name”). Of course, there are other comments such as @foreignKey for foreign keys

DAO

With the database table set up, it is now time to provide methods for manipulating the data in the data table.

@Dao
interface ContactsDao {
 
    @Query("SELECT * FROM contacts")
    fun getAllContacts(): List<ContactsModel>
 
    @Query("SELECT * FROM contacts WHERE contacts_id = :id")
    fun getContactsById(id: Int): LiveData<ContactsModel>
 
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insertContacts(contactsModel: ContactsModel)
 
    @Query("UPDATE contacts SET name = :name AND phone = :phone WHERE contacts_id = :id")
    fun updateContacts(id: Int, name: String, phone: String)
 
    @Query("DELETE FROM contacts WHERE contacts_id = :id")
    fun deleteContacts(id: Int)
Copy the code

Here we simply create an interface that identifies it as a set of methods that provide manipulation of the data table via @DAO. Note that it has to be an interface, where we just define the interface method. Instead of the usual interface method definition, we have to comment on each interface method to indicate what the method does.

For example, in order for the getAllContacts() method to get all the data in the Contacts table, we need to add the @Query annotation to the method. Since it is a Query method, we use Query, or Insert(the third method). The second content in () is a normal query statement. Here we’re getting all the Contacts, so we’re using

@Query("SELECT * FROM contacts")
Copy the code

For SQL statements with parameters, you can view the second method. The parameter value is simply prefixed with: before the corresponding method parameter name. This is the format for passing parameters.

@Query("SELECT * FROM contacts WHERE contacts_id = :id")
fun getContactsById(id: Int): LiveData<ContactsModel>
Copy the code

Room is as simple as that, by defining the form of interfaces and interface methods, combined with annotations to simplify the amount of code and complexity. Of course, eventually Room will implement these interface methods for us based on comments, and the compiler will implement them for us. We can build the project, and then we can search for the ContactsDao_Impl class, which readers can try on their own. Essentially, ContactsDao_Impl implements the ContactsDao interface.

One of the great things about Room is that it can check at compile time that your SQL statement is written correctly, and if it is written incorrectly, the compilation will fail. This prevents the App from crashing when it runs. This reader can test it for himself.

Database

Now that we have tables, we have methods to manipulate tables, and finally we have the database to hold each table. Talk is cheap. Show me the code.

@Database(entities = arrayOf(ContactsModel::class), version = 1)
abstract class ContactsDataBase : RoomDatabase() {
 
    abstract fun contactsDao(): ContactsDao
 
    companion object {
        private var instance: ContactsDataBase? = null
        fun getInstance(context: Context): ContactsDataBase {
            if (instance == null) {
                instance = Room.databaseBuilder(context.applicationContext,
                        ContactsDataBase::class.java,
                        "contacts.db").build()
            }
            return instance as ContactsDataBase
        }
    }
}
Copy the code

Yes, again using annotations, here we define the ContactsDataBase abstract class to inherit from the RoomDatabase abstract class. Also with @database to indicate that it is a Database. It takes two arguments, entities and version. The former receives an array of type Class[], which is the Class for the table. The latter is the database version number of int.

You also need to define an abstract method in ContactsDataBase that returns the ContactsDao annotated by @DAO, which provides methods to retrieve the data table. Essentially exposes the entry point for the database to manipulate the data table. The corresponding auto-generated file, ContactsDataBase_Impl class, can also be viewed in build for its specific method implementation.

Since contactsDao is the only entry to the database, avoid creating ContactsDataBase instances every time you operate on the database. In the above code, we can use the singleton pattern to reduce the overhead of frequent instance creation.

use

After creating the Entity, DAO, and Database above, we now have a complete local Database structure. Let’s look at the simplest database call code ever:

private val mContactsDao by lazy { ContactsDataBase.getInstance(application).contactsDao() }
 
fun getContactsById(id: Int): LiveData<ContactsModel> = mContactsDao.getContactsById(id)
Copy the code

You read that right and with just two lines of code, we can get the data we’re using in the Contacts table in our database.

The first line of code gets the ContactsDao instance, which contains all the methods for manipulating the data table. The second line of code calls the action method in ContactsDao. Return the data we need.

In the second line of code, if you’re careful, you might notice that it returns LiveData. It is another powerful component of ACC, and this is another powerful part of Room, which can directly return the LiveData data type, perfectly combined with LiveData. As for what LiveData does, stay tuned for the next article Android Architecture Components Part2:LiveData.

conclusion

If your App uses Room, your App’s local data retrieval architecture will look like this

The code in the final article is available on Github. To use it, switch the branch to Feat_architecture_Components

Related articles

Android Architecture Components Part2:LiveData

Focus on

Personal blog