Search code examples
androidkotlinandroid-jetpack-composeandroid-room

View not updating when adding new data from Room database in kotlin and compose


So I am trying to build out a watch app for Android using kotlin, jetpack, and compose with a room database. I can see that the data is being retrieved and my observer is being called and adding data to my mutableList, but the UI isn't updating and I get an empty chart.

I'm still pretty new to kotlin, even newer to compose, and first time I am trying to use room, so this is all a little confusing and I could use a hand please.

I'll omit the dao, repo, and viewmodel code because it's all pretty boilerplate stuff and try to give you the parts of code that would be relevant, but I will gladly add more code samples of what I have if it's needed. Just trying to keep this short.

my view code...

@Composable
fun ChartsView(mode: TimeViewModel) {
  val dayFormat = SimpleDateFormat("EEEE", Locale.US)
  val date = LocalDateTime.now().minusDays(6)
  val lastWeek = Date(date.toEpochSecond(ZoneOffset.UTC))
  var barData = mutableListOf<BarData>()
  model.getFrom(lastWeek.time).observe(LocalLifecycleOwner.current) { times ->
    Log.d("CHARTS", "Loading ${times.size} entries into barData")
    for(time in times) {
      val eventDate = Utils.storageFormatter.parts(time.dateString)!!
      val day = dayFormat.format(eventDate)
      barData.add(BarData(day, time.time.toFloat())
    }
  }

  Column(
    modifier = Modifer.fillMaxSize().padding(start = 18.dp, end = 18.dp),
    horizontalAlignment = Alignment.CenterHorizontally,
    verticalArrangement = Arrangement.Top
  ) {
    Spacer(Modifier.height(28.dp))
    //...some code to display information
    Log.d("CHARTS", "INSIDE VIEW: $barData")
    Log.d("CAHRTS", "${barData.size}")
    if(barData.isEmpty()) {
      Log.d("CHARTS", "barData.size was 0, adding blank entry")
      barData.add(BarData("?", 0f))
    }
    //...my chart using bar data.
  }
}

In case you need it, from the dao it's returning live data....

@Query("SELECT * FROM times where timestamp > :timestamp")
fun getFrom(timestamp: Long): LiveData<List<Times>>

In the console I can see it's getting the data from the room database and adding the entries, but the count in the view is always 0. Console...

D/CHARTS: loading 1 entries into barData
D/CHARTS: INSIDE VIEW: []
D/CHARTS: 0
D/CHARTS: bardata.size was 0, adding a blank entry

Based on the first comment I got I changed the code to...

val barData = remember {
  mutableStateListOf<BarData>()
}
...
model.getFrom(lastWeekDate.time).observe(LocalLifecycleOwner.current) { times ->
  for(time in times) {
    val eventDate = Utils.storageFormatter.parse(time.dateString)!!
    val day = dayFormat.format(eventDate)
    barData.add(BarData(day, time.time.toFloat())
  }
}

However that puts it into an infinite loop where, by the time I hit the stop button on the emulator, I had this in my console...

D/CHARTS: INSIDE VIEW: androidx.compose.runtime.snapshots.SnapshotStateList@f4e8df2
D/CHARTS: 229
D/CHARTS: INSIDE VIEW, with Data: androidx.compose.runtime.snapshots.SnapshotStateList@ede5c43
D/CHARTS: 0
D/CHARTS: loading 1 entries into barData
D/CHARTS: INSIDE VIEW: androidx.compose.runtime.snapshots.SnapshotStateList@f4e8df2
D/CHARTS: 230
D/CHARTS: INSIDE VIEW, with Data: androidx.compose.runtime.snapshots.SnapshotStateList@ede5c43
D/CHARTS: 0
D/CHARTS: loading 1 entries into barData

Solution

  • In each recomposition you create new instace of list

     var barData = mutableListOf<BarData>()
    

    first thing you should do is to remember your list and instead of using mutableListOf use SnapshotStateList to trigger recomposition when you add or remove items from the list

    var barData = remember { mutableStateListOf<BarData>() } will solve the issue.

    And to prevent adding new observer on every recomposition which you add new value and trigger another recomposition causing your Composable to recompose use LaunchedEffect to do it only once or use with keys.

    LaunchedEffect(Unit) {
        model.getFrom(lastWeekDate.time).observe(LocalLifecycleOwner.current) { times ->
            for (time in times) {
                val eventDate = Utils.storageFormatter.parse(time.dateString)!!
                val day = dayFormat.format(eventDate)
                barData.add(BarData(day, time.time.toFloat())
            }
        }
    }