Search code examples
androidkotlinandroid-jetpack-composeandroid-roomandroid-jetpack

Dynamic composable with Flow from Room Db not updating


I have an app where the users screen is made up of a list of objects from the DB. Those objects are updated in the background business logic code. The composable is filled correctly on first compose but is never updated. Breakpoints in the VMs collect are fired when upon initial composition, but not when the tables are updated as verified with App Inspection.

I've removed the extra fields and code that maps everything to the UI for brevity.

Any help would be appreciated!!!

The Entity

@Entity(primaryKeys = {"participantId", "studyId"})
public class Participant {
    //******
    //Required parameters
    //******
    @NonNull @ColumnInfo
    public String participantId;

    @NonNull @ColumnInfo
    public String studyId;


    //Other fields go here

    public Participant(@NonNull String participantId, @NonNull String studyId) {
        this.participantId = participantId;
        this.studyId = studyId;
    }
}

The Dao

@Dao
interface ParticipantDao {
    @Insert
     fun insertParticipant(participant: Participant): Long

    @Delete
     fun deleteParticipant(participant: Participant)


    @Query("SELECT * FROM Participant ORDER BY participantId ASC")
     fun getAllParticipants(): List<Participant>
}

The Db helper

interface DatabaseHelper {
    fun getAllSensors(): Flow<List<Sensor>>
    fun getAllParticipants(): Flow<List<Participant>>
}

class AppDatabaseHelper(private val appDatabase: AppDatabase) : DatabaseHelper{
    override fun getAllSensors(): Flow<List<Sensor>> = flow {
        emit(appDatabase.sensorDao().getAllSensors())
    }

    override fun getAllParticipants(): Flow<List<Participant>> = flow {
        emit(appDatabase.participantDao().getAllParticipants())
    }

}

The composable

@Composable
fun Users(navController: NavHostController, usersVM: UsersVM){
    val uiState by usersVM.uiState.collectAsState()
    usersVM.navController = navController

    Column(
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center,
        modifier = Modifier.fillMaxWidth()
    ) {
        if(uiState.participants.isNotEmpty()) {
            for(participant in uiState.participants) {
                //Display the users
            }
        }
    }
}

The VM

class UsersVM(private val appDB: AppDatabase): ViewModel() {
    private val _uiState = MutableStateFlow(UsersUiState())
    val uiState: StateFlow<UsersUiState> = _uiState.asStateFlow()
    var participants: List<Participant> = listOf()
    private var appDBHelper: AppDatabaseHelper = AppDatabaseHelper(appDB)

    init {
        viewModelScope.launch {
            appDBHelper.getAllParticipants()
                .flowOn(Dispatchers.IO)
                .catch { e ->
                    Timber.e("fillUsersList getAllParticipants ex: ${e.message}")
                }.collect {
                    participants = it
                    _uiState.value = UsersUiState(participants)
                }
        }
    }
}

data class UsersUiState(val participants: List<Participant> = mutableListOf(), val sensors: List<Sensor> = mutableListOf())

Solution

  • The Dao is returning a List and not a Flow.
    For observable data, you have to return Flow from Dao.

    Dao

    @Dao
    interface ParticipantDao {
        @Query("SELECT * FROM Participant ORDER BY participantId ASC")
         fun getAllParticipants(): Flow<List<Participant>>
    }
    
    class AppDatabaseHelper(private val appDatabase: AppDatabase) : DatabaseHelper{
        override fun getAllParticipants(): Flow<List<Participant>> = appDatabase.participantDao().getAllParticipants()
    }
    

    Reference
    https://developer.android.com/training/data-storage/room/async-queries#observable