1 of 57

Building Async Mechanisms

Using the Kotlin Coroutines API, in Android

Filip Babić

@filbabic

2 of 57

Contents

  • Why Coroutines?
  • What are Coroutines?
  • Suspend vs. Blocking
  • Implementing Coroutines

  • Effective usage
  • Best Practices
  • Internal Works

3 of 57

Why Coroutines?

Moving from Rx and Callbacks

Provides a clear, sequential and understandable syntax, to handle complex asynchronous work.

Performant (out of the box), easy to use, and simple to learn*.

Solves all the problems as other mechanisms, while being straightforward.

4 of 57

What are Coroutines?

A blast from the past

Fairly old concept (dates back to the 60s) in computer programming.

A special kind of system routines (think functions), which can be suspended and resumed at any point in time.

Can run in parallel, with other routines - hence the term (co)routine.

5 of 57

Routine lifecycle

Simplified

End

S/routines

S/routines

Finish

Start

6 of 57

Routine lifecycle (w/ coroutines)

End

S/routines

S/routines

Finish

Start

Start

End

Coroutine

Suspend

7 of 57

Generalized type of subroutines, which can be suspended and resumed at any point in time.

8 of 57

What’s in the API

Most important functions

Launch - Basic coroutine builder, returns a Job (piece of work you can cancel)

Async/Await - Advanced coroutine builder, returns a Deferred value (also a Job), await to get the result

WithContext - value producing suspendable function

9 of 57

Other Important Constructs

Internal components

CoroutineScope - Lifecycle component used to start and confine coroutines

CoroutineContext - Set of unique elements, dictates rules for a given coroutine (Lifecycle, Exception Handling, Threading)

Suspend - Modifier which marks that the function can be suspended

10 of 57

GlobalScope.launch(context = Dispatchers.Main) {

val userId = withContext(Dispatchers.IO) { getUserId() }

println(userId)

}

11 of 57

GlobalScope.launch(context = Dispatchers.Main) {

val userId = withContext(Dispatchers.IO) { getUserId() }

println(userId)

}

12 of 57

GlobalScope.launch(context = Dispatchers.Main) {

val userId = withContext(Dispatchers.IO) { getUserId() }

println(userId)

}

13 of 57

GlobalScope.launch(context = Dispatchers.Main) {

val userId = withContext(Dispatchers.IO) { getUserId() }

println(userId)

}

14 of 57

GlobalScope.launch(context = Dispatchers.Main) {

val userId = withContext(Dispatchers.IO) { getUserId() }

println(userId)

}

15 of 57

GlobalScope.launch(context = Dispatchers.Main) {

val userId = withContext(Dispatchers.IO) { getUserId() }

println(userId)

}

16 of 57

Suspend vs. Blocking

The power behind Coroutines

17 of 57

private fun doHeavyWork() {

val expensiveResult = getExpensiveResult()

if (isValid(expensiveResult)) {

// do some work

} else {

// show an error

}

}

18 of 57

private fun doHeavyWork() {

val expensiveResult = getExpensiveResult()

if (isValid(expensiveResult)) {

// do some work

} else {

// show an error

}

}

19 of 57

private suspend fun doHeavyWork() {

val expensiveResult = getExpensiveResult()

if (isValid(expensiveResult)) {

// do some work

} else {

// show an error

}

}

20 of 57

private suspend fun getExpensiveResult() =

withContext(Dispatchers.IO) {

// return some expensive value

}

21 of 57

private suspend fun doHeavyWork() {

val expensiveResult = getExpensiveResult()

if (isValid(expensiveResult)) {

// do some work

} else {

// show an error

}

}

22 of 57

private suspend fun getExpensiveResult() =

withContext(Dispatchers.IO) {

// return some expensive value

}

Separate Thread

23 of 57

private suspend fun doHeavyWork() {

val expensiveResult = getExpensiveResult()

if (isValid(expensiveResult)) {

// do some work

} else {

// show an error

}

}

24 of 57

private suspend fun doHeavyWork() {

val expensiveResult = getExpensiveResult()

if (isValid(expensiveResult)) {

// do some work

} else {

// show an error

}

}

25 of 57

Time to get our hands on some code!

Implementing Coroutines in Android

26 of 57

Disclaimer!!!

“How can I do it myself?”

27 of 57

Disclaimer!!!

“How can I do it myself?”

How can I do it without effort?

28 of 57

Starting a coroutine

  • CoroutineScope - Lifecycle
  • CoroutineContext - rules

  • CoroutineBuilder - type of created coroutine

29 of 57

dependencies {

implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:+"

}

30 of 57

class UserViewModel(private val repo: UserRepository) : ViewModel() {

private fun registerUser(userData: UserData) {

viewModelScope.launch {

...

}

}

}

31 of 57

class UserViewModel(private val repo: UserRepository) : ViewModel() {

private fun registerUser(userData: UserData) {

viewModelScope.launch {

...

}

}

}

32 of 57

class UserViewModel(private val repo: UserRepository) : ViewModel() {

private fun registerUser(userData: UserData) {

viewModelScope.launch {

...

}

}

}

33 of 57

class UserViewModel(private val repo: UserRepository) : ViewModel() {

private fun registerUser(userData: UserData) {

viewModelScope.launch {

val data = repo.registerUser(userData)

// check if the data is a Success or Failure

}

}

}

34 of 57

Effective usage

Implementing Coroutines in Android

35 of 57

Using Async

End

async

await

Start

Start

End

Coroutine Running

Await -> Suspend

36 of 57

(Properly) Using Async

...

End

async

await

Start

End

Coroutine Running

async

Start

Coroutine Running

End

37 of 57

private suspend fun getUserProfile(userId: String): UserProfile {

val userDeferred = async(Dispatchers.IO) { apiService.getUserById(userId) }

val postsDeferred = async(Dispatchers.IO) { apiService.getAllPosts(userId) }

val featuredDeferred = async(Dispatchers.IO) { apiService.getFeaturedPosts(userId) }

return UserProfile(userDeferred.await(), postsDeferred.await(), featuredDeferred.await())

}

38 of 57

async/await is best used for multiple, parallel, value fetching. For single, or sequential-like value returns, prefer withContext.

39 of 57

// somewhere in the repo

override suspend fun getUserProfile(data): UserProfile =

withContext(Dispatchers.IO) {

apiService.getUserProfile()

}

40 of 57

Using WithContext

End

withContext

Consume Value

Start

41 of 57

To bridge threads and sync and async work, use withContext. It executes a block of code in one dispatcher, returning the value to the call site, in the original dispatcher.

42 of 57

Best Practices

Optimizing and cleaning up your code

43 of 57

Best Practices

  • Decoupling coroutine responsibility
  • Using builders effectively
  • Abstracting away Context

  • Handling exceptions
  • … & returning Results

44 of 57

Decoupling Responsibility

Knowing about Coroutines in the right layers

45 of 57

Layered approach

View

Repository

VM

Entity

Use Case

Mapper

46 of 57

Layered approach

View

Repository

VM

Entity

Use Case

Mapper

47 of 57

Layered approach

View

Repository

VM

Entity

Use Case

Mapper

48 of 57

Moving Coroutines to Repositories

The Business layer shouldn’t really know if the data comes from the IO thread pool the Default pool, or something else.

The business logic code should look and feel as if it were sequential, non-asynchronous code.

49 of 57

Additionally, sometimes, it’s best to rely on pre-built mechanisms!

50 of 57

// Room integrations with coroutines

@Dao

interface UsersDao {

@Query("SELECT * FROM users")

suspend fun getUsers(): List<User>

@Insert

suspend fun insertUser(user: User)

@Delete

suspend fun deleteUser(user: User)

}

51 of 57

// Retrofit / Room integrations with coroutines

@GET // your path

fun getUserProfile(@Query(value = "id") userId: String): Call<UserProfile>

// With coroutines support

@GET

suspend fun getUserProfile(@Query(value = "id") userId: String): UserProfile

52 of 57

What About Exceptions?

Nah, our things won’t crash! (We hope)

53 of 57

Two ways of handling exceptions

Adding a try/catch block to your coroutine-powered code. Completely fine, but can become a bit cumbersome!

Using the CoroutineExceptionHandler as an automatic exception interceptor. Easy, clean, but not always what you want.

54 of 57

Resources

55 of 57

Kotlin Coroutines

By Tutorials

A bunch of useful examples and projects.

In depth Coroutine exploration.

Half screen photo slide if �text is necessary

56 of 57

Questions?

57 of 57

Thank You!

Filip Babić

@filbabic