Kotlin Coroutines Tutorial for Android : Advanced
Gain a deeper understanding of Kotlin Coroutines in this Advanced tutorial for Android, by replacing common asynchronous programming methods, such as Thread, in an Android app. By Rod Biresch.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Kotlin Coroutines Tutorial for Android : Advanced
30 mins
- What Are Coroutines?
- The Origins
- Nowadays
- Getting Started
- Key Components
- Suspendable Functions
- Continuations
- Coroutine Context
- Coroutine Builders
- Executing Concurrently
- Blocking Builder
- CoroutineScope
- Canceling a Job
- CoroutineDispatchers
- Handling Exceptions
- Coding Time
- Downloading the Project
- Adding Dependencies
- Life-cycle Awareness
- Updating the Repository
- Updating Injection Singleton
- Updating the PhotosFragment
- Registering the Lifecycle
- Main-Safe Design
- Defining CoroutineScope
- Hooking Into the Life-cycle
- Introducing Coroutines
- Where to Go From Here?
Updating the Repository
First, open Repository.kt and modify it as follows:
interface Repository : DefaultLifecycleObserver {
fun getPhotos(): LiveData<List<String>?>
fun getBanner(): LiveData<String?>
fun registerLifecycle(lifecycle: Lifecycle)
}
Here, you extended from DefaultLifecycleObserver
. This allows you to listen to lifecycle events and provide your custom logic.
The new registerLifecycle
function will provide the ability to hook in the life-cycle from the PhotosFragment
.
Updating Injection Singleton
Next, open Injection.kt. Change the method signature on provideViewModelFactory()
to the following:
fun provideViewModelFactory(lifecycle: Lifecycle): PhotosViewModelFactory {
val repository = provideRepository()
repository.registerLifecycle(lifecycle)
return PhotosViewModelFactory(repository)
}
You included a Lifecycle
parameter. Then, register the Lifecycle
in the Repository
.
Updating the PhotosFragment
Now, open PhotosFragment.kt and change the following method:
override fun onAttach(context: Context) {
super.onAttach(context)
val viewModelFactory = Injection.provideViewModelFactory(lifecycle)
viewModel = ViewModelProvider(this, viewModelFactory).get(PhotosViewModel::class.java)
}
You’ve provided the Fragment’s lifecycle
as an argument in provideViewModelFactory()
.
Registering the Lifecycle
Next, open PhotosRepository.kt again, and implement the new registerLifecycle()
function.
override fun registerLifecycle(lifecycle: Lifecycle) {
lifecycle.addObserver(this)
}
This adds an observer that will be notified when the Fragment changes state.
Main-Safe Design
Google encourages main-safety when writing coroutines. The concept is similar to how the Android system creates a main thread when an app launches.
The main thread is in charge of dispatching events to the appropriate user interface widgets. You should delegate I/O and CPU-intensive operations to a background thread to avoid jank in the app.
Main-safety is a design pattern for Kotlin coroutines. It lets coroutines use the Dispatchers.Main
, or main thread, as a default threading context. They then favor delegating to Dispatchers.IO
for heavy I/O operations or Dispatchers.Default
for CPU heavy operations.
A CoroutineScope
interface is available to classes which require scoped coroutines. However, you must define a CoroutineContext
instance which the scope will use for all the coroutines. Let’s do that.
Defining CoroutineScope
First, open the PhotosRepository
and modify it as follows:
class PhotosRepository : Repository, CoroutineScope {
private val TAG = PhotosRepository::class.java.simpleName
private val job: Job = Job()
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job
//...omitted code...
}
You’ve implemented CoroutineScope
, and defined a Job
and CoroutineContext
.
The Job
will determine if the coroutine is active and you will then use to cancel it. Per the main-safe design pattern, the Dispatchers.Main
defines the CoroutineScope
.
Hooking Into the Life-cycle
Add the following code to the PhotosRepository
.
override fun onStop(owner: LifecycleOwner) {
super.onStop(owner)
cancelJob()
}
private fun cancelJob() {
Log.d(TAG, "cancelJob()")
if (job.isActive) {
Log.d(TAG, "Job active, canceling")
job.cancel()
}
}
Because you previously registered this object as an observer to the Fragment’s life-cycle, Android will call the onStop()
method when the Lifecycle.Event.ON_STOP
event occurred in the PhotosFragment
. Here you’ve canceled the job.
A typical implementation is to include a Job
instance plus a Dispatcher
as context for the scope. Implementing the interface will let you call launch()
at any place and handle cancellation with the Job
you provided. The suspend functions can then call withContext(Dispatchers.IO)
or withContext(Dispatchers.Default)
to delegate work to background threads if necessary, keeping the initial threading tied to the main thread.
Introducing Coroutines
Currently, both the fetchBanner()
and fetchPhotos()
use a Runnable
and execute with a new Thread
. Firstly, you have to change the method implementation to use Kotlin coroutines. Then, you’ll run the project, to see if everything works as before.
The banner and images will download in the background. They’ll display like before with the separate background thread implementation.
Modify fetchBanner()
and fetchPhotos()
methods as follows:
// Dispatchers.Main
private suspend fun fetchBanner() {
val banner = withContext(Dispatchers.IO) {
// Dispatchers.IO
val photosString = PhotosUtils.photoJsonString()
// Dispatchers.IO
PhotosUtils.bannerFromJsonString(photosString)
}
// Dispatchers.Main
bannerLiveData.value = banner
}
// Dispatchers.Main
private suspend fun fetchPhotos() {
val photos = withContext(Dispatchers.IO) {
// Dispatchers.IO
val photosString = PhotosUtils.photoJsonString()
// Dispatchers.IO
PhotosUtils.photoUrlsFromJsonString(photosString)
}
// Dispatchers.Main
photosLiveData.value = photos
}
The functions above are annotated with comments. They show what thread or thread pool executes each line of code.
In this case, the thread is Dispatchers.Main
and the thread pool is Dispatchers.IO
. This helps visualize the main-safety design.
Notice that you don’t need to use liveData.postValue()
anymore because now you’re setting the Livedata values in the main thread.
Because the modified methods are now marked with suspend
, you have to change these function declarations, to avoid compiler errors:
override fun getPhotos(): LiveData<List<String>?> {
launch { fetchPhotos() }
return photosLiveData
}
override fun getBanner(): LiveData<String?> {
launch { fetchBanner() }
return bannerLiveData
}
Now, build and run the app. Open Logcat, filter with PhotosRepository
and then background the app. You should see the following:
PhotosRepository
received an Lifecycle.Event.ON_STOP
triggering an active coroutine Job
to cancel.
Congratulations! You’ve successfully converted asynchronous code to Kotlin coroutines. And everything still works but looks nicer! :]
Where to Go From Here?
You can download the completed project by clicking on the Download Materials button at the top or bottom of the tutorial.
To continue building your understanding of Kotlin Coroutines and how you can use them in Android app development check resources such as: Kotlin Coroutines by Tutorials book, kotlinx.coroutines and Coroutines Guide.
Please join the forum discussion below if you have any questions or comments.