Kotlin Compose: Data not display async in a lazy column

117 views Asked by At

i have a issue with a lazy column and a database. When i load my activity with a lazy column, the database is call to get list of tournaments. But the list is not display in the lazy column. The list is display the moment after i click to change of page/activity although it makes sense.

DAO:

@Dao
interface TournamentDao {
    @Insert(onConflict = OnConflictStrategy.IGNORE)
    suspend fun insert(tournament: TournamentEntity)

    @Update
    suspend fun update(tournament: TournamentEntity)

    @Delete
    suspend fun delete(tournament: TournamentEntity)

    @Query("SELECT * from tournaments WHERE id = :id")
    fun getItem(id: Int): Flow<TournamentEntity>

    @Query("SELECT * from tournaments ORDER BY id ASC")
    fun getAllItems(): Flow<List<TournamentEntity>>

}

REPOSITORY:

class OfflineTournamentRepository(private val tournamentDao: TournamentDao):TournamentRepository {
    override fun getAllStream(): Flow<List<TournamentEntity>> = tournamentDao.getAllItems()

    override fun getStream(id: Int): Flow<TournamentEntity?> = tournamentDao.getItem(id)

    override suspend fun insert(tournament: TournamentEntity) = tournamentDao.insert(tournament)

    override suspend fun delete(tournament: TournamentEntity) = tournamentDao.delete(tournament)

    override suspend fun update(tournament: TournamentEntity) = tournamentDao.update(tournament)
}

VIEW MODEL:

class TournamentListViewModel(private val tournamentRepository: TournamentRepository):ViewModel() {
    private val _uiState = MutableStateFlow(TournamentListState())
    var uiState: StateFlow<TournamentListState> = _uiState.asStateFlow()
        private set

    fun updateUiState(tournaments: List<Tournament>) {
        _uiState.value.tournaments=tournaments
    }

    private suspend fun getAll(){
        _uiState.value.tournaments=tournamentRepository.getAllStream().first().map { it.toModel() }
    }

    init{
        viewModelScope.launch {
            getAll()
        }

    }

}

STATE:

class TournamentListState(
    var tournaments: List<Tournament> = listOf(),
) {
}

LAZY COLUMN OF ACTIVITY:

LazyColumn(
            modifier = Modifier
                .fillMaxWidth()
                .fillMaxHeight(0.91f)
                .padding(0.dp, 10.dp, 0.dp, 0.dp),
            verticalArrangement = Arrangement.spacedBy(10.dp)
        ) {
            items(tournamentListViewModel.uiState.value.tournaments){ tournament: Tournament ->
                TournamentItemCard(
                    tournament = tournament,
                    modifier = Modifier
                        .fillParentMaxWidth()
                        .fillParentMaxHeight(0.1f)
                )
            }
        }

Please what i have to change for the list display when data arrives?

1

There are 1 answers

0
Ahsan Ullah Rasel On BEST ANSWER

I think the reason might be you are not observing state. Which in turn doesn't know the inner property (tournaments) changes of your uiState variable. Compose needs state to recompose. So, as it doesn't know your state change and it wasn't recomposing to show the data. When the state changes (e.g. after click) the it recomposes with the already loaded data thus showing the list. The resolution is that, you observe your state change in your compose side and update the state in your StateHolder (ViewModel in your case) whenever you get new data.
The below way will show your list in UI whenever it's available in ViewModel side with some minor optimization.
Change your TournamentListViewModel to:

class TournamentListViewModel(private val tournamentRepository: TournamentRepository):ViewModel() {
    private val _uiState = MutableStateFlow(TournamentListState())
    val uiState: StateFlow<TournamentListState> = _uiState.asStateFlow() // <- Changed to val as it doesn't need to be Mutable and being accessed outside StateHolder

    fun updateUiState(tournaments: List<Tournament>) {
        _uiState.value.tournaments = tournaments
    }

    private suspend fun getAll() {
        _uiState.value = _uiState.value.copy(tournaments = tournamentRepository.getAllStream().first().map { it.toModel() } // <- Changed to updating state with new value instead of inner property so that your UI is notified of data changes
    }

    init {
        viewModelScope.launch {
            getAll()
        }
    }
}

Changing your TournamentListState class into data class. Also add state observing property in your UI side (LazyColumn's place in your case) like below.

val uiState = tournamentListViewModel.uiState.collectAsState()
LazyColumn(
    modifier = Modifier
        .fillMaxWidth()
        .fillMaxHeight(0.91f)
        .padding(0.dp, 10.dp, 0.dp, 0.dp),
    verticalArrangement = Arrangement.spacedBy(10.dp)
) {
    items(uiState.value.tournaments){ tournament: Tournament ->
        TournamentItemCard(
            tournament = tournament,
            modifier = Modifier
                .fillParentMaxWidth()
                .fillParentMaxHeight(0.1f)
        )
    }
}