These four articles will give you a quick introduction to Jetpck (part 2) Room, WorkManager

Jetpack

Jetpack is a suite of libraries that help developers follow best practices, reduce boilerplate code and write code that runs consistently across Android versions and devices, allowing developers to focus on the code that matters.

Android Architecture Component (AAC).

Official Recommendation Structure

Note that each component depends only on the component at the level below it. For example, activities and fragments rely only on the viewmodel. The storage area is the only class that depends on multiple other classes; In this case, the storage relies on a persistent data model and a remote back-end data source.

Room

ORM: Also known as object-relational mapping.

To establish a mapping relationship between the language of facial objects and the database of facial relations, called ORM.

The benefit of the ORM framework is that it allows us to interact with the database with a face to face mindset, most of the time without having to deal with SQL statements.

The ORM framework for Android, added to Jetpack, and that’s where we’re going with Room.

Room structure

It consists of Entity, Dao, and Database.

  • Entity: An Entity class that encapsulates the actual data. Each Entity class corresponds to a table in the data, and the columns in the table are automatically generated based on the fields in the Entity class.
  • Dao: The Dao is the data access object, which is usually used to encapsulate the operations of the database. In actual programming, the logical layer does not need to deal with the underlying database, but directly interacts with the Dao layer.
  • Database: Used to define key information in the Database, including the version number of the Database, which entity classes it contains, and the access instances that provide the Dao layer.

Add the dependent

apply plugin: 'kotlin-kapt'
Copy the code
implementation 'androidx. Room: room - the runtime: 2.2.5'
annotationProcessor "Androidx. Room: a room - the compiler: 2.2.5." "
kapt "Androidx. Room: a room - the compiler: 2.2.5." "
Copy the code

Define the Entity

First, define an Entity, that is, an Entity class.

@Entity
data class User(var firstName: String, var lastName: String, var age: Int) {

    @PrimaryKey(autoGenerate = true)
    var id: Long = 0

}
Copy the code

Here we declare the User class as an Entity class with the @entity annotation on the User class name, add an id field to the User class, set it to the PrimaryKey with the @primarykey annotation, and specify the autoGenerate parameter to true so that the value of the PrimaryKey is automatically generated.

Define the Dao

@Dao
interface UserDao {

    @Insert
    fun insertUser(user: User): Long

    @Update
    fun updateUser(newUser: User)

    @Query("select * from User")
    fun loadAllUsers(a): List<User>

    @Query("select * from User where age > :age")
    fun loadUsersOlderThan(age: Int): List<User>

    @Delete
    fun deleteUser(user: User)

    @Query("delete from User where lastName = :lastName")
    fun deleteUserByLastName(lastName: String): Int

}
Copy the code

Define the Database

@Database(version = 1, entities = [User::class])
abstract class AppDatabase : RoomDatabase() {

    abstract fun userDao(a): UserDao

    companion object {

        private var instance: AppDatabase? = null

        @Synchronized
        fun getDatabase(context: Context): AppDatabase { instance? .let {return it
            }
            return Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, "app_database").build().apply {
                instance = this}}}}Copy the code

Upgrade the Room database

The database interface is not designed to stay the same forever, and the database needs to be upgraded as requirements and releases change.

A simple model

FallbackToDestructiveMigration will destroy the current database, and then to recreate, the resulting problem is data lost, is suitable for testing phase.

 Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, "app_database").fallbackToDestructiveMigration().build()
Copy the code

Upgrade mode

  1. Change the version number.
  2. Define the updated VERSION of the SQL statement.
  3. Execute the statement.

The sample

User

/ * * *@EntityDeclare the class as an entity class */
@Entity
data class User(var firstName: String, var lastName: String, var age: Int) {
    / * * *@PrimaryKeyThe annotation sets the field to the primary key * autoGenerate to true and the value of the primary key is automatically generated */
    @PrimaryKey(autoGenerate = true)
    var id: Long = 0
}
Copy the code

UserDao

/ * * *@DaoThe inside of UserDao is the encapsulation of various database operations according to business requirements. * * Room writing SQL statements supports dynamic building SQL statement syntax at compile time */
@Dao
interface UserDao {

    @Insert
    fun insertUser(user: User): Long

    @Update
    fun updateUser(user: User)

    @Query("select * from User")
    fun loadAllUsers(a): List<User>

    @Query("select * from User where age > :age")
    fun loadUsersOlderThan(age: Int): List<User>

    @Delete
    fun deleteUser(user: User)

    @Query("delete from User where lastName = :lastName")
    fun deleteUserByLastName(lastName: String): Int
}
Copy the code

AppDatabase

/ * * *@DatabaseIt declares the version number of the database and which entity classes it contains. Multiple entity classes are separated by commas. * In addition, the AppDatabase class must inherit from RoomDatabase and must be declared as an abstract class using the abstract keyword */
@Database(version = 1, entities = [User::class])
abstract class AppDatabase : RoomDatabase() {
    abstract fun userDao(a): UserDao

    companion object {
        private var instance: AppDatabase? = null

        @Synchronized
        fun getDatabase(context: Context): AppDatabase { instance? .let {return it
            }
            return Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, "app_database").build().apply {
                instance = this}}}}Copy the code

RoomActivity

class RoomActivity : AppCompatActivity() {
    val TAG = this.javaClass.simpleName
    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_room)
        val userDao = AppDatabase.getDatabase(this).userDao()
        val user1 = User("Y"."X".22)
        val user2 = User("T"."Y".33)
        btn_add.setOnClickListener {
            thread {
                user1.id = userDao.insertUser(user1)
                user2.id = userDao.insertUser(user2)
            }
        }
        btn_udpdate.setOnClickListener {
            thread {
                user1.age = 50
                userDao.updateUser(user1)
            }
        }
        btn_delete.setOnClickListener {
            thread {
                userDao.deleteUserByLastName("Y")
            }
        }
        btn_query.setOnClickListener {
            thread {
                for (user in userDao.loadAllUsers()) {
                    Log.d(TAG, user.toString())
                }
            }
        }
    }
}
Copy the code

Book

@Entity
data class Book(var name: String, var pages: Int) {
    @PrimaryKey(autoGenerate = true)
    var id: Long = 0
}
Copy the code

BookDao

@Dao
interface BookDao {
    @Insert
    fun insertBook(book: Book): Long

    @Query("select * from Book")
    fun loadAllBooks(a): List<Book>
}
Copy the code

The modified AppDatabase

  1. version = 2
  2. User::class, Book::class
  3. abstract fun bookDao(): BookDao
  4. addMigrations(MIGRATION_1_2)
// Change: 1
@Database(version = 2, entities = [User::class, Book::class])
abstract class AppDatabase : RoomDatabase() {
    abstract fun userDao(a): UserDao
    // Change: 2
    abstract fun bookDao(a): BookDao

    companion object {
    // Change: 3
        val MIGRATION_1_2 = object : Migration(1.2) {
            override fun migrate(database: SupportSQLiteDatabase) {
                database.execSQL("create table Book (id integer primary key autoincrement not null,name text not null,pages integer not null)")}}private var instance: AppDatabase? = null

        // Change: 4
        @Synchronized
        fun getDatabase(context: Context): AppDatabase { instance? .let {return it
            }
            return Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, "app_database").addMigrations(MIGRATION_1_2).build().apply {
                instance = this}}}}Copy the code

Add var author: String again

@Entity
data class Book(var name: String, var pages: Int.var author: String) {
    @PrimaryKey(autoGenerate = true)
    var id: Long = 0
}
Copy the code

Change version =3 again for the third version

  1. version = 3
  2. MIGRATION_2_3
  3. addMigrations(MIGRATION_1_2, MIGRATION_2_3)
@Database(version = 3, entities = [User::class, Book::class])

val MIGRATION_2_3 = object : Migration(1.2) {
    override fun migrate(database: SupportSQLiteDatabase) {
        database.execSQL("alter table Book add column author text not null default 'unknown'")}}return Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, "app_database").addMigrations(MIGRATION_1_2, MIGRATION_2_3).build().apply {
                instance = this
            }
Copy the code

WorkManager

WorkManager is suitable for handling tasks that require scheduled execution, and it can automatically select whether the underlying implementation is AlarmManager or JobScheduler based on the version of the operating system, thus reducing our use cost.

In addition, WorkManager also supports periodic tasks, chained task processing and other functions, which is a very powerful tool.

Add the dependent

implementation 'androidx. Work: work - the runtime: 2.4.0'
Copy the code

Basic usage of WorkManager

The basic usage of WorkManager is actually very simple, mainly divided into the following three steps:

Define a background task and implement the specific task logic.

Configure the running conditions and constraints of the background task, and construct the request for the background task.

Pass the background task request into the WorkManager’s enqueue() method, and the system will run at the appropriate time.

Defining background Tasks

The first step to define a background task is to create a SimpleWorker class that looks like this:

class SimpleWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
    override fun doWork(a): Result {
        Log.d("SimpleWorker"."do work in SimpleWorker")
        return Result.success()
    }
}
Copy the code

Configure background tasks

Step 2: Configure the running conditions and constraints of background tasks. The code is as follows:

val request = OneTimeWorkRequest.Builder(SimpleWorker::class.java)
    .setInitialDelay(5, TimeUnit.MINUTES)
    .build()
Copy the code

As a final step, pass the constructed background task request into the WorkManager enqueue() method, and the system will run at the appropriate time, as shown below:

WorkManager.getInstance(context).enqueue(request)
Copy the code

Delay start

setInitialDelay(1, TimeUnit.MINUTES)
Copy the code

Set up the label

addTag("example")
Copy the code

Cancel the task

WorkManager.getInstance(this).cancelWorkById(request.id)
WorkManager.getInstance(this).cancelAllWork()
WorkManager.getInstance(this).cancelAllWorkByTag("example")
Copy the code

Observe the task

WorkManager.getInstance(this).getWorkInfoByIdLiveData(request.id)
	.observe(this) { workInfo ->
                when (workInfo.state) {
                    WorkInfo.State.SUCCEEDED -> {
                        Log.d(TAG, "WorkInfo.State.SUCCEEDED")
                    }
                    WorkInfo.State.FAILED -> {
                        Log.d(TAG, "WorkInfo.State.FAILED")
                    }
                    WorkInfo.State.RUNNING -> {
                        Log.d(TAG, "WorkInfo.State.RUNNING")
                    }
                    WorkInfo.State.CANCELLED -> {
                        Log.d(TAG, "WorkInfo.State.CANCELLED")
                    }
                    WorkInfo.State.ENQUEUED -> {
                        Log.d(TAG, "WorkInfo.State.ENQUEUED")}}}Copy the code

Repeat tasks

The doWork method returns result.retry ().

setBackoffCriteria(BackoffPolicy.LINEAR, 1, TimeUnit.SECONDS)
Copy the code

The sample

class WorkManagerActivity : AppCompatActivity() {
    val TAG = this.javaClass.simpleName
    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_work_manager)
        btn_do.setOnClickListener {
            val request = OneTimeWorkRequest.Builder(SimpleWorker::class.java)
                .setInitialDelay(2, TimeUnit.SECONDS).addTag("example")
                .setBackoffCriteria(BackoffPolicy.LINEAR, 1, TimeUnit.SECONDS)
                .build()



            WorkManager.getInstance(this).enqueue(request)
            WorkManager.getInstance(this).getWorkInfoByIdLiveData(request.id)
                .observe(this) { workInfo ->
                    when (workInfo.state) {
                        WorkInfo.State.SUCCEEDED -> {
                            Log.d(TAG, "WorkInfo.State.SUCCEEDED")
                        }
                        WorkInfo.State.FAILED -> {
                            Log.d(TAG, "WorkInfo.State.FAILED")
                        }
                        WorkInfo.State.RUNNING -> {
                            Log.d(TAG, "WorkInfo.State.RUNNING")
                        }
                        WorkInfo.State.CANCELLED -> {
                            Log.d(TAG, "WorkInfo.State.CANCELLED")
                        }
                        WorkInfo.State.ENQUEUED -> {
                            Log.d(TAG, "WorkInfo.State.ENQUEUED")}}}//WorkManager.getInstance(this).cancelWorkById(request.id)
        }
        btn_cancel.setOnClickListener {
            //WorkManager.getInstance(this).cancelAllWork()
            WorkManager.getInstance(this).cancelAllWorkByTag("example")}}}Copy the code

Project code Project video