I’ve got some data classes loaded from a database,...
# compose
t
I’ve got some data classes loaded from a database, which make their way to my compose function, via StateIn in a ViewModel. I wanted to adjust the equality predicate for these objects, to be based on ID, rather than comparing all fields. Since making this change, I’ve noticed that my layout isn’t recomposed when the database emits an updated model whose ID has not changed.
1
I believe that it’s the StateIn operator that is swallowing subsequent emissions.. probably because StateFlow conflates emissions based on equality. I’ve tried adjusting to use ShareIn rather than StateIn, but I’ve observed the same behaviour. I can’t quite understand where I’m going wrong here, and advice is appreciated. Ideally, I can retain the ID based equality. The StateFlow:
Copy code
val screenState = appRepository
    .getRoutines(listOf(routineId))
    .mapNotNull { routines -> routines.firstOrNull() }
    .map { routine -> ScreenState.Ready(routine) } // Seems to emit every time a field changes in Routine
    .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), ScreenState.Loading) // Seems to swallow subsequent emissions that are equal
The data class:
Copy code
data class Routine(
    val id: Long,
    val order: Int,
    val name: String,
    val exercises: List<RoutineExercise>
) {

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (javaClass != other?.javaClass) return false

        other as Routine

        if (id != other.id) return false

        return true
    }

    override fun hashCode(): Int {
        return id.hashCode()
    }
}
ScreenState:
Copy code
sealed class ScreenState {
    object Loading : ScreenState()
    data class Ready(val routine: Routine) : ScreenState()
}
The ShareIn attempt:
Copy code
val screenState = appRepository
    .getRoutines(listOf(routineId))
    .mapNotNull { routines -> routines.firstOrNull() }
    .map { routine -> ScreenState.Ready(routine) }
    .onStart { ScreenState.Loading }
    .shareIn(viewModelScope, SharingStarted.WhileSubscribed(), 1) // Seems to swallow subsequent emissions that are equal as per StateIn
I think maybe I need to customise the ShareIn
distinctBy
policy (conflation policy?) But I’m a little out of my league here.
Upon further investigation, it’s possible that
shareIn
is not conflating subsequent emissions, which would mean the lack of recomposition is a Compose problem, rather than a Coroutine problem.
The compose code looks like so:
Copy code
val screenState: ScreenState by viewModel.screenState.collectAsState(ScreenState.Loading)
It looks like subsequent emissions of `viewModel.screenStat`` do not trigger a recomposition, possibly because of the abovementioned equality predicate..?