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

Kotlin Flow Application and Testing on Android

Ivan
November 19, 2020

Kotlin Flow Application and Testing on Android

Kotlin Flow is an asynchronous stream library based on top of Kotlin Coroutines. There are different ways to use Flow and test them on Android. In this talk, we will discuss the applications of Flow and their pros & cons. For example, we can also apply with Flow on UI events and network requests. As you may know, unit testing on Android sometimes could be painful. We will go through Flow testing methods and examples to let you get familiar with them. In the last part of this talk, we will talk about the experience and pitfalls of using Flow in production.

Ivan

November 19, 2020
Tweet

More Decks by Ivan

Other Decks in Programming

Transcript

  1. How to Use it? flow { emit(6) } .collect {

    number -> println(number) }
  2. How to Use it? flowOf("Pikachu", "John", "Blabla") .map { names

    -> names[5] } .catch { e -> println(e) } .onCompletion { println("Completed") } .collect { ch -> println(ch) }
  3. Flow + Retrofit • Wrap Retrofit calls in call adapter

    factory. • Use libraries. Retrofit.Builder() .baseUrl(BASE_DOMAIN) .client(okhttpClient) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(MyRetrofitFlowFactory.create()) .build()
  4. Flow + UI Events • UI inputs as a flow

    of UI events. findViewById<Button>(R.id.button) .clicks() .onEach { Log.d("logTag", "clicked!") } .launchIn(lifecycleScope)
  5. Advantages • Binds to lifecycles automatically. • Makes code more

    readable. • Avoid callback hell & boilerplates. • You can write everything related to UI in Flow.
  6. Do it! • Write it by yourself ◦ Extension methods

    • Libs ◦ FlowBinding ◦ Corbind
  7. Use Cases • Avoid quick double clicks on buttons. findViewById<Button>(R.id.button)

    .clicks() .sample(2000L) .onEach { Log.d("ivan", "FAB click!") }.launchIn(lifecycleScope)
  8. Use Cases • To implement API search by TextWatcher. view.findViewById<EditText>(R.id.edit_text)

    .textChanges() .skipInitialValue() .sample(2000L) .map { it.toString() } .onEach { search(it) } .launchIn(lifecycleScope)
  9. Use Cases • To detect gestures with onTouch events. ◦

    Use sealed class as gesture types! sealed class Touch(open val x: Float, open val y: Float)
  10. Use Cases object Click: Touch(0F, 0F) data class Down(override val

    x: Float, override val y: Float): Touch(x, y) data class Move(override val x: Float, override val y: Float): Touch(x, y) data class Up(override val x: Float, override val y: Float): Touch(x, y) object Other: Touch(0F, 0F)
  11. Mocking Libs • Flow can be easily tested by using

    mocking libraries. • Use Mocking Libs ◦ Mockito-kotlin ◦ MockK
  12. Example data class User(val name: String, val id: Long) interface

    Service { fun getUser(): User } class ApiService : Service { override fun getUser() = User("John", 1L) }
  13. Using MockK to Test Flow @Test fun `Test getting an

    user info by MockK`() = runBlocking { } val fakeUser = User("Doe", 2L) val mockApiService = mockk<ApiService>(relaxed = true) every { mockApiService.getUser() } returns fakeUser val userRepository = UserRepository(mockApiService) userRepository.getUser().collect { assertEquals(it, fakeUser) }
  14. Flow Assert / Turbine • It is originally from SqlDelight

    lib ◦ It was published as Turbine. • An extension function of Flow • Makes expressions more specific ◦ expectItem() ◦ expectError() ◦ expectComplete() • Takes advantage of Channels API and Coroutines API. •
  15. The Extension suspend fun <T> Flow<T>.test( timeout: Duration = 1.seconds,

    validate: suspend FlowTurbine<T>.() -> Unit ) { ... } Source: https://github.com/cashapp/turbine/blob/trunk/src/commonMain/kotlin/app/cash/turbine/FlowTurbine.kt
  16. How does it Work? Flow Items Unlimited Buffer Channel FlowTurbine

    expectItem() expectError() expectComplete() Query the Channel Reference: https://proandroiddev.com/kotlin-flow-assert-ff45465c01c0
  17. Flow Assert / Turbine @Test fun `Test getting an user

    info by Flow Assert`() = runBlocking { } val fakeUser = User("Pikachu", 3L) val fakeApiService = provideFakeService(fakeUser) val userRepository = UserRepository(fakeApiService) userRepository.getUser().test { assertEquals(expectItem(), fakeUser) expectComplete() }
  18. Threading Operators • In RxJava, we declare start and modify

    chain below. observeSomething() .subscribeOn(io()) .observe(mainThread()) .subscribeOn(computation()) .subscribe { result -> println(result) }
  19. Threading Operators • In Flow, we have end declared and

    can modify chain above. CouroutineScope(Job() + Dispatchers.Main).launch { doSomething(url) .flowOn(Dispathcers.IO) .map { channel -> channel.getResult() } .flowOn(Dispatchers.Default) .collect { println(it) } }
  20. Retrofit + Flow • When you retry your API calls,

    you will get an exception. ◦ java.lang.IllegalStateException: Already executed.
  21. Retrofit + Flow • Simply copy the call before you

    execute it in your CallAdapterFactory. override fun adapt(call: Call<T>): Flow<Response<T>> = flow { val newCall = call.takeIf { !it.isExecuted } ?: call.clone() emit(newCall.awaitResponse()) }
  22. From RxJava to Flow • No need to do the

    whole migration all at once. ◦ The commits will be huge. ◦ Making incremental changes might be a good idea! • Use kotlinx-coroutines-rx2 ◦ Observables transformation ◦ Suspending extensions
  23. Observable to Flow runBlocking { Observable.just(1, 2, 3, 4, 5)

    .asFlow() .flowOn(Dispatchers.IO) .collect { } }
  24. CREDITS: This presentation template was created by Slidesgo, including icons

    by Flaticon, infographics & images by Freepik THANKS!