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

Kotlin Flow Application and Testing on Android

Ivan
September 29, 2020
420

Kotlin Flow Application and Testing on Android

Android Taipei, 2020/09/29

Ivan

September 29, 2020
Tweet

Transcript

  1. What is Flow? • A new stream processing API developed

    by JetBrains. • Flow ◦ Simple ◦ Kotlin language features support ◦ Flows are cold
  2. How to Use it? flow { emit(6) } .collect {

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

    -> names[5] } .catch { e -> println(e) } .onCompletion { println("Completed") } .collect { ch -> println(ch) }
  4. Flow + UI Events • We can consider 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 lifecycle automatically. • Makes code more

    readable. • Avoids 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. Scenarios • Some cases we can use flow binding… ◦

    To implement API search by TextWatcher. ◦ To detect gestures with onTouch events. ◦ Avoid quick double clicks on buttons.
  8. Flow + Retrofit • Wrap Retrofit calls in call adapter

    factory. • Use libs. Retrofit.Builder() .baseUrl(BASE_DOMAIN) .client(okhttpClient) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(MyRetrofitFlowFactory.create()) .build()
  9. How to Test Flow? • Use Mocking Libraries ◦ Mockito

    ◦ MockK • Flow Assert / Turbine
  10. Using Mocking Libs • Flow can be easily tested by

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

    Service { fun getUser(): User } class ApiService : Service { override fun getUser() = User("John", 1L) }
  12. 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) }
  13. Flow Assert / Turbine • It is originally from SqlDelight

    lib ◦ And it was published as Turbine. • It is an extension function of Flow • Makes expressions more specific ◦ expectItem() ◦ expectError() ◦ expectComplete() • Takes advantage of Channels API and Coroutines API
  14. 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
  15. Implementation interface FlowTurbine<T> { suspend fun expectItem(): T suspend fun

    expectComplete() suspend fun expectError(): Throwable } 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 { Reader.flowRead<RssStandardChannel>(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. Experience • From RxJava to Flow ◦ We can use

    a tool to transform stream types. • Apply flow to UI can actually be really convenient. ◦ View extensions (avoid quick clicks, text watchers, and so on) ◦ Use it with sealed classes. • Flow is simple.
  23. CREDITS: This presentation template was created by Slidesgo, including icons

    by Flaticon, and infographics & images by Freepik. Thanks KtRssReader!