I have a class OverlaySensorViewModel that exposes a Flow<Boolean>. This Flow object comes from an object that it receives in a constructor:
class OverlaySensorViewModel(val repository: Repository) {
val showTimer = repository.showTimer
}
Other classes then reference showTimer as viewModel.showTimer.
The repository parameter is not being used outside of initialization, so it technically doesn't need to be declared as a val.
And IntelliJ rightly points this out:
However... removing the val qualifier breaks the class: Subscriptions to OverlaySensorViewModel.showTimer will no longer work.
In fact, to isolate the problem I tried moving all subscriptions to the class itself, and even in the init block they stop working:
class OverlaySensorViewModel{
//...
init {
CoroutineScope(Dispatchers.IO).launch {
showTimer.collect{
print("Updated $it") // Stops working if `val` is removed
}
}
}
Is this some sort of deep JVM initialization order quirk that I'm stumbling across? Or some Kotlin Flow inconsistency?
Repository has an implementation along the lines of:
class Repository{
private val mutableShowTimer = MutableStateFlow(true)
val showTimer = mutableShowTimer.asSharedFlow()
}
Could there be some sort of initialization order issue with the .asSharedFlow() call? Even so, I really don't understand how adding or removing val would change anything, especially with IntelliJ agreeing I didn't accidentally use it somewhere else
To deepen the mystery, I have another class that follows an almost identical pattern.
This class references repository outside of initialization, so as a test I captured the value of repository in a closure and removed val
class ConfigurationViewModel(repository : Repository) {
val repositoryCaptor = { repostiory } // Needed so that val can be removed
val showTimer = repostitory.showTimer
// ...
fun setShowTimer(value : Boolean){
repositoryCaptor().setShowTimer(value)
}
}
Everything continues to work. As I would expect, there's no meaningful difference between capturing the value at initialization or using it later on.
I'm really reluctant to just leave this be because it's extremely non-obvious that there'd be any difference.
I was thinking that it could be an issue with garbage collection, but holding onto a reference of someObject.member should prevent someObject from being garbage collected right? I'd also expect an NPE if anything, but subscriptions seem to just silently fail.
Both classes are created in a similar manner, XXXViewModel(Repository()), so I'd expect a similar failure in both cases too
Edit: For posterity these are the full class definitions, and a tag where it the issue exists
OverlaySensorViewModel.kt (Having removed val at the linked line broke the class)
ConfigurationViewModel.kt (Works as expected despite identical usage)