CodeNewbie Community 🌱

Cover image for Kotlin quicky: Under the hood of collecting flows in Android with Kotlin
Tristan
Tristan

Posted on

Kotlin quicky: Under the hood of collecting flows in Android with Kotlin

Introduction

  • Anytime I find something interesting about Kotlin I will post it here.

Resources

  • Main source. I would really recommend reading this before continuing.

  • Originally I wanted this to be a quick little tutorial about how we can collect from flows, but a lot goes on under the hood with flows and I am not sure if I fully understand but this is my best shot. If anyone has anything to add please comment below.

Flows

  • So before we start collection from a flow lets first create on:
override suspend fun testFunc()= flow {
        emit("LOADING")
        try {
            emit("SUCCESS")
        }catch (e:Exception){  
            emit("FAIL")
        }
    }

Enter fullscreen mode Exit fullscreen mode
  • The code block above basically leaves us with 2 questions:

1) what is the trailing lambda in flow doing ?
2) where the heck is emit() coming from?

1) what is the trailing lambda in flow doing ?

  • If you are unfamiliar with the term trailing lambda, check out the documentation, HERE.
  • Now lets move on and look at the documentation for flow{} we can see it takes a very unusual parameter, block: suspend FlowCollector<T>.() -> Unit. Generally this syntax is called a function type, which allows us to define the signature of the high order function. What's interesting is the .() -> Unit, this makes it a function type with a receiver.

  • A receiver is defined in the documentation as:
    Function types can optionally have an additional receiver type, which is specified before the dot in the notation: the type A.(B) -> C represents functions that can be called on a receiver object A with a parameter B and return a value C

  • But doesn't calling FlowCollector<T>.() -> Unit seem a little odd? We are calling a function on FlowCollection that has no parameters and returns nothing. It is odd until we learn about Type Safe Builders. .() -> Unit is used to create a type safe builder

Type safe builders

flow{   }

Enter fullscreen mode Exit fullscreen mode
  • It is really a function call that takes a lambda expression as an argument, which is:
public fun <T> flow(  
  block: suspend FlowCollector<T>.() → Unit
): Flow<T>
Enter fullscreen mode Exit fullscreen mode
  • It takes one parameter called block, which itself is a suspending function. With the type of the function being, FlowCollector<T>.() → Unit, which is function type with a receiver. Meaning, we need to pass an instance of type FlowController (the receiver)to the function(flow), which allows us to call members of that instance inside the function. Meaning flow{} gets handed an instance of FlowController and inside of flow we can call the FlowController's emit() method. But where is the instance of the FlowController coming from? The collect() method!

collect()

  • So an instance of FlowCollector is coming from collect(), how?. Well mainly because it says so in the blog post, HERE but we can do a little digging. Now collect() takes a collector of type FlowCollector and emits values to it. The normal call looks something like this:
myFlow.collect { value -> println("Collected $value") }

Enter fullscreen mode Exit fullscreen mode
  • The trailing lambda might look normal but its actually not. FlowCollector is considered a functional SAM(single abstract method) interface. With a functional SAM interface Kotlin allows us to do a SAM conversion, which makes our code more precise. So the lambda here:
collect { value -> println("Collected $value") }
Enter fullscreen mode Exit fullscreen mode
  • Is actually instantiating the FlowCollector interface and implementing the emit() method. value -> println("Collected $value") is actually us implementing the emit() method. Which is then passed to the flow{} builder.

Still confused?

  • Me too but we can make things a little better if we remove SAM conversion all together and get an implementation of the FlowController like this:
val data = object : FlowCollector<YourReturnDataType>{
    override suspend fun emit(value: YourReturnDataType) {
         println("Collected $value")
    }

Enter fullscreen mode Exit fullscreen mode
  • Which we could then pass to collect like this:
myFlow.collect(data)
Enter fullscreen mode Exit fullscreen mode
  • Again, still a little confusing but this does make it a little more clear that we are passing a value to the collect method. this value just happens to be an instance of FlowCollector and is used by flow{}

Conclusion

  • Thank you for taking the time out of your day to read this blog post of mine. If you have any questions or concerns please comment below or reach out to me on Twitter.

Top comments (0)