Asynchronous Tasks in Android
The Main/UI thread dispatches events to UI element (widgets). Hence, code running on that threat should always be non-blocking: if it is blocked for more than 5 seconds, it will result in the ‘application not responding’ dialog.
The Android UI toolkit is not thread-safe; that is, it should never be accessed from outside the UI thread.
In Kotlin, Thread and Runnable can be used to create worker threads.
AsyncTask simplifies the creation of workers that interact with the UI.
AsyncTask
Usage:
- Create a worker by subclassing
AsyncTask - Implement the
doInBackground()callback function - Implement
onPostExecute(), which delivers the result to the UI (runs on the UI thread) - Run the task by calling
execute()from the UI thread
You can optionally implement the onProgressUpdate method to provide progress
updates (e.g. progress bars) in the UI.
Kotlin coroutines
Starting with version 1.3, the Kotlin language provides coroutines which
will replace AsyncTask in future Android versions.
Coroutines manage long-running tasks and can safely call network or disk operations.
Coroutines are lighter than threads (use less memory).
Dispatchers:
Dispatchers.Main:- Runs on the Main/UI thread
- Use for:
- Calling suspend functions
- Calling UI functions
- Updating LiveData
Dispatchers.IO:- Thread for performing disk or network I/O outside the main thread:
- Use for:
- Database access
- Reading/writing files
- Networks
Dispatchers.Default:- For CPU-incentive work outside of the main thread (e.g. parsing JSON)
suspend functions:
- Functions that do long-running computations without blocking
- Can only be called from other
suspendfunctions- Other
suspendfunctions can be called like normal methods: synchronously
- Other
- Runs on
Dispatchers.Mainby default withContext(Dispatcher)can be used to change the dispatcher it runs on
Calling suspend functions:
suspend func suspendMethodCall() -> Int {
return withContext(Dispatchers.IO) {
// some kind of synchronous work
10 // Last statement is the return value
}
}
// async
coroutineScope {
val deferred = async { suspendMethodCall() }
val result = deferred.await()
}
// launch
viewModelScope.launch {
suspendMethodCall()
}
// no return value when using launch
Built-in coroutine scopes:
ViewModelScope:- defined for each
ViewModelobject - Cancelled when the
ViewModelis cleared
- defined for each
LifecycleScopeLifecycleholds lifecycle state for a component (e.g. activity, fragment)- Cancelled when the
Lifecycleis destroyed
LiveData
Flows
Flows emit multiple values sequentially, compared to suspend functions which
can only return a single value.
Actors:
- Producer: is usually a data source or repository (e.g. database, socket).
- Consumer: usually the view
- Intermediaries: optional actors which transform the emitted values before arriving
to the consumer (e.g.
map,filter)
Flow builder notation:
fun counter(): Flow<Int> = flow {
// flow builder
for (i in 1..3) {
delay(100); // doing useful work here
emit(i)
}
}
counter().collect { value -> println(value) }
Room + Flow + LiveData + ViewModel:
Suspend Room
┌─────> Remote Data Source
View -----> ViewModel -----> Repository ---|
LiveData Flow └─────> Local Data Source
Suspend Room