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")
}
}
- 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 afunction 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 areceiver
.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
- Type Safe Builders make all the magic disappear. When we put:
flow{ }
- 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>
- It takes one parameter called
block
, which itself is a suspending function. With the type of the function being,FlowCollector<T>.() → Unit
, which isfunction 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. Meaningflow{}
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") }
- 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 aSAM conversion
, which makes our code more precise. So the lambda here:
collect { value -> println("Collected $value") }
- 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 theflow{}
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")
}
- Which we could then pass to collect like this:
myFlow.collect(data)
- 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)