SlideShare a Scribd company logo
Structured concurrency
With Kotlin Coroutines
Vadims Savjolovs
Product Engineer @ TransferWise
@savjolovs@savjolovs @savjolovs
// Agenda
Theory
● Coroutines
● Suspending functions
● Asynchronous Flows
● Channels
Practice
Business lookup feature in
TransferWise app for Android
// What are Kotlin Coroutines?
Lightweight threads?
Programming paradigm?
New reactive framework?
✅
✅
✅
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
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
// 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);
});
// Main-safety
viewModelScope.launch(Dispatchers.Main) {
val someString = getFromNetwork()
liveData.value = someString
}
suspend fun getFromNetwork(): String {
return URL("https://google.com").readText()
}
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.transferwise.android
android.os.NetworkOnMainThreadException
Redundant ‘suspend’
modifier
suspend fun getFromNetwork(): String {
return withContext(Dispatchers.IO) {
return@withContext URL("https://google.com").readText()
}
}
GlobalScope
{ getFromNetwork() }
{ getIdFromNetwork() }
myScope
getSomeData()
// Parallel decomposition
suspend fun getSomeData(): String {
val someString = GlobalScope.async { getFromNetwork() }
val id = GlobalScope.async { getIdFromNetwork() }
val res = process(someString.await(), id.await())
return res
}
warning
job.cancel()
val job = myScope.launch {
val someData = getSomeData()
}
LEAK
GlobalScope
// Structured concurrency
suspend fun getSomeData(): String {
return coroutineScope {
val someString = async { getFromNetwork() }
val id = async { getIdFromNetwork() }
val res = process(someString.await(), id.await())
return@coroutineScope res
}
}
job.cancel()
val job = myScope.launch {
val someData = getSomeData()
} myScope
getSomeData()
{ getFromNetwork() }
{ getIdFromNetwork() }
// Asynchronous Flow
fun numberFlow(): Flow<Int> = flow {
for (i in 1..100) {
delay(1000)
emit(i)
}
}
myScope.launch {
numberFlow()
.filter { it % 2 == 0 }
.map { it * 2 }
.take(10)
.collect { number ->
println(number)
}
}
// Channels
myScope.launch {
channel.send("1")
delay(1000L)
channel.send("2")
}
myScope.launch {
channel.consumeEach {
println(it)
}
}
coroutine 1 coroutine 2
send receive
channel
// 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
// Business lookup
// 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>
// 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)
}
}
// 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()
// 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
// 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
// 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)
}
// 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)
}
// 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)
}
})
// 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

More Related Content

What's hot

What's hot (20)

Introduction to kotlin and OOP in Kotlin
Introduction to kotlin and OOP in KotlinIntroduction to kotlin and OOP in Kotlin
Introduction to kotlin and OOP in Kotlin
 
Coroutines for Kotlin Multiplatform in Practise
Coroutines for Kotlin Multiplatform in PractiseCoroutines for Kotlin Multiplatform in Practise
Coroutines for Kotlin Multiplatform in Practise
 
Introduction to kotlin coroutines
Introduction to kotlin coroutinesIntroduction to kotlin coroutines
Introduction to kotlin coroutines
 
JUnit & Mockito, first steps
JUnit & Mockito, first stepsJUnit & Mockito, first steps
JUnit & Mockito, first steps
 
Using oracle-erp-cloud-adapter-oracle-integration
Using oracle-erp-cloud-adapter-oracle-integrationUsing oracle-erp-cloud-adapter-oracle-integration
Using oracle-erp-cloud-adapter-oracle-integration
 
Intro to Asynchronous Javascript
Intro to Asynchronous JavascriptIntro to Asynchronous Javascript
Intro to Asynchronous Javascript
 
File system node js
File system node jsFile system node js
File system node js
 
Introduction to Coroutines @ KotlinConf 2017
Introduction to Coroutines @ KotlinConf 2017Introduction to Coroutines @ KotlinConf 2017
Introduction to Coroutines @ KotlinConf 2017
 
Introducing the FLUID Principles
Introducing the FLUID PrinciplesIntroducing the FLUID Principles
Introducing the FLUID Principles
 
Whitebox testing of Spring Boot applications
Whitebox testing of Spring Boot applicationsWhitebox testing of Spring Boot applications
Whitebox testing of Spring Boot applications
 
Optional in Java 8
Optional in Java 8Optional in Java 8
Optional in Java 8
 
From a monolith to microservices + REST: The evolution of LinkedIn's architec...
From a monolith to microservices + REST: The evolution of LinkedIn's architec...From a monolith to microservices + REST: The evolution of LinkedIn's architec...
From a monolith to microservices + REST: The evolution of LinkedIn's architec...
 
Java 8 lambda expressions
Java 8 lambda expressionsJava 8 lambda expressions
Java 8 lambda expressions
 
Utilizing kotlin flows in an android application
Utilizing kotlin flows in an android applicationUtilizing kotlin flows in an android application
Utilizing kotlin flows in an android application
 
gRPC, GraphQL, REST - Which API Tech to use - API Conference Berlin oct 20
gRPC, GraphQL, REST - Which API Tech to use - API Conference Berlin oct 20gRPC, GraphQL, REST - Which API Tech to use - API Conference Berlin oct 20
gRPC, GraphQL, REST - Which API Tech to use - API Conference Berlin oct 20
 
JSON-LD: JSON for the Social Web
JSON-LD: JSON for the Social WebJSON-LD: JSON for the Social Web
JSON-LD: JSON for the Social Web
 
Introduction to thymeleaf
Introduction to thymeleafIntroduction to thymeleaf
Introduction to thymeleaf
 
Sequence and Traverse - Part 1
Sequence and Traverse - Part 1Sequence and Traverse - Part 1
Sequence and Traverse - Part 1
 
Quick introduction to scala
Quick introduction to scalaQuick introduction to scala
Quick introduction to scala
 
Map(), flatmap() and reduce() are your new best friends: simpler collections,...
Map(), flatmap() and reduce() are your new best friends: simpler collections,...Map(), flatmap() and reduce() are your new best friends: simpler collections,...
Map(), flatmap() and reduce() are your new best friends: simpler collections,...
 

Similar to Structured concurrency with Kotlin Coroutines

Similar to Structured concurrency with Kotlin Coroutines (20)

GDG Jakarta Meetup - Streaming Analytics With Apache Beam
GDG Jakarta Meetup - Streaming Analytics With Apache BeamGDG Jakarta Meetup - Streaming Analytics With Apache Beam
GDG Jakarta Meetup - Streaming Analytics With Apache Beam
 
Quick Introduction to Kotlin Coroutine for Android Dev
Quick Introduction to Kotlin Coroutine for Android DevQuick Introduction to Kotlin Coroutine for Android Dev
Quick Introduction to Kotlin Coroutine for Android Dev
 
Concurrent Programming in Java
Concurrent Programming in JavaConcurrent Programming in Java
Concurrent Programming in Java
 
Improving app performance with Kotlin Coroutines
Improving app performance with Kotlin CoroutinesImproving app performance with Kotlin Coroutines
Improving app performance with Kotlin Coroutines
 
Kotlin Coroutines and Rx
Kotlin Coroutines and RxKotlin Coroutines and Rx
Kotlin Coroutines and Rx
 
Server side JavaScript: going all the way
Server side JavaScript: going all the wayServer side JavaScript: going all the way
Server side JavaScript: going all the way
 
Presto anatomy
Presto anatomyPresto anatomy
Presto anatomy
 
Scaling application with RabbitMQ
Scaling application with RabbitMQScaling application with RabbitMQ
Scaling application with RabbitMQ
 
Eclipse IoT Talk (Montreal JUG)
Eclipse IoT Talk (Montreal JUG)Eclipse IoT Talk (Montreal JUG)
Eclipse IoT Talk (Montreal JUG)
 
Aplicações assíncronas no Android com
Coroutines & Jetpack
Aplicações assíncronas no Android com
Coroutines & JetpackAplicações assíncronas no Android com
Coroutines & Jetpack
Aplicações assíncronas no Android com
Coroutines & Jetpack
 
Aplicações assíncronas no Android com
Coroutines & Jetpack
Aplicações assíncronas no Android com
Coroutines & JetpackAplicações assíncronas no Android com
Coroutines & Jetpack
Aplicações assíncronas no Android com
Coroutines & Jetpack
 
Dragoncraft Architectural Overview
Dragoncraft Architectural OverviewDragoncraft Architectural Overview
Dragoncraft Architectural Overview
 
[NDC 2019] Enterprise-Grade Serverless
[NDC 2019] Enterprise-Grade Serverless[NDC 2019] Enterprise-Grade Serverless
[NDC 2019] Enterprise-Grade Serverless
 
[NDC 2019] Functions 2.0: Enterprise-Grade Serverless
[NDC 2019] Functions 2.0: Enterprise-Grade Serverless[NDC 2019] Functions 2.0: Enterprise-Grade Serverless
[NDC 2019] Functions 2.0: Enterprise-Grade Serverless
 
Java Concurrency
Java ConcurrencyJava Concurrency
Java Concurrency
 
Fork and join framework
Fork and join frameworkFork and join framework
Fork and join framework
 
Aplicações Assíncronas no Android com Coroutines e Jetpack
Aplicações Assíncronas no Android com Coroutines e JetpackAplicações Assíncronas no Android com Coroutines e Jetpack
Aplicações Assíncronas no Android com Coroutines e Jetpack
 
Current State of Coroutines
Current State of CoroutinesCurrent State of Coroutines
Current State of Coroutines
 
Fun Teaching MongoDB New Tricks
Fun Teaching MongoDB New TricksFun Teaching MongoDB New Tricks
Fun Teaching MongoDB New Tricks
 
TorqueBox at GNUnify 2012
TorqueBox at GNUnify 2012TorqueBox at GNUnify 2012
TorqueBox at GNUnify 2012
 

Recently uploaded

Search and Society: Reimagining Information Access for Radical Futures
Search and Society: Reimagining Information Access for Radical FuturesSearch and Society: Reimagining Information Access for Radical Futures
Search and Society: Reimagining Information Access for Radical Futures
Bhaskar Mitra
 

Recently uploaded (20)

Demystifying gRPC in .Net by John Staveley
Demystifying gRPC in .Net by John StaveleyDemystifying gRPC in .Net by John Staveley
Demystifying gRPC in .Net by John Staveley
 
Bits & Pixels using AI for Good.........
Bits & Pixels using AI for Good.........Bits & Pixels using AI for Good.........
Bits & Pixels using AI for Good.........
 
Key Trends Shaping the Future of Infrastructure.pdf
Key Trends Shaping the Future of Infrastructure.pdfKey Trends Shaping the Future of Infrastructure.pdf
Key Trends Shaping the Future of Infrastructure.pdf
 
When stars align: studies in data quality, knowledge graphs, and machine lear...
When stars align: studies in data quality, knowledge graphs, and machine lear...When stars align: studies in data quality, knowledge graphs, and machine lear...
When stars align: studies in data quality, knowledge graphs, and machine lear...
 
Empowering NextGen Mobility via Large Action Model Infrastructure (LAMI): pav...
Empowering NextGen Mobility via Large Action Model Infrastructure (LAMI): pav...Empowering NextGen Mobility via Large Action Model Infrastructure (LAMI): pav...
Empowering NextGen Mobility via Large Action Model Infrastructure (LAMI): pav...
 
Dev Dives: Train smarter, not harder – active learning and UiPath LLMs for do...
Dev Dives: Train smarter, not harder – active learning and UiPath LLMs for do...Dev Dives: Train smarter, not harder – active learning and UiPath LLMs for do...
Dev Dives: Train smarter, not harder – active learning and UiPath LLMs for do...
 
To Graph or Not to Graph Knowledge Graph Architectures and LLMs
To Graph or Not to Graph Knowledge Graph Architectures and LLMsTo Graph or Not to Graph Knowledge Graph Architectures and LLMs
To Graph or Not to Graph Knowledge Graph Architectures and LLMs
 
Slack (or Teams) Automation for Bonterra Impact Management (fka Social Soluti...
Slack (or Teams) Automation for Bonterra Impact Management (fka Social Soluti...Slack (or Teams) Automation for Bonterra Impact Management (fka Social Soluti...
Slack (or Teams) Automation for Bonterra Impact Management (fka Social Soluti...
 
UiPath Test Automation using UiPath Test Suite series, part 1
UiPath Test Automation using UiPath Test Suite series, part 1UiPath Test Automation using UiPath Test Suite series, part 1
UiPath Test Automation using UiPath Test Suite series, part 1
 
Behind the Scenes From the Manager's Chair: Decoding the Secrets of Successfu...
Behind the Scenes From the Manager's Chair: Decoding the Secrets of Successfu...Behind the Scenes From the Manager's Chair: Decoding the Secrets of Successfu...
Behind the Scenes From the Manager's Chair: Decoding the Secrets of Successfu...
 
SOQL 201 for Admins & Developers: Slice & Dice Your Org’s Data With Aggregate...
SOQL 201 for Admins & Developers: Slice & Dice Your Org’s Data With Aggregate...SOQL 201 for Admins & Developers: Slice & Dice Your Org’s Data With Aggregate...
SOQL 201 for Admins & Developers: Slice & Dice Your Org’s Data With Aggregate...
 
Search and Society: Reimagining Information Access for Radical Futures
Search and Society: Reimagining Information Access for Radical FuturesSearch and Society: Reimagining Information Access for Radical Futures
Search and Society: Reimagining Information Access for Radical Futures
 
De-mystifying Zero to One: Design Informed Techniques for Greenfield Innovati...
De-mystifying Zero to One: Design Informed Techniques for Greenfield Innovati...De-mystifying Zero to One: Design Informed Techniques for Greenfield Innovati...
De-mystifying Zero to One: Design Informed Techniques for Greenfield Innovati...
 
From Siloed Products to Connected Ecosystem: Building a Sustainable and Scala...
From Siloed Products to Connected Ecosystem: Building a Sustainable and Scala...From Siloed Products to Connected Ecosystem: Building a Sustainable and Scala...
From Siloed Products to Connected Ecosystem: Building a Sustainable and Scala...
 
How world-class product teams are winning in the AI era by CEO and Founder, P...
How world-class product teams are winning in the AI era by CEO and Founder, P...How world-class product teams are winning in the AI era by CEO and Founder, P...
How world-class product teams are winning in the AI era by CEO and Founder, P...
 
10 Differences between Sales Cloud and CPQ, Blanka Doktorová
10 Differences between Sales Cloud and CPQ, Blanka Doktorová10 Differences between Sales Cloud and CPQ, Blanka Doktorová
10 Differences between Sales Cloud and CPQ, Blanka Doktorová
 
"Impact of front-end architecture on development cost", Viktor Turskyi
"Impact of front-end architecture on development cost", Viktor Turskyi"Impact of front-end architecture on development cost", Viktor Turskyi
"Impact of front-end architecture on development cost", Viktor Turskyi
 
IoT Analytics Company Presentation May 2024
IoT Analytics Company Presentation May 2024IoT Analytics Company Presentation May 2024
IoT Analytics Company Presentation May 2024
 
In-Depth Performance Testing Guide for IT Professionals
In-Depth Performance Testing Guide for IT ProfessionalsIn-Depth Performance Testing Guide for IT Professionals
In-Depth Performance Testing Guide for IT Professionals
 
Exploring UiPath Orchestrator API: updates and limits in 2024 🚀
Exploring UiPath Orchestrator API: updates and limits in 2024 🚀Exploring UiPath Orchestrator API: updates and limits in 2024 🚀
Exploring UiPath Orchestrator API: updates and limits in 2024 🚀
 

Structured concurrency with Kotlin Coroutines

  • 2. Vadims Savjolovs Product Engineer @ TransferWise @savjolovs@savjolovs @savjolovs
  • 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); });
  • 8. // Main-safety viewModelScope.launch(Dispatchers.Main) { val someString = getFromNetwork() liveData.value = someString } suspend fun getFromNetwork(): String { return URL("https://google.com").readText() } E/AndroidRuntime: FATAL EXCEPTION: main Process: com.transferwise.android android.os.NetworkOnMainThreadException Redundant ‘suspend’ modifier suspend fun getFromNetwork(): String { return withContext(Dispatchers.IO) { return@withContext URL("https://google.com").readText() } }
  • 9. GlobalScope { getFromNetwork() } { getIdFromNetwork() } myScope getSomeData() // Parallel decomposition suspend fun getSomeData(): String { val someString = GlobalScope.async { getFromNetwork() } val id = GlobalScope.async { getIdFromNetwork() } val res = process(someString.await(), id.await()) return res } warning job.cancel() val job = myScope.launch { val someData = getSomeData() } LEAK
  • 10. GlobalScope // Structured concurrency suspend fun getSomeData(): String { return coroutineScope { val someString = async { getFromNetwork() } val id = async { getIdFromNetwork() } val res = process(someString.await(), id.await()) return@coroutineScope res } } job.cancel() val job = myScope.launch { val someData = getSomeData() } myScope getSomeData() { getFromNetwork() } { getIdFromNetwork() }
  • 11. // Asynchronous Flow fun numberFlow(): Flow<Int> = flow { for (i in 1..100) { delay(1000) emit(i) } } myScope.launch { numberFlow() .filter { it % 2 == 0 } .map { it * 2 } .take(10) .collect { number -> println(number) } }
  • 12. // Channels myScope.launch { channel.send("1") delay(1000L) channel.send("2") } myScope.launch { channel.consumeEach { println(it) } } coroutine 1 coroutine 2 send receive channel
  • 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
  • 20. // 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) }
  • 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