2

Right now, my method of updating my jetpack compose UI on database update is like this:

My Room database holds Player instances (or whatever they're called). This is my PlayerDao:

@Dao
interface PlayerDao {
    @Query("SELECT * FROM player")
    fun getAll(): Flow<List<Player>>

    @Insert
    fun insert(player: Player)

    @Insert
    fun insertAll(vararg players: Player)

    @Delete
    fun delete(player: Player)

    @Query("DELETE FROM player WHERE uid = :uid")
    fun delete(uid: Int)

    @Query("UPDATE player SET name=:newName where uid=:uid")
    fun editName(uid: Int, newName: String)

}

And this is my Player Entity:

@Entity
data class Player(
    @PrimaryKey(autoGenerate = true) val uid: Int = 0,
    @ColumnInfo(name = "name") val name: String,
)

Lastly, this is my ViewModel:

class MainViewModel(application: Application) : AndroidViewModel(application) {


    private val db = AppDatabase.getDatabase(application)

    val playerNames = mutableStateListOf<MutableState<String>>()
    val playerIds = mutableStateListOf<MutableState<Int>>()

    init {
        CoroutineScope(Dispatchers.IO).launch {
            db.playerDao().getAll().collect {
                playerNames.clear()
                playerIds.clear()
                it.forEach { player ->
                    playerNames.add(mutableStateOf(player.name))
                    playerIds.add(mutableStateOf(player.uid))
                }
            }
        }
    }

    fun addPlayer(name: String) {
        CoroutineScope(Dispatchers.IO).launch {
            db.playerDao().insert(Player(name = name))
        }
    }

    fun editPlayer(uid: Int, newName: String) {
        CoroutineScope(Dispatchers.IO).launch {
            db.playerDao().editName(uid, newName)
        }
    }

}

As you can see, in my ViewHolder init block, I 'attach' a 'collector' (sorry for my lack of proper terminology) and basically whenever the database emits a new List<Player> from the Flow, I re-populate this playerNames list with new MutableStates of Strings and the playerIds list with MutableStates of Ints. I do this because then Jetpack Compose gets notified immediately when something changes. Is this really the only good way to go? What I'm trying to achieve is that whenever a change in the player table occurs, the list of players in the UI of the app gets updated immediately. And also, I would like to access the data about the players without always making new requests to the database. I would like to have a list of Players at my disposal at all times that I know is updated as soon as the database gets updated. How is this achieved in Android app production?

Dj Sushi
  • 313
  • 2
  • 14
  • instead of `mutableStateListOf>()`, the recommendation is `StateFlow>` via `.stateIn(viewModelScope)` collected as state in the Composable from the ViewModel – EpicPandaForce Jan 12 '22 at 19:24
  • `I would like to have a list of Players at my disposal at all times`. Are you referring to the `playerNames.clear()` call? – Arpit Shukla Jan 12 '22 at 19:32
  • EpicPandaForce thank you, I feel like that's exactly the way to go with `Flow`s and `State`s. – Dj Sushi Jan 13 '22 at 09:28
  • @Arpit Shukla I don't understand your question. Basically, I would like to have the players cached so that I don't have to access the database all the time. I would like to have them cached in some sort of `List`. – Dj Sushi Jan 13 '22 at 09:30

2 Answers2

0

you can instead use live data. for eg -

val playerNames:Livedata<ListOf<Player>> = db.playerDao.getAll().asliveData

then you can set an observer like -

viewModel.playerNames.observe(this.viewLifecycleOwner){
    //do stuff when value changes. the 'it' will be the changed list.
}

and if you have to have seperate lists, you could add a dao method for that and have two observers too. That might be way more efficient than having a single function and then seperating them into two different lists.

  • 1
    Thank you for you answer. Is using `LiveData` the recommended way though? With Jetpack Compose, I feel like I should be working with `State`s and `Flow`s and stuff. Maybe I'm wrong though. But I know that `LiveData` is used even if you don't use Jetpack Compose. – Dj Sushi Jan 13 '22 at 09:26
  • I recently undertook an android training programme through google. This was the way they recommended. So.... anyways, if you want the link to the programme, here it is - https://developer.android.com/courses/android-basics-kotlin/course – Vaishnav Kanhirathingal Jan 13 '22 at 12:18
0

First of all, place a LiveData inside your data layer (usually ViewModel) like this

val playerNamesLiveData: LiveData<List<Player>> 
    get() = playerNamesMutableLiveData
private val playerNamesMutableLiveData = MutableLiveData<List<Player>>

So, now you can put your list of players to an observable place by using playerNamesLiveData.postValue(...).

The next step is to create an observer in your UI layer(fragment). The observer determines whether the information is posted to LiveData object and reacts the way you describe it.

private fun observeData() {
    viewModel.playerNamesLiveData.observe(
        viewLifecycleOwner,
        { // action you want your UI to perform }
    )
}

And the last step is to call the observeData function before the actual data posting happens. I prefer doing this inside onViewCreated() callback.

  • Thank you very much for your answer. Unfortunately, I feel like using `LiveData` is a little obsolete these days. – Dj Sushi Jan 13 '22 at 11:33
  • In this case I would recommend you to read the Official Android Architecture article. LiveData and Flows are the lates Google modern data-observing patterns =) So, they are lightweight at the same time. RxJava would be to heavy for your purpose... Here's the "Expose UI" paragraph that advises to use LiveData/Flows https://developer.android.com/jetpack/guide/ui-layer – Shvedkov Ivan Jan 14 '22 at 07:02