Android Jetpack Compose navigation with ViewModels
Android Jetpack Compose navigation with ViewModels. In the course of a recent project, I chose to use Jetpack Compose as my view layer completely. Although Google’s Get Started examples of the UI are quite easy to follow, you soon reach the point where you need to move between various screen sizes (or Composables
). While Google has also got you covered in this area with the Compose component in their navigation library What Google does not offer is a complete perspective, which is why I would like to share some of my experiences with you.
How did it work in the past times
Table of Contents
In the beginning there was light startActivity(AnyActivity::class.java) whenever we wanted to show a new screen, alternatively you would use fragments with the FragmentManager and FragmentTransactions, manage the backstack(s), remember to use the ChildFragmentManager, too, whenever you had to, remember there is an “old” FragmentManager and a SupportFragmentManager that you could mix up etc. Google thought this was not a good idea and came up with an Navigation component providing navigation graphs as well as the NavController
which has every feature of the previous times rolled into one.
The most basic concepts of Jetpack Compose navigation
We’ll follow the guide of the Compose Navigation component. utilize the Compose variant of this newly released NavController
and then we come up with something like:
Jetpack Compose navigation
class HomeActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { val navController = rememberNavController() MyTheme { Scaffold { NavigationComponent(navController) } } } } } @Composable fun NavigationComponent(navController: NavHostController) { NavHost( navController = navController, startDestination = "home" ) { composable("home") { HomeScreen(navController) } composable("details") { DetailScreen() } } } @Composable fun HomeScreen(navController: NavController) { Button(onClick = { navController.navigate("detail") }) { Text(text = "Go to detail") } } @Composable fun DetailScreen() { Text(text = "Detail") }
We developed a basic NaviHost
that has two different routes including home and detail which has a home screen with an option to switch directly to detail, both comprised of a text field.
Introduction of ViewModels
At an optimum point, you’ll want to introduce some more complicated logic which is usually implemented using ViewModels
in the Android Architecture package. This is explained in great detail below. Thankfully, they have also clarified how to link this to the navigation component that was previously described and is explained in this article.
Let’s make an ViewModel
using the steps to create our screen of detail. We’ll make the text appear from it, as well as providing it through the navigation element:
Jetpack Compose navigation with ViewModels
@Composable fun NavigationComponent(navController: NavHostController) { NavHost( navController = navController, startDestination = "home" ) { composable("home") { HomeScreen(navController) } composable("details") { DetailScreen(viewModel()) } } } @Composable fun DetailScreen(viewModel: DetailViewModel) { Text(text = viewModel.getDetailText()) } class DetailViewModel : ViewModel() { fun getDetailText(): String { // some imaginary backend call return "Detail" } }
In contrast with the “ancient times”, where we retrieved a ViewModel
within an activity or fragment and it was pretty obvious when ViewModel.onCleared()
was called, being that it was tied to the lifecycle of the activity/fragment, when is it now called?
No matter if you are using viewModel() as well Hilt hiltViewModel() to get your ViewModel and viewModel, both will invoke onCleared() after the NavHost is finished transitioning to a new route. When you go to another Composable
that is cleaned. This is accomplished by setting an effect called DisposableEffect for the navigation route at the time the NavHost is set up and you could emulate this behaviour even if you’re not making use of the navigator library, and require cleaning the ViewModel by yourself.
With the introduction of ViewModels
our detail screen now needs a DetailViewModel
instance as an input. So, if you defined a Preview
anywhere, it is broken now.
@Preview @Composable fun DetailScreenPreview() { DetailScreen(viewModel = ??) }
The best practice is likely to stay clear of referencing AAC ViewModels within your function that is composable.
The concept is that functional composable code only requires low-level inputs, such as lambdas LiveData
or a Flow
(which could be required in order to work using the state). This also allows us to quickly preview various texts
@Composable fun NavigationComponent(navController: NavHostController) { NavHost( navController = navController, startDestination = "home" ) { composable("home") { HomeScreen(navController) } composable("details") { val viewModel = viewModel<DetailViewModel>() DetailScreen(viewModel::getDetailText) } } } @Composable fun DetailScreen(textProvider: () -> String) { Text(text = textProvider()) } @Preview @Composable fun DetailScreenPreview() { DetailScreen { "Sample text" } }
Read More Tutorial