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

Kotlin Flow Application and Testing on Android

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
Avatar for Ivan Ivan
September 29, 2020
450

Kotlin Flow Application and Testing on Android

Android Taipei, 2020/09/29

Avatar for Ivan

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!