Reputation: 656
I am getting my flow list like that:
val list = repository.someFlowList()
Sometimes I do this like that:
fun list() = repository.someFlowList()
In the Google Codelab it's used like that:
val list: Flow<List<Something>>
get() = repository.someFlowList()
I know what properties, getters, setters, functions are. But I want to know only one thing: is there any difference in terms of efficency, performance, etc? If it matters, I use that flow as livedata(just using asLiveData() method) in activity.
Upvotes: 5
Views: 1809
Reputation: 2547
TL;DR: You should prefer a field-backed property over a computed property, but the outcome is the same. If it's a hot flow, in this case you should use a field-backed property to avoid unexpected behavior.
The Flow API is sort of declarative, meaning that when you create a Flow you are just defining what it does, this is why it's said to be a cold flow. The computation you define only runs when the flow is collected. This means that you can call collect
on the same instance as many times as you need, the computation it defines will run from scratch each time. The only underlying impact of creating a new Flow instance each time as a computed property, or as result of a function, is that you allocate more instances of the same Flow definition.
Check this trivial example:
val flow = flow {
emit(0)
emit(1)
}
runBlocking {
val f1 = flow
f1.collect {
println("Flow ID: ${f1.hashCode()} - emits $it")
}
val f2 = flow
f2.collect {
println("Flow ID: ${f2.hashCode()} - emits $it")
}
}
This will print:
Flow ID: 608188624 - emits 0
Flow ID: 608188624 - emits 1
Flow ID: 608188624 - emits 0
Flow ID: 608188624 - emits 1
You see that the same Flow instance, when collected, will run the Flow emission, each time you collect it.
If you change the assignment with a getter (val flow get() = flow {...}
), the output is:
Flow ID: 608188624 - emits 0
Flow ID: 608188624 - emits 1
Flow ID: 511833308 - emits 0
Flow ID: 511833308 - emits 1
See that the outcome is the same, the difference is that now you have 2 Flow instances.
When the Flow is hot, that is, it has values even before a collector starts collecting, then the story is different. A StateFlow is a typical hot Flow. Check this out:
val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
val flow = MutableStateFlow(0)
val job1 = scope.launch {
val f = flow
f.value = 1 // <-- note this
f.collect {
println("A: $it")
}
}
val job2 = scope.launch {
val f = flow
f.collect {
println("B: $it")
}
}
runBlocking {
job1.cancelAndJoin()
job2.cancelAndJoin()
}
The output is:
A: 1
B: 1
Both Flow collections receive the latest value of the single hot Flow instance.
If you change to val flow get() = MutableStateFlow(0)
you get:
A: 1
B: 0
This time we create a different instance of the StateFlow, so the second collector misses the value change that we did before on the first Flow instance. This is a problem if the property is exposed as public, since the implementation of the property, either field-backed or computed, should not be relevant to the caller. Eventually this may end up creating an unexpected behaviour - a bug.
Upvotes: 6
Reputation: 91
In Kotlin there is a concept of Backing Fields, these fields are only used when needed as part of a property to hold its value in memory.
Using a getter function get() = repository.someFlowList()
the body is evaluated every time the property is accessed since there is no backing field assigned to it. While in case of val list = repository.someFlowList()
value is evaluated during initialization and saved in a backing field. Kotlin documents on Getters & Setters explains it too.
Upvotes: 2