Is this a Kotlin compiler bug? Removing unnecessary `val` qualifier breaks this class

Viewed 33

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)

ConfigurationRepository.kt (The repository in question)

0 Answers
Related