Search code examples
androidkotlinandroid-sqliteandroid-room

Sorting list with repository and ViewModel


First time adding an object (Deck) is added invisibly. It will only appear once the sorting method has been chosen from context menu. However, this has to be repeated each time for the screen to be updated.

The issue should lie within the repository as getAllDecks references allDecks. It is as if allDecks is not updating or not realising its .value is changing as allDecks.postValue() intakes the List<Deck> from database. This isn't a LiveData<List<Deck>>, it only does a one time thing. How to make repository reading constant updates from database?

I am trying to sort a list stored in the repository. My ViewModel has a List<obj> referencing items in repository. Sorting occurs when a user presses a context menu item. This action isn't working. The debugging tool showed repository method being called and things were reassigned. This should have worked as ViewModel was referencing the repository and MainActivity would automatically update if the list changed.

MainActivity context menu opens and reacts to onClick sorting changes. Thus it is called. I also know since delete and insert queries are working that MainActivity is listening to the ViewModel's changing list. What am I doing wrong? Also, how to debug database queries (when debugger transitions to viewing SQLite query it gets in a loop)?

Main Activity (abbreviated) :

         globalViewModel.sortBy(Sort.ALPHA_ASC) //Set default sorting

        //Listen for livedata changes in ViewModel. if there is, update recycler view
        globalViewModel.allDecks.observe(this, Observer { deck ->
            deck?.let { adapter.setDecks(deck) }
        })
 override fun onContextItemSelected(item: MenuItem): Boolean {
        when (item.itemId) {
            R.id.sort_by_alpha_asc -> { globalViewModel.sortBy(Sort.ALPHA_ASC) ; currentSort = Sort.ALPHA_ASC ; contextMenuText.setText(R.string.sort_by_alpha_asc) ; return true; }
            R.id.sort_by_alpha_desc -> { globalViewModel.sortBy(Sort.ALPHA_DES) ; currentSort = Sort.ALPHA_DES ; contextMenuText.setText(R.string.sort_by_alpha_des) ; return true; }
            R.id.sort_by_completed_hidden -> { globalViewModel.sortBy(Sort.NON_COM) ; currentSort = Sort.NON_COM ; contextMenuText.setText(R.string.sort_by_non_complete) ; return true; }
            R.id.sort_by_due_date -> { globalViewModel.sortBy(Sort.DUE_DATE) ; currentSort = Sort.DUE_DATE ; contextMenuText.setText(R.string.sort_by_due_date) ; return true; }
            else -> return super.onContextItemSelected(item)
        }
    }

View Model :

private val repository: DeckRepository
val allDecks: LiveData<List<Deck>>

init {
    val decksDao = FlashCardDB.getDatabase(application, viewModelScope).DeckDAO()
    repository = DeckRepository(deckDao = decksDao)
    allDecks = repository.getAllDecks()
}

fun sortBy(sortMethod: Sort) = viewModelScope.launch(Dispatchers.IO) {
    when (sortMethod) {
        Sort.ALPHA_ASC -> repository.sortBy(Sort.ALPHA_ASC)
        Sort.ALPHA_DES -> repository.sortBy(Sort.ALPHA_DES)
        Sort.NON_COM -> repository.sortBy(Sort.NON_COM)
        Sort.DUE_DATE -> repository.sortBy(Sort.DUE_DATE)
    }
}

DeckRepository :

private var allDecks = MutableLiveData<List<Deck>>() //instantiate object

    fun getAllDecks(): LiveData<List<Deck>> = allDecks //Repository handles livedata transmission. ViewModel references the actual Data.

    suspend fun sortBy(sortingMethod: Sort) {
        when (sortingMethod) {
            Sort.ALPHA_ASC -> allDecks.postValue(deckDao.getDecksSortedByAlphaAsc())
            Sort.ALPHA_DES -> allDecks.postValue(deckDao.getDecksSortedByAlphaDesc())
            Sort.NON_COM -> allDecks.postValue(deckDao.getDecksSortedByNonCompleted())
            Sort.DUE_DATE -> allDecks.postValue(deckDao.getDecksSortedByDueDate())
        }
    }

    suspend fun insert(deck: Deck) {
        deckDao.insert(deck)
    }

Database :

//Sorting
@Query("SELECT * from deck_table ORDER BY title ASC")
fun getDecksSortedByAlphaAsc(): List<Deck>

@Query("SELECT * from deck_table ORDER BY title DESC")
fun getDecksSortedByAlphaDesc(): List<Deck>

@Query("SELECT * from deck_table WHERE completed=1 ORDER BY title ASC")
fun getDecksSortedByNonCompleted(): List<Deck>

@Query("SELECT * from deck_table ORDER BY date ASC")
fun getDecksSortedByDueDate(): List<Deck>

//Modifying
@Insert(onConflict = OnConflictStrategy.ABORT)
suspend fun insert(deck: Deck)

Solution

  • Your activity isn't showing the changes in real time because your repository reassigns its LiveData. While your approach of having a single LiveData to be observed by the activity is correct, you should actually change only its value, not the reference if that makes sense.

    Here's an example:

    Repository

    private val allDecks = MutableLiveData<List<Deck>>()
    
    fun getAllDecks(): LiveData<List<Deck>> = allDecks
    
    fun sortBy(sortingMethod: Sort) {
        when (sortingMethod) {
            /* If you're handling your DB operations with coroutines, this function
             * should be suspendable and you should set the value to allDecks
             * with postValue
             */
            Sort.ALPHA_ASC -> allDecks.value = deckDao.getDecksSortedByAlphaAsc()
            Sort.ALPHA_DES -> allDecks.value = deckDao.getDecksSortedByAlphaDesc()
            Sort.NON_COM -> allDecks.value = deckDao.getDecksSortedByNonCompleted()
            Sort.DUE_DATE -> allDecks.value = deckDao.getDecksSortedByDueDate()
        }
    }
    

    Consequently, your DAO queries will no longer return LiveData, but the lists themselves:

    DAO

    @Query("SELECT * from deck_table ORDER BY title ASC")
    fun getDecksSortedByAlphaAsc(): List<Deck>
    
    
    @Query("SELECT * from deck_table ORDER BY title DESC")
    fun getDecksSortedByAlphaDesc(): List<Deck>
    
    @Query("SELECT * from deck_table WHERE completed=1 ORDER BY title ASC")
    fun getDecksSortedByNonCompleted(): List<Deck>
    
    @Query("SELECT * from deck_table ORDER BY date ASC")
    fun getDecksSortedByDueDate(): List<Deck>