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

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 

You may also like...