'How to communicate betweeen viewmodels in jetpack compose
I have a home screen in my application that is basically content with a navigation bar
Each of the three selections of the navigation bar lead to a different screen, so the code looks like this:
@Composable
fun HomeScreen(state: HomeState, event: (HomeEvent) -> Unit) {
val navController = rememberNavController()
Scaffold(
modifier = Modifier.fillMaxSize(),
bottomBar = {
BottomNavigation { .... //add the three bottom navigation menu items
}
},
) {
NavHost(
navController = navController,
startDestination = "news",
) {
composable(route = "news") {
val newsVm: NewsViewModel = hiltViewModel()
NewsScreen(newsVm)
}
composable(route = "tickets") { NewTicketScreen() }
composable(route = "archive") { ArchiveScreen() }
}
}
}
this works correctly
this homescreen is used by the following composeable to actually draw the screen
@Composable
fun HomeScreen(
vm: HomeViewModel = hiltViewModel()
) {
val state = vm.state.value
HomeScreen(state, vm::process )
}
so HomeScreen
has its own viewmodel
in this example let us take the NewsScreen
which takes as an argument its own viewmodel
What this viewmodel will do is load news articles and show them to the user. But in order to not have to reload data every time the user changes the shown screen, what I would do before compose, is pass the homeViewModel as an argument to the newsViewModel.
Home would contain the data loaded up to now and expose it to its children.
and news would load data and save the loaded data in homeViewmodel
so it would go something like this
class HomeViewModel()..... { internal val newsArticles = mutableListOf() }
class NewsViewModel() ..... {
val parent :HomeViewModel = ????
val list = mutableStateOf<List<NewsArticle>>(listOf())
init {
val loaded = parent.newsArticles
loadData(loaded)
}
fun loadData(loaded :List<NewsArticle>) {
if (loaded.isEmpty()) {
list.value = repo.loadNews()
} else {
list.value = loaded
}
}
}
I know that I could do the above in my repository, and have it do the caching, but I also use the homeViewModel
for communication between the screens , and if the user has to log in , the app uses the MainActivity's navController to start a new screen where the user will log in.
Is there a way to have a reference to the parent viewmodel from one of the children?
Solution 1:[1]
You can either explicitly call the viewmodel that you want to contact by injecting both viewmodel belonging to same nav graph.
Alternatively, you can share a interface among both viewmodels, ensure it is same instance and use it as communication bridge.
interface ViewModelsComBridge<T>{
fun registerCallback(onMessageReceived : (T) -> Unit)
fun onDispatchMessage(message : T)
fun unregister(onMessageReceived : (T) -> Unit)
}
and in your view models:
class ViewModelA @Inject constructor(private val bridge : ViewModelCommunicationBridge<MyData>, ...) : ViewModel(){
init {
bridge.registerCallback { //TODO something with call }
}
}
in second view model:
class ViewModelA @Inject constructor(private val bridge : ViewModelCommunicationBridge<MyData>, ...) : ViewModel(){
fun onClick(){
val myData = processMyData()
bridge.onDispatchMessage(myData)
}
}
On the other end the other viewmodel will receive this call if it is alive.
Ensure your implementation is inject correctly and it is same instance in both viewmodels.
Solution 2:[2]
Your can change your NewsViewModel
's viewModelStoreOwner
(fragment, activity or HomeScreen's destination), not the lifecycle of news
's destination.
so your data will be survive while NewsScreen
changes.
@Composable
fun HomeScreen(state: HomeState, event: (HomeEvent) -> Unit) {
val navController = rememberNavController()
val newsVm: NewsViewModel = hiltViewModel() //move to here,
Scaffold(
modifier = Modifier.fillMaxSize(),
bottomBar = {
BottomNavigation { .... //add the three bottom navigation menu items
}
},
) {
NavHost(
navController = navController,
startDestination = "news",
) {
composable(route = "news") {
NewsScreen(newsVm)
}
composable(route = "tickets") { NewTicketScreen() }
composable(route = "archive") { ArchiveScreen() }
}
}
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Nikola Despotoski |
Solution 2 | CTD |