Share
Embed
Diving into Coroutines
Diving into Coroutines
Bangkok Mobile Days
Enrique López Mañas
September 07, 2020
Transcript
82 slides
-
Why do we want coroutines? val user = fetchUserData() textView.text
= user.name NetworkOnMainThreadException -
Why do we want coroutines? thread { val user =
fetchUserData() textView.text = user.name } CalledFromAnotherThreadException -
Why do we want coroutines? fetchUserData { user -> //callback
textView.text = user.name } Leaking callbacks all the time -
Why do we want coroutines? val subscription = fetchUserData {
user -> textView.text = user.name } override fun onStop() { subscription.cancel() } -
Why do we want coroutines? override fun onStop() { subscription.cancel()
subscription1.cancel() subscription2.cancel() subscription3.cancel() subscription4.cancel() subscription5.cancel() subscription6.cancel() subscription7.cancel() subscription8.cancel() subscription9.cancel() } -
Why do we want coroutines? object MyTask: AsyncTask() { override
fun doInBackground { code } override fun onPostExecute { code } } -
RxJava fun fetchUser() : Observable<User> = ... fetchUser() .as(autoDisposable(AndroidLifecycleScopeProvider.from(this))) .subscribe{
user -> textView.text = user.name } -
LiveData RxJava Observable Data Holder Observable + Schedulers + Observers
Coroutines Suspendable computations -
RxJava Complete, but: - Easy to misuse - Feels like
an overkill - Learning curve -
Coroutines - Simplified - Comprehensive - Robust First class support
from Google in Jetpack -
Coroutines blocking.kt fun loadUser() { val user = api.fetchUser() show(user)
} fetchUser onDraw onDraw onDraw onDraw Show -
Coroutines async.kt fun loadUser() { api.fetchUser { user -> show(user)
} } fetchUser onDraw onDraw onDraw onDraw Show Ready! -
Coroutines coroutines.kt suspend fun loadUser() { val user = api.fetchUser()
show(user) } fetchUser onDraw onDraw onDraw onDraw Show Ready! suspend resume -
Coroutines coroutines.kt suspend fun loadUser() { val user = api.fetchUser()
show(user) } async.kt fun loadUser() { api.fetchUser { user -> show(user) } } -
Coroutines coroutines.kt suspend fun loadUser() { val user = api.fetchUser()
show(user) } suspend fun fetchUser() = withContext(Dispatchers.IO) { } -
Under the hood suspend fun loadUser() { val user =
api.fetchUser() show(user) } Main Thread Stack > Suspend marker Everything above a coroutine Everything underneath a regular function loadUser() api.fetchUser() -
Under the hood suspend fun loadUser() { val user =
api.fetchUser() show(user) } Main Thread Stack suspend resume api.fetchUser() loadUser() -
Under the hood suspend fun loadUser() { val user =
api.fetchUser() show(user) } Main Thread Stack suspend resume api.fetchUser() loadUser() -
Building blocks withContext() launch() async() T Job Deferred Returns result
Fire and forget Returns non-blocking future Uncaught exception Crashes app Uncaught exception Crashes app Uncaught exception is returned in deferred -
Building blocks CoroutineContext UI CommonPool Unconfined Dispatch execution into Android
MainThread Dispatch execution into Android Background Dispatch execution into Current thread -
Launch private fun loadData() { view.showLoading() val result = dataProvider.provideData()
view.showData(result) view.hideLoading() } -
Launch private fun loadData() = launch(uiContext) { view.showLoading() val result
= dataProvider.provideData() view.showData(result) view.hideLoading() } -
Launch private fun loadData() = launch(uiContext) { view.showLoading() val result
= withContext(bgContext) { dataProvider.provideData() } view.showData(result) view.hideLoading() } -
Launch two tasks sequentially private fun loadData() = launch(uiContext) {
view.showLoading() // ui thread // non ui thread, suspend until task is finished val result1 = withContext(bgContext) { dataProvider.provideData() } // non ui thread, suspend until task is finished val result2 = withContext(bgContext) { dataProvider.provideData() } val result = "$result1 $result2" // ui thread view.showData(result) view.hideLoading() } -
Launch two tasks sequentially private fun loadData() = launch(uiContext) {
view.showLoading() // ui thread // non ui thread, suspend until task is finished val result1 = withContext(bgContext) { dataProvider.provideData() } // non ui thread, suspend until task is finished val result2 = withContext(bgContext) { dataProvider.provideData() } val result = "$result1 $result2" // ui thread view.showData(result) view.hideLoading() } -
Launch two tasks in parallel private fun loadData() = launch(uiContext)
{ view.showLoading() // ui thread val task1 = async(bgContext) { dataProvider.provideData() } val task2 = async(bgContext) { dataProvider.provideData() } // non ui thread, suspend until finished val result = "${task1.await()} ${task2.await()}" view.showData(result) // ui thread view.hideLoading() } -
Launch two tasks in parallel private fun loadData() = launch(uiContext)
{ view.showLoading() // ui thread val task1 = async(bgContext) { dataProvider.provideData() } val task2 = async(bgContext) { dataProvider.provideData() } // non ui thread, suspend until finished val result = "${task1.await()} ${task2.await()}" view.showData(result) // ui thread view.hideLoading() } -
Launch two tasks in parallel private fun loadData() = launch(uiContext)
{ view.showLoading() // ui thread val deferred = listOf( async(bgContext) { dataProvider.provideData() } async(bgContext) { dataProvider.provideData() } ) deferred.awaitAll() } -
Launch coroutine with timeout private fun loadData() = launch(uiContext) {
view.showLoading() // ui thread // non ui thread, suspend until the task is finished // or return null in 2 sec val result = withTimeoutOrNull(2, TimeUnit.SECONDS) { withContext(bgContext) { dataProvider.provideData() } } view.showData(result) // ui thread view.hideLoading() } -
Launch coroutine with timeout private fun loadData() = launch(uiContext) {
view.showLoading() // ui thread // non ui thread, suspend until the task is finished // or return null in 2 sec val result = withTimeoutOrNull(2, TimeUnit.SECONDS) { withContext(bgContext) { dataProvider.provideData() } } view.showData(result) // ui thread view.hideLoading() } -
Cancel a coroutine private var job: Job? = null fun
onNextClicked() { job = loadData() } fun onBackClicked() { job?.cancel() } private fun loadData() = launch(uiContext) { // code } -
Cancel a coroutine private var job: Job? = null fun
onNextClicked() { job = loadData() } fun onBackClicked() { job?.cancel() } private fun loadData() = launch(uiContext) { // code } -
Cancel a coroutine private var job: Job? = null fun
onNextClicked() { job = loadData() } fun onBackClicked() { job?.cancel() } private fun loadData() = launch(uiContext) { // code } -
Try-catch private fun loadData() = launch(uiContext) { view.showLoading() // ui
thread try { val result = withContext(bgContext) { dataProvider.provideData() } view.showData(result) // ui thread } catch (e: IllegalArgumentException) { e.printStackTrace() } view.hideLoading() } -
Store inside deferred private fun loadData() = async(uiContext) { view.showLoading()
// ui thread val task = async(bgContext) { dataProvider.provideData() } val result = task.await() // non ui thread view.showData(result) // ui thread view.hideLoading() } -
Store inside deferred var job: Deferred = loadData() job.invokeOnCompletion {
it: Throwable? -> it?.printStackTrace() } -
Exception Handler val exceptionHandler= CoroutineExceptionHandler { _, throwable -> throwable.printStackTrace()
} private fun loadData() = launch(uiContext + exceptionHandler) { // code } -
Return null if exception suspend fun <T> Deferred<T>.awaitSafe(): T? =
try { await() } catch (e: Exception) { e.printStackTrace() null } -
Return null if exception private fun loadData() = launch(uiContext) {
val task = async(bgContext) { dataProvider.provideData() } val result = task.awaitSafe() if(result != null) { // success } else { // failure } } -
Return <T> suspend fun <T> Deferred<T>.awaitResult(): Result<T> = try {
Result(success = await(), failure = null) } catch (e: Exception) { Result(success = null, failure = e) } data class Result<out T>(val success: T?, val failure: Exception?) -
Return <T> private fun loadData() = launch(uiContext) { val task
= async(bgContext) { dataProvider.provideData() } val (success, failure) = task.awaitResult() if(success != null) { // success T } else { // failure Exception } } -
Testing class MainPresenter(val uiContext: CoroutineContext = UI, val bgContext: CoroutineContext
= CommonPool) { fun loadData() = launch(uiContext) { ... } } -
Testing @Test fun test() { val presenter = MainPresenter(Unconfined, Unconfined)
// test presenter.loadData() // verify verify(mockView).showLoading() verify(mockDataProvider).provideData() } -
More // retrofit 2 interface MyService { @GET("/user") fun getUser():
Deferred<User> // or @GET("/user") fun getUser(): Deferred<Response<User>> } -
Room @Dao interface UsersDao { @Query("SELECT * FROM users") suspend
fun getUsers(): List<User> @Query("UPDATE users SET age = age + 1 WHERE userId = :userId") suspend fun incrementUserAge(userId: String) @Insert suspend fun insertUser(user: User) @Update suspend fun updateUser(user: User) @Delete suspend fun deleteUser(user: User) } -
Room @Dao abstract class UsersDao { @Transaction open suspend fun
setLoggedInUser(loggedInUser: User) { deleteUser(loggedInUser) insertUser(loggedInUser) } @Query("DELETE FROM users") abstract fun deleteUser(user: User) @Insert abstract suspend fun insertUser(user: User) } -
Room class Repository(val database: MyDatabase) { suspend fun clearData(){ database.withTransaction
{ database.userDao().deleteLoggedInUser() // suspend function database.commentsDao().deleteComments() // suspend function } } } -
Room - Testing @Test fun insertAndGetUser() = runBlocking { //
Given a User that has been inserted into the DB userDao.insertUser(user) // When getting the Users via the DAO val usersFromDb = userDao.getUsers() // Then the retrieved Users matches the original user object assertEquals(listOf(user), userFromDb) } -
What to do? • Existing project? - Do they suit
you? - Refactoring? - Time? - Small sections? -
Further resources Kotlin Slack (kotlinlang.slack.com) Kotlin Weekly (http://kotlinweekly.net) Coroutines (https://github.com/Kotlin/kotlinx.coroutines)
Coroutines Guide (https://kotlinlang.org/docs/reference/coroutines/coroutines-guide.html) Codelabs (https://codelabs.developers.google.com/codelabs/kotlin-coroutines/#0)