Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Streams with Kotlin Coroutines

ArthurNagy
November 27, 2019

Streams with Kotlin Coroutines

Asynchronous operations are part of our challenges that we face daily. Sending, receiving or retrieving data has to be offloaded to another thread so that we don't freeze or crash our app.
Luckily with Kotlin Coroutines, this got a whole lot easier. We can model asynchronous data handling operations with the help of suspending functions.
But what if we are trying to represent a stream of data? What options do we have?
This talk will introduce the different options Kotlin Coroutines give us for representing streams. We'll take a look at Channels and Flows, how and when to use either of them, how you can build on top of them with custom operators and some patterns specific to Android development.

ArthurNagy

November 27, 2019
Tweet

More Decks by ArthurNagy

Other Decks in Programming

Transcript

  1. userRepository.getUser(object: Callback<User> { override fun onResult(user: User) { // Update

    UI } override fun onError(throwable: Throwable) { // Handle error } })
  2. object: Callback<User> { override fun onResult(user: User) { // Update

    UI } override fun onError(throwable: Throwable) { // Handle error } } userRepository.getUser(object: Callback<User> { override fun onResult(user: User) { // Update UI } override fun onError(throwable: Throwable) { // Handle error } })
  3. suspend keyword • used to mark a function • built

    into the Kotlin compiler • use other suspending functions from your function
  4. CoroutineScope • life span of a coroutine • holds the

    CoroutineContext • structured concurrency
  5. Coroutine context and dispatchers • contains coroutine Elements • Job:

    used to cancel a coroutine • Dispatcher: defines the thread in which the coroutine will run
  6. Coroutine builder • creates a new coroutine • extensions written

    on Scope • launch(), async(), (withContext())
  7. WHEN TO USE STREAMS ? 1. API’s which are based

    on streams: • Bluetooth device scanning/notification API • Socket based services, i.e: chat app, etc. 2. Architecture patterns like: MVVM, MVI, etc, • Unidirectional data flow • Reactive UI’s
  8. Channels • hot data sources • low-level communication primitives •

    send/receive, similar to BlockingQueue • synchronisation between coroutines
  9. Channels • subscription management • verbose and error prone •

    used as an implementation detail, never expose a channel, expose a Flow
  10. Flows • cold asynchronous data sources • runs in one

    coroutine, no-concurrency • based on suspending functions • simple design: safe, easy to learn and to use
  11. Flows interface Flow<out T> { suspend fun collect(collector: FlowCollector<T>) }

    interface FlowCollector<in T> { suspend fun emit(value: T) } fun <T> flow( block: suspend FlowCollector<T>.() -> Unit ): Flow<T>
  12. Flows interface Flow<out T> { suspend fun collect(collector: FlowCollector<T>) }

    interface FlowCollector<in T> { suspend fun emit(value: T) } fun <T> flow( block: suspend FlowCollector<T>.() -> Unit ): Flow<T>
  13. Flows interface Flow<out T> { suspend fun collect(collector: FlowCollector<T>) }

    interface FlowCollector<in T> { suspend fun emit(value: T) } fun <T> flow( block: suspend FlowCollector<T>.() -> Unit ): Flow<T>
  14. Flows interface Flow<out T> { suspend fun collect(collector: FlowCollector<T>) }

    interface FlowCollector<in T> { suspend fun emit(value: T) } fun <T> flow( block: suspend FlowCollector<T>.() -> Unit ): Flow<T>
  15. class Repository(val userService: UserService) { suspend fun getUser() { val

    user: User = userService.getUser() channel.send(user) } suspend fun saveUser(user: User) { val updatedUser = userService.updateUser(user) channel.send(updatedUser) } } private val channel = BroadcastChannel<User>() fun getUserFlow(): Flow<User> = channel.asFlow()
  16. class Repository(val userService: UserService) { suspend fun getUser() { val

    user: User = userService.getUser() channel.send(user) } suspend fun saveUser(user: User) { val updatedUser = userService.updateUser(user) channel.send(updatedUser) } } private val channel = BroadcastChannel<User>() fun getUserFlow(): Flow<User> = channel.asFlow()
  17. class Repository(val userService: UserService) { suspend fun getUser() { channel.send(user)

    } suspend fun saveUser(user: User) { channel.send(updatedUser) } } private val channel = BroadcastChannel<User>() fun getUserFlow(): Flow<User> = channel.asFlow() val user: User = userService.getUser() val updatedUser = userService.updateUser(user)
  18. class Repository(val userService: UserService) { suspend fun getUser() { channel.send(user)

    } suspend fun saveUser(user: User) { channel.send(updatedUser) } } private val channel = BroadcastChannel<User>() fun getUserFlow(): Flow<User> = channel.asFlow() val user: User = userService.getUser() val updatedUser = userService.updateUser(user)
  19. class Repository(val userService: UserService) { suspend fun getUser() { }

    suspend fun saveUser(user: User) { } } private val channel = BroadcastChannel<User>() fun getUserFlow(): Flow<User> = channel.asFlow() val user: User = userService.getUser() val updatedUser = userService.updateUser(user) channel.send(user) channel.send(updatedUser)
  20. class Repository(val userService: UserService) { suspend fun getUser() { }

    suspend fun saveUser(user: User) { } } private val channel = BroadcastChannel<User>() fun getUserFlow(): Flow<User> = channel.asFlow() val user: User = userService.getUser() val updatedUser = userService.updateUser(user) channel.send(user) channel.send(updatedUser)
  21. class ViewModel(private val repository: UserRepository) { val user = LiveData<UserUiModel>()

    ... viewModelScope.launch { repository } .getUserFlow() .map { user -> UserUiModel.from(user) } .collect { userUiModel -> // Use the data received user.value = userUiModel }
  22. class ViewModel(private val repository: UserRepository) { val user = LiveData<UserUiModel>()

    ... viewModelScope.launch { repository } .getUserFlow() .map { user -> UserUiModel.from(user) } .collect { userUiModel -> // Use the data received user.value = userUiModel }
  23. .getUserFlow() .map { user -> UserUiModel.from(user) } .collect { userUiModel

    -> // Use the data received user.value = userUiModel }
  24. fun updateUser( firstName: String, lastName: String, displayName: String ) {

    viewModelScope.launch { val updatedUser = currentUser.copy( firstName = firstName, lastName = lastName, displayName = displayName ) userRepository.saveUser(updatedUser) } } val updatedUser = currentUser.copy( firstName = firstName, lastName = lastName, displayName = displayName ) userRepository.saveUser(updatedUser)
  25. fun updateUser( firstName: String, lastName: String, displayName: String ) {

    viewModelScope.launch { val updatedUser = currentUser.copy( firstName = firstName, lastName = lastName, displayName = displayName ) userRepository.saveUser(updatedUser) } } val updatedUser = currentUser.copy( firstName = firstName, lastName = lastName, displayName = displayName ) userRepository.saveUser(updatedUser)