$30 off During Our Annual Pro Sale. View Details »

Android Suspenders

Chris Banes
October 04, 2018

Android Suspenders

So you’ve read the Coroutines guide and you’re ready to start using them in your Android app to coroutines? Great!

This talk will focus on the best practices of using coroutines in your app, including how to handle lifecycle changes with Architecture Components, integration with background job processing, and moving away from RxJava.

Chris Banes

October 04, 2018
Tweet

More Decks by Chris Banes

Other Decks in Programming

Transcript

  1. C R U D reate ead pdate elete What does

    your typical mobile app do?
  2. -core -android -rx2 jars: 774KB apk: 499KB 3260 method refs

    r8: 113KB 1208 method refs r8
 optimized: 99KB 834 method refs
  3. override suspend fun updateShow(showId: Long) { val localResult = localShowStore.getShow(showId)

    val result1 = remoteSource1.getShow(showId) val result2 = remoteSource2.getShow(showId) val merged = mergeShow(localResult, result1, result2) localShowStore.saveShow(merged) }.
  4. override suspend fun updateShow(showId: Long) { val localResult = localShowStore.getShow(showId)

    val result1 = remoteSource1.getShow(showId) val result2 = remoteSource2.getShow(showId) val merged = mergeShow(localResult, result1, result2) localShowStore.saveShow(merged) }. 1
  5. override suspend fun updateShow(showId: Long) { val localResult = localShowStore.getShow(showId)

    val result1 = remoteSource1.getShow(showId) val result2 = remoteSource2.getShow(showId) val merged = mergeShow(localResult, result1, result2) localShowStore.saveShow(merged) }. 1 2
  6. override suspend fun updateShow(showId: Long) { val localResult = localShowStore.getShow(showId)

    val result1 = remoteSource1.getShow(showId) val result2 = remoteSource2.getShow(showId) val merged = mergeShow(localResult, result1, result2) localShowStore.saveShow(merged) }. 1 2 3
  7. override suspend fun updateShow(showId: Long) { val localResult = localShowStore.getShow(showId)

    val result1 = remoteSource1.getShow(showId) val result2 = remoteSource2.getShow(showId) val merged = mergeShow(localResult, result1, result2) localShowStore.saveShow(merged) }.
  8. override suspend fun updateShow(showId: Long) { val localResult = localShowStore.getShow(showId)

    val result1 = remoteSource1.getShow(showId) val result2 = remoteSource2.getShow(showId) val merged = mergeShow(localResult, result1, result2) localShowStore.saveShow(merged) }. 1 2 3 Runs in 1.2 seconds
  9. override suspend fun updateShow(showId: Long) { val localDeferred = async

    {
 localShowStore.getShow(showId)
 } val remoteDeferred1 = async { remoteSource1.getShow(showId) } val remoteDeferred2 = async { remoteSource2.getShow(showId) } 
 val localResult = localDeferred.await() val result1 = remoteDeferred1.await() val result2 = remoteDeferred2.await() val merged = mergeShow(localResult, result1, result2) localShowStore.saveShow(merged) }.
  10. override suspend fun updateShow(showId: Long) { val localDeferred = async

    {
 localShowStore.getShow(showId)
 } val remoteDeferred1 = async { remoteSource1.getShow(showId) } val remoteDeferred2 = async { remoteSource2.getShow(showId) } 
 val localResult = localDeferred.await() val result1 = remoteDeferred1.await() val result2 = remoteDeferred2.await() val merged = mergeShow(localResult, result1, result2) localShowStore.saveShow(merged) }.
  11. override suspend fun updateShow(showId: Long) { val localDeferred = async

    {
 localShowStore.getShow(showId)
 } val remoteDeferred1 = async { remoteSource1.getShow(showId) } val remoteDeferred2 = async { remoteSource2.getShow(showId) } 
 val localResult = localDeferred.await() val result1 = remoteDeferred1.await() val result2 = remoteDeferred2.await() val merged = mergeShow(localResult, result1, result2) localShowStore.saveShow(merged) }.
  12. override suspend fun updateShow(showId: Long) { val localDeferred = async

    {
 localShowStore.getShow(showId)
 } val remoteDeferred1 = async { remoteSource1.getShow(showId) } val remoteDeferred2 = async { remoteSource2.getShow(showId) } 
 val localResult = localDeferred.await() val result1 = remoteDeferred1.await() val result2 = remoteDeferred2.await() val merged = mergeShow(localResult, result1, result2) localShowStore.saveShow(merged) }.
  13. override suspend fun updateShow(showId: Long) { val localDeferred = async

    {
 localShowStore.getShow(showId)
 } val remoteDeferred1 = async { remoteSource1.getShow(showId) } val remoteDeferred2 = async { remoteSource2.getShow(showId) } 
 val localResult = localDeferred.await() val result1 = remoteDeferred1.await() val result2 = remoteDeferred2.await() val merged = mergeShow(localResult, result1, result2) localShowStore.saveShow(merged) }.
  14. override suspend fun updateShow(showId: Long) { val localDeferred = async

    {
 localShowStore.getShow(showId)
 } val remoteDeferred1 = async { remoteSource1.getShow(showId) } val remoteDeferred2 = async { remoteSource2.getShow(showId) } 
 val localResult = localDeferred.await() val result1 = remoteDeferred1.await() val result2 = remoteDeferred2.await() val merged = mergeShow(localResult, result1, result2) localShowStore.saveShow(merged) }. Runs in 0.8 second
  15. class ShowDetailsViewModel: ViewModel() { val showRepository: ShowRepository = // ...

    val state: LiveData<ViewState> init { refresh() } private fun refresh() { launch { showRepository.updateShow(showId) // update view state } } }
  16. class ShowDetailsViewModel: ViewModel() { val showRepository: ShowRepository = // ...

    val state: LiveData<ViewState> init { refresh() } private fun refresh() { launch { showRepository.updateShow(showId) // update view state } } }
  17. class ShowDetailsViewModel: ViewModel() { val showRepository: ShowRepository = // ...

    val state: LiveData<ViewState> init { refresh() } private fun refresh() { launch { showRepository.updateShow(showId) // update view state } } }
  18. class ShowDetailsViewModel: ViewModel() { val showRepository: ShowRepository = // ...

    val state: LiveData<ViewState> init { refresh() } private fun refresh() { launch { showRepository.updateShow(showId) // update view state } } }
  19. class ShowDetailsViewModel: ViewModel() { val showRepository: ShowRepository = // ...

    val state: LiveData<ViewState> init {• refresh() }. private fun refresh() {• launch {• showRepository.updateShow(showId) // update view state }. }. }.
  20. class ShowDetailsViewModel: ViewModel() { private fun refresh() { launch {

    showRepository.updateShow(showId) // update view state }. }. }.
  21. class ShowDetailsViewModel: ViewModel() { private fun refresh() { val job

    = launch { showRepository.updateShow(showId) // update view state }. }. }.
  22. class ShowDetailsViewModel: ViewModel() { private fun refresh() { val job

    = launch { showRepository.updateShow(showId) // update view state }. // can call job.cancel() }. }.
  23. class ShowDetailsViewModel: ViewModel() { private var updateShowJob: Job? = null

    private fun refresh() { val job = launch { showRepository.updateShow(showId) // update view state }. // can call job.cancel() }. }.
  24. class ShowDetailsViewModel: ViewModel() { private var updateShowJob: Job? = null

    private fun refresh() { updateShowJob = launch { showRepository.updateShow(showId) // update view state }. // can call job.cancel() }. }.
  25. class ShowDetailsViewModel: ViewModel() { private var updateShowJob: Job? = null

    private fun refresh() { updateShowJob = launch { showRepository.updateShow(showId) // update view state }. }. }.
  26. class ShowDetailsViewModel: ViewModel() { private var updateShowJob: Job? = null

    private fun refresh() { updateShowJob = launch { showRepository.updateShow(showId) // update view state }. }. override fun onCleared() { updateShowJob ?.cancel() } }.
  27. class ShowDetailsViewModel: ViewModel() { private var updateShowJob: Job? = null

    private var updateEpisodesJob: Job? = null private fun refresh() { updateShowJob = launch { showRepository.updateShow(showId) // update view state }. updateShowJob = launch { ... } }. override fun onCleared() { updateShowJob ?.cancel() updateEpisodesJob ?.cancel() } }.
  28. class ShowDetailsViewModel: ViewModel() { private var updateShowJob: Job? = null

    private var updateEpisodesJob: Job? = null private var updateCastJob: Job? = null private fun refresh() { updateShowJob = launch { showRepository.updateShow(showId) // update view state }. updateShowJob = launch { ... } updateCastJob = launch { ... } }. override fun onCleared() { updateShowJob ?.cancel() updateEpisodesJob ?.cancel() updateCastJob ?.cancel() } }.
  29. class ShowDetailsViewModel: ViewModel() { val showRepository: ShowRepository = // ...

    val state: LiveData<ViewState> private val job = Job() init {• refresh() }. private fun refresh() {• launch(parent = job) {• showRepository.updateShow(showId) // update view state }. }.
  30. class ShowDetailsViewModel: ViewModel() { val showRepository: ShowRepository = // ...

    val state: LiveData<ViewState> private val job = Job() init { refresh() }. private fun refresh() { launch(parent = job) { showRepository.updateShow(showId) // update view state }. }. override onCleared() {
  31. private val job = Job() init { refresh() }. private

    fun refresh() { launch(parent = job) { showRepository.updateShow(showId) // update view state }. }. override onCleared() { job.cancel() } .. }.
  32. private val job = Job() init { refresh() }. private

    fun refresh() { launch(parent = job) { showRepository.updateShow(showId) // update view state }. }. override onCleared() { job.cancel() } } ..
  33. private val job = Job() init { refresh() }. private

    fun refresh() { launch(parent = job) { showRepository.updateShow(showId) // update view state }. }. override onCleared() { job.cancel() } } ..
  34. private val job = Job() init { refresh() }. private

    fun refresh() { launch(parent = job) { showRepository.updateShow(showId) // update view state }. }. override onCleared() { job.cancel() } } .. launch is now scoped to the lifetime of the ViewModel
  35. private val job = Job() init { refresh() }. private

    fun refresh() { launch(parent = job) { showRepository.updateShow(showId) // update view state }. }. override onCleared() { super.onCleared() job.cancel() } } .. Explicit
  36. launch(parent = job) { } suspend fun updateShow(showId: Long) {


    val local = async {
 localShowStore.getShow(showId)
 }}
 val remote = async { remoteSource.getShow(showId) }} val merged = mergeShow(local.await(), remote.await()) localShowStore.saveShow(merged) }.
  37. launch(parent = job) { } suspend fun updateShow(showId: Long) {


    val local = async {
 localShowStore.getShow(showId)
 }}
 val remote = async { remoteSource.getShow(showId) }} val merged = mergeShow(local.await(), remote.await()) localShowStore.saveShow(merged) }. New coroutines No parent Suspended job.cancel() Cancelled
  38. init { refresh() }. private fun refresh() { launch(parent =

    job) { showRepository.updateShow(showId) // update view state }. }. override onCleared() { Deprecated in v0.26 async too
  39. New! v0.26 CoroutineScope async & launch become instance methods Allows

    objects to provide scope for coroutines You provide a default context
  40. class ShowDetailsViewModel: ViewModel() {• val showRepository: ShowRepository = // ...

    val state: LiveData<ViewState> private val job = Job() init { refresh() }. private fun refresh() {• launch(parent = job) {• showRepository.updateShow(showId) // update view state }. }. override onCleared() {
  41. class ShowDetailsViewModel: ViewModel(), CoroutineScope {• val showRepository: ShowRepository = //

    ... val state: LiveData<ViewState> private val job = Job() init { refresh() }. private fun refresh() {• launch(parent = job) {• showRepository.updateShow(showId) // update view state }. }. override onCleared() {
  42. class ShowDetailsViewModel: ViewModel(), CoroutineScope {• val showRepository: ShowRepository = //

    ... val state: LiveData<ViewState> private val job = Job() override val coroutineContext: CoroutineContext get() = job + Dispatchers.Main init { refresh() }. private fun refresh() {• launch(parent = job) {• showRepository.updateShow(showId) // update view state }.
  43. override val coroutineContext: CoroutineContext get() = job + Dispatchers.Main init

    { refresh() }. private fun refresh() {• launch(parent = job) {• showRepository.updateShow(showId) // update view state }. }. override onCleared() { super.onCleared() job.cancel() }. }.
  44. override val coroutineContext: CoroutineContext get() = job + Dispatchers.Main init

    { refresh() }. private fun refresh() {• launch {• showRepository.updateShow(showId) // update view state }. }. override onCleared() {• super.onCleared() job.cancel() }. }.
  45. override val coroutineContext: CoroutineContext get() = job + Dispatchers.Main init

    { refresh() }. private fun refresh() {• this.launch(context = coroutineContext) {• showRepository.updateShow(showId) // update view state }. }. override onCleared() {• super.onCleared() job.cancel() }. }.
  46. open class ScopedViewModel : ViewModel(), CoroutineScope { private val job

    = Job() override val coroutineContext: CoroutineContext get() = job + Dispatchers.Main override fun onCleared() { super.onCleared() job.cancel() } }
  47. launch { } suspend fun updateShow(showId: Long) {}
 val local

    = async {}
 localShowStore.getShow(showId)
 }}
 val remote = async {} remoteSource.getShow(showId) }} val merged = mergeShow(local.await(), remote.await()) localShowStore.saveShow(merged) }.
  48. launch { } suspend fun updateShow(showId: Long) = coroutineScope {}


    val local = async {}
 localShowStore.getShow(showId)
 }}
 val remote = async {} remoteSource.getShow(showId) }} val merged = mergeShow(local.await(), remote.await()) localShowStore.saveShow(merged) }.
  49. launch { } suspend fun updateShow(showId: Long) = coroutineScope {}


    val local = async {}
 localShowStore.getShow(showId)
 }}
 val remote = async {} remoteSource.getShow(showId) }} val merged = mergeShow(local.await(), remote.await()) localShowStore.saveShow(merged) }. Child coroutines Suspended job.cancel() Cancelled Children are cancelled too
  50. open class ScopedFragment : Fragment(), CoroutineScope { private val job

    = Job() override val coroutineContext: CoroutineContext get() = job + Dispatchers.Main override fun onDestroy() { super.onDestroy() job.cancel() } }
  51. // In your Activity / Fragment val observer = MyObserver()

    getLifecycle().addObserver(observer) class MyObserver : DefaultLifecycleObserver {{ override fun onCreate(owner: LifecycleOwner) {{ // we’re created }- override fun onDestroy(owner: LifecycleOwner) {{ // we’re destroyed }) }{
  52. class MyObserver : DefaultLifecycleObserver {{ override fun onCreate(owner: LifecycleOwner) {{

    // we’re created }- override fun onDestroy(owner: LifecycleOwner) {{ // we’re destroyed }) }{ // In your Activity / Fragment val observer = MyObserver() getLifecycle().addObserver(observer)
  53. class LifecycleScope : DefaultLifecycleObserver {{ val job = Job(){ override

    fun onDestroy(owner: LifecycleOwner) {{ // we’re destroyed }) }{
  54. class LifecycleScope : DefaultLifecycleObserver {{ val job = Job() override

    fun onDestroy(owner: LifecycleOwner) { // we’re destroyed{ job.cancel() }) }{
  55. class LifecycleScope : DefaultLifecycleObserver, CoroutineScope {{ val job = Job()

    override fun onDestroy(owner: LifecycleOwner) { // we’re destroyed{ job.cancel() }) }{
  56. class LifecycleScope : DefaultLifecycleObserver, CoroutineScope {{ val job = Job()

    override val coroutineContext = job + Dispatchers.Main override fun onDestroy(owner: LifecycleOwner) { // we’re destroyed{ job.cancel() }) }{ class DetailsFragment : Fragment() { private val scope = LifecycleScope() init { lifecycle.addObserver(scope) }}
  57. class DetailsFragment : Fragment() { private val scope = LifecycleScope()

    init { lifecycle.addObserver(scope) }} override fun onCreateView( ...) { // ... }} }} // we’re destroyed{ job.cancel() }) }{
  58. class DetailsFragment : Fragment() { private val scope = LifecycleScope()

    init { lifecycle.addObserver(scope) }} override fun onCreateView( ...) { // ... }} }}
  59. class DetailsFragment : Fragment() { private val scope = LifecycleScope()

    init { lifecycle.addObserver(scope) }} override fun onStart() { scope.launch { // something async } } override fun onCreateView( ...) { // ... }} }}
  60. class DetailsFragment : Fragment() { private val scope = LifecycleScope()

    init { lifecycle.addObserver(scope) }} override fun onStart() { scope.launch { // something async } } override fun onCreateView( ...) { // ... }} }} Scoped to Fragment lifecycle
  61. Call a suspending function Check isActive if (job != null

    && !job.isActive) { throw job.getCancellationException() } Two ways to co-operate yield()
  62. Two ways to co-operate Call a suspending function Check isActive

    launch { files.forEach { file -> if (isActive) doSomethingWith(file) } }
  63. suspend fun updateShow(showId: Long) = coroutineScope {} val local =

    async {} localShowStore.getShow(showId) }} val remote = async {} remoteSource.getShow(showId) }} val merged = mergeShow(local.await(), remote.await()) localShowStore.saveShow(merged) }}
  64. suspend fun updateShow(showId: Long) = coroutineScope {} val local =

    async {} localShowStore.getShow(showId) }} val trakt = async {} remoteSource.getShow(showId) }} val merged = mergeShow(local.await(), remote.await()) localShowStore.saveShow(merged) // throws CrashyMcCrashfaceException() }}
  65. suspend fun updateShow(showId: Long) = coroutineScope {} val local =

    async {} localShowStore.getShow(showId) }} val trakt = async {} remoteSource.getShow(showId) }} val merged = mergeShow(local.await(), remote.await()) localShowStore.saveShow(merged) // throws CrashyMcCrashfaceException() }} Treated like an uncaught exception
  66. suspend fun updateShow(showId: Long) = coroutineScope {} val local =

    async {} localShowStore.getShow(showId) }} val remote = async {} remoteSource.getShow(showId) }} val merged = mergeShow(local.await(), remote.await()) localShowStore.saveShow(merged) }}
  67. suspend fun updateShow(showId: Long) = coroutineScope {} val local =

    async {} localShowStore.getShow(showId) // throws CrashyMcCrashfaceException() }} val remote = async {} remoteSource.getShow(showId) }} val merged = mergeShow(local.await(), remote.await())} localShowStore.saveShow(merged)} }}
  68. suspend fun updateShow(showId: Long) = coroutineScope {} val local =

    async {} localShowStore.getShow(showId) // throws CrashyMcCrashfaceException() }} val remote = async {} remoteSource.getShow(showId) }} val merged = mergeShow(local.await(), remote.await())} localShowStore.saveShow(merged)} }} Thrown here
  69. suspend fun updateShow(showId: Long) = coroutineScope {} val local =

    async {} localShowStore.getShow(showId) // throws CrashyMcCrashfaceException() }} val remote = async {} remoteSource.getShow(showId) }} try {} val merged = mergeShow(local.await(), remote.await()) localShowStore.saveShow(merged) } catch (t: Throwable) {} // Handle the exception }} }}
  70. New! v0.25 IO Designed for blocking I/O tasks Uses 64

    <= cpuCount threads launch(IO) {{ // network things }}
  71. New! v0.25 async { val local = withContext(IO) { fileStore.loadImage(

    ...) }} // No thread context switch! return processImage(local) }} Shares threads IO dispatcher uses DefaultScheduler IO
  72. async { val local = withContext(IO) { fileStore.loadImage( ...) }}

    // No thread context switch! return processImage(local) }} Shares threads IO dispatcher uses DefaultScheduler New! v0.25 IO
  73. async { val local = withContext(IO) { fileStore.loadImage(...) }} //

    No thread context switch! return processImage(local) }} Shares threads IO dispatcher uses DefaultScheduler New! v0.25 IO
  74. interface RemoteService {} @GET("/trendingshows") suspend fun trendingShows(): List<Show> }} val

    show = withContext(dispatchers.io) {} service.trendingShows().await() }}
  75. interface RemoteService {} @GET("/trendingshows") suspend fun trendingShows(): List<Show> }} val

    show = withContext(dispatchers.io) {} service.trendingShows().await() }} Coming to Retrofit soon™
  76. interface RemoteService {} @GET("/trendingshows") suspend fun trendingShows(): List<Show> }} val

    show = withContext(dispatchers.io) {} service.trendingShows().await() }}
  77. interface RemoteService {} @GET("/trendingshows") suspend fun trendingShows(): List<Show> }} val

    show = withContext(dispatchers.io) {} service.trendingShows() }}
  78. val channel = Channel<Bitmap>() launch { // First coroutine to

    load images files.forEach { file -> val bitmap = loadBitmap(file) channel.send(bitmap) } // We're done with the channel channel.close() } launch { // Second coroutine to process them
  79. val channel = Channel<Bitmap>() launch { // First coroutine to

    load images files.forEach { file -> val bitmap = loadBitmap(file) channel.send(bitmap) } // We're done with the channel channel.close() } launch { // Second coroutine to process them
  80. val channel = Channel<Bitmap>() launch { // First coroutine to

    load images files.forEach { file -> val bitmap = loadBitmap(file) channel.send(bitmap) } // We're done with the channel channel.close() } launch { // Second coroutine to process them
  81. val channel = Channel<Bitmap>() launch { // First coroutine to

    load images files.forEach { file -> val bitmap = loadBitmap(file) channel.send(bitmap) } // We're done with the channel channel.close() } launch { // Second coroutine to process them
  82. files.forEach { file -> val bitmap = loadBitmap(file) channel.send(bitmap) }

    // We're done with the channel channel.close() } launch { // Second coroutine to process them channel.consumeEach { bitmap -> processBitmap(bitmap) } }
  83. all any associate associateBy associateByTo associateTo consume consumeEach consumeEachIndexed consumes

    count distinct distinctBy drop dropWhile elementAt elementAtOrElse elementAtOrNull filter filterIndexed filterIndexedTo filterNot filterNotNull filterNotNullTo filterNotTo filterTo find findLast first firstOrNull flatMap fold foldIndexed groupBy groupByTo indexOf indexOfFirst indexOfLast last l a s t I n d e x O f l a s t O r N u l l m a p m a p I n d e x e d mapIndexedNotNull mapIndexedNotNullTo mapIndexedTo mapNotNull mapNotNullTo mapTo maxBy maxWith minBy minWith none partition reduce reduceIndexed requireNoNulls single singleOrNull sumBy sumByDouble take takeWhile toChannel toCollection toList toMap toMutableList toMutableSet toSet withIndex zip Channels has 73 built-in operators
  84. aggregate all amb ambWith and apply asObservable asyncAction asyncFunc averageDouble

    length limit longCount map mapMany materialize max maxBy merge mergeDelayError Compare that to RxJava 230ish
  85. Operators are easier to write in coroutines fun <T, R>

    Publisher<T>.map( context: CoroutineContext, mapper: (T) -> R ) = GlobalScope.publish(context) { consumeEach { send(mapper(it)) } }
  86. Get last known location FusedLocationProviderClient client.getLastLocation().addOnCompleteListener { task -> //

    where am i? val location = task.result } Location In Google Play Services
  87. suspendCoroutine { ... } Given a continuation to later resume

    Will run the lambda and then immediately suspend How you pass the result back
  88. Get last known location suspend fun getLastLocation(): Location { return

    suspendCoroutine { continuation -> }} }} // TODO
  89. Get last known location suspend fun getLastLocation(): Location { return

    suspendCoroutine { continuation -> locationClient.lastLocation.addOnCompleteListener { task -> continuation.resume(task.result) }} }} }}
  90. Get last known location suspend fun getLastLocation(): Location { return

    suspendCoroutine { continuation -> locationClient.lastLocation.addOnCompleteListener { task -> continuation.resume(task.result) }} }} }} Resumes coroutine with result
  91. Get last known location suspend fun getLastLocation(): Location { return

    suspendCoroutine { continuation -> locationClient.lastLocation.addOnCompleteListener { task -> if (task.isSuccessful) { continuation.resume(task.result) } else { continuation.resumeWithException(task.exception !!) } }} }} }}
  92. fun removeLocationUpdates( callback: LocationCallback ): Task<Void> Remove the callback when

    done request: LocationRequest, callback: LocationCallback, looper: Looper ): Task<Void>
  93. fun observeLocation( locationRequest: LocationRequest ): ReceiveChannel<Location> {} val channel =

    Channel<Location>() val callback = object : LocationCallback() { override fun onLocationResult(result: LocationResult) { channel.sendBlocking(result.getLastLocation()) }} }} // TODO request location updates return channel }}
  94. fun observeLocation( locationRequest: LocationRequest ): ReceiveChannel<Location> { val channel =

    Channel<Location>() val callback = object : LocationCallback() { override fun onLocationResult(result: LocationResult) { channel.sendBlocking(result.getLastLocation()) } } // TODO request location updates return channel }
  95. fun observeLocation( locationRequest: LocationRequest ): ReceiveChannel<Location> { val channel =

    Channel<Location>() val callback = object : LocationCallback() { override fun onLocationResult(result: LocationResult) { channel.sendBlocking(result.getLastLocation()) }} }} // TODO request location updates return channel }}
  96. fun observeLocation( locationRequest: LocationRequest ): ReceiveChannel<Location> { val channel =

    Channel<Location>() val callback = object : LocationCallback() { override fun onLocationResult(result: LocationResult) { channel.sendBlocking(result.getLastLocation()) }} }} return channel }}
  97. fun observeLocation( locationRequest: LocationRequest ): ReceiveChannel<Location> { val channel =

    Channel<Location>() val callback = object : LocationCallback() { override fun onLocationResult(result: LocationResult) { channel.sendBlocking(result.getLastLocation()) }} }} // Start location updates, sending locations to the // channel locationClient.requestLocationUpdates( locationRequest, callback, Looper.myLooper() )}
  98. val channel = Channel<Location>() val callback = object : LocationCallback()

    { override fun onLocationResult(result: LocationResult) { channel.sendBlocking(result.getLastLocation()) }} }} // Start location updates, sending locations to the // channel locationClient.requestLocationUpdates( locationRequest, callback, Looper.myLooper() )} return channel }}
  99. }} // Start location updates, sending locations to the //

    channel locationClient.requestLocationUpdates( locationRequest, callback, Looper.myLooper() )} // Remove the location callback when the channel // is closed channel.invokeOnClose { locationClient.removeLocationUpdates(callback) } return channel }}
  100. lateinit var locationChannel: ReceiveChannel<Location> override fun onStart() { val locationRequest

    = LocationRequest() .setSmallestDisplacement(100f) // 100m locationChannel = observeLocation(locationRequest) launch(Main) { locationChannel.consumeEach { // We have a new location! updateUiForLocation(it) } } } override fun onStop() { // Stop observing the location and
  101. lateinit var locationChannel: ReceiveChannel<Location> override fun onStart() { val locationRequest

    = LocationRequest() .setSmallestDisplacement(100f) // 100m locationChannel = observeLocation(locationRequest) launch(Main) { locationChannel.consumeEach { // We have a new location! updateUiForLocation(it) } } } override fun onStop() { // Stop observing the location and
  102. lateinit var locationChannel: ReceiveChannel<Location> override fun onStart() { val locationRequest

    = LocationRequest() .setSmallestDisplacement(100f) // 100m locationChannel = observeLocation(locationRequest) launch(Main) { locationChannel.consumeEach { // We have a new location! updateUiForLocation(it) } } } override fun onStop() { // Stop observing the location and
  103. lateinit var locationChannel: ReceiveChannel<Location> override fun onStart() { val locationRequest

    = LocationRequest() .setSmallestDisplacement(100f) // 100m locationChannel = observeLocation(locationRequest) launch(Main) { locationChannel.consumeEach { // We have a new location! updateUiForLocation(it) }} }} }} override fun onStop() { // Stop observing the location and
  104. .setSmallestDisplacement(100f) // 100m locationChannel = observeLocation(locationRequest) launch(Main) { locationChannel.consumeEach {

    // We have a new location! updateUiForLocation(it) }} }} }} override fun onStop() { // Stop observing the location and // close the channel locationChannel.cancel() }}
  105. .setSmallestDisplacement(100f) // 100m locationChannel = observeLocation(locationRequest) launch(Main) { locationChannel.consumeEach {

    // We have a new location! updateUiForLocation(it) }} }} }} override fun onStop() { // Stop observing the location and // close the channel locationChannel.cancel() }}
  106. Exploring Coroutines in Kotlin Kotlin Coroutines in Practice Coroutines and

    Reactive Programming - friends or foes? Effectenbeurzaal Tomorrow, 13:00 Effectenbeurzaal Tomorrow, 10:15 Granbeurszaal Tomorrow, 15:15