Structured concurrency with Kotlin Coroutines
1. Theory
- Coroutines
- Suspending functions
- Asynchronous Flows
- Channels
2. Practice
- Business lookup feature implementation in TransferWise app for Android
3. // Agenda
Theory
● Coroutines
● Suspending functions
● Asynchronous Flows
● Channels
Practice
Business lookup feature in
TransferWise app for Android
4. // What are Kotlin Coroutines?
Lightweight threads?
Programming paradigm?
New reactive framework?
✅
✅
✅
5. fun main() {
GlobalScope.launch {
delay(1000L)
println("World!")
}
println("Hello,")
Thread.sleep(2000L)
}
// Coroutine are like threads
In a context of a
CoroutineScope
launch is a
coroutine builder
that launches a new
coroutine
Print “Hello, ”
Keep JVM alive for 2
more seconds
suspending function
that does not block
a thread, but
suspends coroutine
6. val counter = AtomicLong()
for (i in 1..1_000_000L) {
GlobalScope.launch {
counter.incrementAndGet()
}
}
println(counter)
val counter = AtomicLong()
for (i in 1..1_000_000L) {
thread(start = true) {
counter.incrementAndGet()
}
}
println(counter)
// Coroutines are cheap
~1.5 seconds~63 seconds
7. // Suspending functions
suspend fun getFromNetwork(): String
GlobalScope.launch {
val data = getSomeData()
updateUi(data)
}
suspend fun getSomeData(): String {
val someString = getFromNetwork()
val id = getIdFromNetwork()
val res = process(someString, id)
return res
}
// Dart language
Future<String> getSomeData() async {
final someString = await getFromNetwork();
final id = await getIdFromNetwork();
final res = process(someString, id);
return Future.value(res);
}
// Dart language
getSomeData().then((data) {
updateUi(data);
});
13. // What did we covered?
Coroutines
● Very lightweight threads
● Structured concurrency
● Start with launch{} or async{}
● Call suspending functions
Suspending functions
● Not blocking thread
● Suspends a coroutine
● For a long-running tasks
Asynchronous Flows
● Data streams
● Transformation operators
Channels
● Communication tool for coroutines
● Hot data streams
15. // Lookup Service
UI (Fragment)
ViewModel
Interactor
Repository
Network
Service
@GET("v2/registry/search")
suspend fun businessLookup(
@Query("searchTerms") query: String,
@Query("countryCode") countryCode: String
): NetworkResponse<List<LookupBusinessResponse>, ErrorsResponse>
16. // Lookup Repository
UI (Fragment)
ViewModel
Interactor
Repository
Network
Service
@Inject
val lookupService: LookupService
suspend fun businessLookup(query: String, countryCode: String) =
withContext(Dispatchers.IO) {
val response = lookupService.businessLookup(query, countryCode)
when (response) {
is NetworkResponse.Success ->
return@withContext mapper.map(response)
is NetworkResponse.Error ->
return@withContext errorMapper.map(response)
}
}
17. // Lookup Interactor
UI (Fragment)
ViewModel
Interactor
Repository
Network
Service
@Inject
val lookupRepository: LookupRepository
suspend operator fun invoke(
query: String,
countryCode: String
): Result<List<LookupBusiness>, ErrorMessage> {
return lookupRepository.businessLookup(query, countryCode)
}
lookupInteractor() instead of lookupInteractor.invoke()
18. // Lookup ViewModel
UI (Fragment)
ViewModel
Interactor
Repository
Network
Service
@Inject
val lookupInteractor: LookupInteractor
fun searchBusiness(query: String) {
viewState.value = ViewState.Loading
viewModelScope.launch(Dispatchers.Main) {
val result = lookupInteractor(query, countryCode)
when (result) {
is Result.Success ->
viewState.value = ViewState.SearchResults(result.entity)
is Result.Failure ->
viewState.value = ViewState.ErrorState(result.failure)
}
}
} warning
19. // Lookup ViewModel
UI (Fragment)
ViewModel
Interactor
Repository
Network
Service
@Inject
val lookupInteractor: LookupInteractor
private fun searchBusiness(query: String)
GET: v2/registry/search
T
Tr
Tra
Tran
Trans
Transf
Transfe
Transfer
Transferw
Transferwi
Transferwis
Transferwise
warning
21. // Lookup ViewModel
UI (Fragment)
ViewModel
Interactor
Repository
Network
Service
private val сhannel = Channel<String>()
init {
viewModelScope.launch(Dispatchers.Main) {
сhannel.consumeAsFlow().debounce(500L).collect { query ->
searchBusiness(query)
}
}
}
fun search(query: String) {
сhannel.offer(query)
}
private suspend fun searchBusiness(query: String) {
viewState.value = ViewState.Loading
val result = lookupInteractor(query, countryCode)
when (result) {
is Result.Success ->
viewState.value = ViewState.SearchResults(result.entity)
is Result.Failure ->
viewState.value = ViewState.ErrorState(result.failure)
}
22. // Lookup Fragment
UI (Fragment)
ViewModel
Interactor
Repository
Network
Service
searchView.addTextChangedListener {
override fun afterTextChanged(s: Editable?) {
viewModel.search(s.toString())
}
}
viewModel.viewState.observe(this, Observer { result ->
return@Observer when (result) {
is ViewState.Loading -> showLoading()
is ViewState.SearchResults -> showResult(result.businessList)
is ViewState.ErrorState -> showError(result.error)
}
})
23. // Links
Official docs
Kotlin Flows and Coroutines - Blog Post
Coroutine Context and Scope - Blog Post
Structured Concurrency - Blog Post
Blocking threads, suspending coroutines - Blog Post
Kotlin: Diving in to Coroutines and Channels - Blog Post