Search code examples
kotlindesign-patternsviewmodelcompose-desktop

How do I make ViewModels talk to each other in a Compose Desktop app?


I'm building a Jetpack Compose Deskop app that currently as a draft looks like this:

enter image description here

The 1 and 2 are two ViewModels:

// 1
class FilterViewModel {
    val query = mutableStateOf<String?>(null)
    val timeframe = mutableStateOf("Any")
}

// 2
class GridViewModel {
    val items = mutableListOf("foo", "bar", "baz")
}

I'd like the GridViewModel to update its items when the FilterViewModel changes. How do I do that in this situation? Does Jetpack Compose Deskop have anything to offer here or perhaps there are some more general solutions?


Solution

  • Consider defining a data layer, such as a repository combining a remote and/or local data sources. From the docs linked:

    While the UI layer contains UI-related state and UI logic, the data layer contains application data and business logic.

    The idea here is that yes you need local data like "Number" both in the query/search from FilterViewModel and also show the results with "Number" coming through in the items in the list. HOWEVER, with a data layer and data driven architecture they can both be pieces of live data in the ViewModels that observe the same single source of truth in the data layer. General hierarchy:

    Layered Architecture for Android

    Example of Hierarchy:

    • FilterViewModel reads and write <-> NumbersRepo, TimeframeRepo, QueryRepo
    • FilterViewModel writes -> CurrentItemsRepo
    • GridViewModel reads -> CurrentItemsRepo

    These repos and use remote data + Room and DataStore, or they can just be singletons/objects to get started. I would recommend looking at Flow and LiveData for this.

    Let's take a look in more detail what this looks like with this Room with a View codelab that uses Words instead of Numbers:

    class WordRepository(private val wordDao: WordDao) {
    
        // Observed Flow will notify the observer when the data has changed.
        val allWords: Flow<List<Word>> = wordDao.getAll()
     
        // The Dao can SELECT words that are filtered.
        val filteredWords: Flow<List<Word>> = wordDao.getFilteredWords()
    
        @WorkerThread
        suspend fun update(words: List<Word>) {
            wordDao.update(words)
        }
    }
    

    FilterViewModel:

    class FilterViewModel(private val repository: WordRepository) : ViewModel() {
    
        /**
         * This viewModel can change the words stored in Room. This means
         * if "filtered/current" is a parameter of word, then it can be
         * set to true or false and then updated here with this method
         */
        fun update(words: List<Word>) = viewModelScope.launch {
            repository.update(words)
        }
    
        // To read the filtered words to find what to set as no longer
        // filtered when the params change you can look here
        val filteredWords: LiveData<List<Word>> = repository.filteredWords.asLiveData()
    
        // To have the list of all words in the DB you can use this live 
        // From here you can you your other in puts to filter this 
        // when the user input changes.
        val allWords: LiveData<List<Word>> = repository.allWords.asLiveData()
    }
    

    GridViewModel:

    class GridViewModel(private val repository: WordRepository) : ViewModel() {
    
        // The filtered words come from the repo that get it from the dao.
        // This is read only, since we are just displaying them in the grid.
        val filteredWords: LiveData<List<Word>> = repository.filteredWords.asLiveData()
    }