Thread
#compose
    t

    Tim Malseed

    1 year ago
    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.
    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:
    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:
    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:
    sealed class ScreenState {
        object Loading : ScreenState()
        data class Ready(val routine: Routine) : ScreenState()
    }
    The ShareIn attempt:
    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:
    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..?