According to a survey we’ve done, developers want Android to maintain a library of useful components and architectural practices to lower the bar for mid-to-large application development, so that development teams can focus more on optimizing and improving the actual business.

Jetpack is a set of tools and components that make it easier for you to build great Android apps. These components help you follow best practices, eliminate boilerplate code, and simplify complex tasks so you can focus on the core code logic. The androidx.* library is decoupled from the Framework API, which provides backward compatibility and more frequent updates.

The architectural components in Android Jetpack help you design robust, testable, and maintainable applications. From the Lifecycle library that was originally released to manage the Activity and Fragment Lifecycle and the Room library that accessed the SQLite database, Later came WorkManager libraries for Paging, Navigation, and managing background tasks. According to the latest developer Survey in 2019, more than 70% of professional developers have used at least one of these five libraries for app development.

Here we present the latest updates to architectural components in two installments. If you haven’t read the previous part of this article, please check out “Recent Developments in Android Architectural Components (Part 1)” here. This article will continue to introduce you to the paging library, Room persistence library, and WorkManager. I hope you will find new features and improvements that will help your app:

Paging library

Paging allows developers to gradually and efficiently load large amounts of data, thereby saving users’ batteries and traffic. And it works with other parts of the architecture component or other technologies, such as Room, Realm, Retrofit, etc.

To make paging easier to use, in a near future release we will provide:

  • Built-in networking support and error handling
  • Header and Footer support
  • Better RxJava support and coroutine integration

Room Persistence library

Room is a persistent repository that provides an abstraction layer on SQLite, and you can review our previous introductory article to learn more about Room in detail.

Coroutines processing

In Room 2.1, developers can use the Kotlin language’s suspend keyword to get Room to generate the correct coroutine code, including the use of a background dispatcher, which greatly reduces the amount of work developers have to do with coroutines:

/ / Room 2.1 @ Query ("SELECT * FROM song WHERE songId = :songId")
suspend fun getSong(songId: String): Song

@Insert
suspend fun insertSong(song: Song)

@Transaction
suspend fun deleteShortSongs(): List<Song> {
    val songs = getSongsWithElapsedTimeLessThan(1000)
    deleteSongsWithIds(songs.map { it.songId })
    return songs
}
Copy the code

In addition, extension functions are provided in Room 2.1 to enable developers to easily start transactions. It also provides a CoroutineContext (CoroutineContext) to make it easier for developers to perform multiple database operations:

database.withTransaction {
    val songs = getSongsWithElapsedTimeLessThan(1000)
    deleteSongsWithIds(songs.map { it.songId })
    return songs
}
Copy the code

Full-text search

The full-text search capability is an extension to SQLite, allowing it to create a data table to retrieve data more efficiently.

In Room 2.0, a Dao retrieval method might look like this:

// Room 2.0

@Dao
interface SongDao {
    @Query("""SELECT * FROM Song WHERE songName LIKE '%' | | : query | | '%' OR albumName LIKE '%' | | : query | | '%' OR artistName LIKE '%' | | : query | | '%'"")
    fun searchSongs(query: String): List<Song>
}
Copy the code

△ Notice the length of WHERE and OR statements

In 2.1, the @fts4 annotation made things a lot easier with the MATCH statement:

// Room 2.1

@Entity
@Fts4
data class Song(
    @PrimaryKey
    @ColumnInfo(name = "rowid")
    val id: Long,
    val url: String,
    val songName: String,
    val albumName: String,
    val artistName: String
)

@Dao
interface SongDao {
    @Query(""" SELECT * FROM Song WHERE Song MATCH :query """)
    fun searchSongs(query: String): List<Song>
}
Copy the code

Database view

It’s like a spreadsheet, but not exactly the same. Basically, you can retrieve a database view just like a data table, but you can’t insert data into it.

In 2.1, you could annotate your data classes with @databaseView, but instead of creating a data table, you put BigQuery directly in the annotations section to make it a view that can be quickly retrieved:

@DatabaseView(""" SELECT Album.*, count(song_id) AS num_of_songs, sum(song_elapsed_time) AS total_time FROM Album JOIN AlbumSongRef ON (album_id = ref_album_id) JOIN Song ON (ref_song_id  = song_id) GROUP BY album_id """)
data class AlbumItem(
    @Embedded
    val album: Album,
    @ColumnName("num_of_songs")
    val numOfSongs: Int,
    @ColumnName("total_time")
    val totalTime: Long
)
Copy the code

And this view (AlbumItem above) can be used like any other data table:

@Query(""" SELECT * FROM AlbumItem ORDER BY num_of_songs DESC """)
    fun getAlbumItemsByNumOfSongs(): List<AlbumItem>
Copy the code

Extended Rx support

In Room 2.1, the insert, update, and delete methods you use can return Completable, Maybe, and Single. And we can use Rx as the return type in Query annotation methods and handle writes like UPDATE, INSERT, or DELETE:

@Insert
    fun addSong(song: Song): Completable

    @Update
    fun updateSong(song: Song): Single<Int>

    @Query(""" UPDATE Song SET lastPlayedTime = :time WHERE id = :id """)
    fun updateSongPlayTime(Long: time, String: id): Completable
Copy the code

Room’s next step is to implement incremental annotation processing to speed up builds, as well as further improve the efficiency of processing relational data and ease of data migration. On the coroutines side, Channel and Flow support will be added.

WorkManager

WorkManager is a background process library for tasks that do not require immediate processing and can ensure that the task is triggered correctly even after the application or device is restarted. In addition, WorkManager supports conditional startup, such as starting specific tasks based on changes in network connection status.

Performance and Compatibility

On-demand configuration

On-demand Configuration allows developers to start WorkManager only when they need it. In WorkManager 2.1, you can get a WorkManager Configuration object by overloading the methods in Configuration.Provider.

/ / the WorkManager 2.1.0 class MyApp: Application (), the Configuration. The Provider {override fun getWorkManagerConfiguration () : Configuration {return Configuration.Builder()
            // set your options here
            .build()
    }
}
Copy the code

In WorkManager 2.0, the workManager.getInstance () method does not require the developer to provide a parameter. In 2.1, if the developer passes in the context parameter, the WorkManager is not initialized. It accesses the application object based on parameters and gets the configuration:

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

Google Play Services integration

This feature is coming soon and improves performance on devices prior to Marshmallow. The integration is optional, and developers can choose whether to integrate as needed.

Compatibility improvement

In terms of compatibility, we are mostly working behind the scenes. For example, communicate with oems to ensure that different devices have consistent application exit operations.

test

The first, and one that developers have been talking about: Robolectric support. Robolectric is an efficient and reliable Unit testing framework for Android that is now fully supported.

Second, Worker already provides support for unit testing.

You can use TestWorkerBuilder:

WorkManager 2.1.0 // Create atest worker
val request = OneTimeWorkRequestBuilder<MyWorker>.build()
val worker = TestWorkerBuilder
    .from(context, request, executor)
    .build()

// Test its behavior
val result = worker.doWork()
assertThat(result, `is`(Result.success()))
assertThat(...)
Copy the code

You can also use TestListenableWorkerBuilder:

/ / the WorkManager 2.1.0 / / Or create a listenable worker val request = OneTimeWorkRequestBuilder < MyWorker >. Build (val) listenableWorker = TestListenableWorkerBuilder .from(context, request) .build() // Test its behavior val result = listenableWorker.startWork().get() assertThat(result, `is`(Result.success())) assertThat(...)Copy the code

Next step for WorkManager

We are working on implementing support for foreground services so that you can use the WorkManager API in the foreground as well.

Thank you for your interest in this series, and I hope that after learning about the latest developments in architecture components, you will find some features that are suitable for your application. You can also revisit our overview of the architecture component’s progress by watching the video below ☟ at ☟.

  • Tencent Video link: V.qq.com/x/page/g300…
  • Bilibili video link: www.bilibili.com/video/av711…

If you have questions or suggestions about architectural components, feel free to share them in the comments section.

Click here toLearn more about Android Jetpack