Navigating with Jetpack Compose
--
In this article we are going to learn how we can navigate from one screen (composable function) to another screen or any activity in jetpack compose.
For that we have to include one library in your build.gradle file.
dependencies {
def nav_version = "2.5.3"
implementation("androidx.navigation:navigation-compose:$nav_version")
}
Before moving to the actual code , let’s learn about some topics which will be responsible for the navigation.
NavHostController
This person plays a very important role in navigation , when you navigate from one screen to another screen this will keep track the back stack of composable function and also keep the state of each screen.
Through rememberNavController()
composable function we make navHostController
.
val navController = rememberNavController()
Key Features of NavHostController
- it provide methods for navigating between different destinations.
- it manages the back stack of composable functions.
- it provide methods for passing data between screens.
NavHost
NavHost
is a composable function which is used to create navigation graph that defines the navigation structure of your app. Basically it will take screens (composable function) inside it and tells you where you have to navigate.
it takes three main parameters inside it.
navHostController
:- it will manage the backstack and keeps the state of composable function.startDestination
:- it takesstring
type of data , which defines the starter or first screen in the navigation graph.NavGraphBuilder.()
:- it is a higher order extension function ofNavGraphBuilder
class and inside it we pass all the composable function that we want to include in the navigation graph.
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "First"){
composable("First"){
FirstScreen()
}
composable("Second"){
SecondScreen()
}
}
As you can see in the above code , first we defined a navHostController
after that in the NavHost
function we passed navController
, startDestination
and all the composable
functions.
composable
is an extension function of NavGraphBuilder
which takes key
and a composable
function.
Here First
and Second
is a key , which identifies a particular screen , also through that keys we can navigate from one screen to another.
FirstScreen
and SecondScreen
is a composable function.
@Composable
fun ComposeNavigation() {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "First"){
composable("First"){
FirstScreen()
}
composable("Second"){
SecondScreen()
}
}
}
// Pass above function in the MainActivity's setContent function :)
setContent{
ComposeNavigation()
}
Navigate from one screen to another screen
Now let’s see how we can navigate from one screen to another screen. As i told you above navHostController
will be responsible for navigation.
@Composable
fun FirstScreen(
navHostController: NavHostController
) {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Button(onClick = {
navHostController.navigate("second")
}) {
Text(text = "Screen one")
}
}
}
@Composable
fun SecondScreen() {
Text(text = "Second screen")
}
In the above code , I want to navigate from first screen to second screen. So for that i passed navHostController
as a parameter and on button click just called navigate
function which takes key
as parameter i,e, second
and navigate to second screen.
Navigate from Composable function to Activity…?
If you want to navigate to any activity from composable function, just simply fired the intent
.
@Composable
fun SecondScreen() {
val context = LocalContext.current
Button(onClick = {
val intent = Intent(context, AnotherActivity::class.java)
context.startActivity(intent)
}) {
Text(text = "Move to activity")
}
}
Manage Back stack of composable functions
navigateUp()
:- Suppose you navigate from first screen to second and you want to get back to previous screen without adding the composable function to the back stack. In that case you can usenavigateUp()
function.
@Composable
fun FirstScreen(
navHostController: NavHostController
) {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 20.dp),
contentAlignment = Alignment.Center
) {
Button(onClick = {
navHostController.navigate("second")
}) {
Text(text = "Screen one")
}
}
}
@Composable
fun SecondScreen(
navHostController: NavHostController
) {
Box(
modifier = Modifier
.fillMaxSize()
.padding(20.dp)
) {
IconButton(onClick = {
navHostController.navigateUp()
}) {
Icon(Icons.Default.ArrowBack, contentDescription = "", tint = Color.Black)
}
}
}
As you can see in the above code in first screen
, on button clicks we are calling the navigate("second")
function to navigate to the second screen as I already mapped the second
key with Second Screen
in the navigation graph.
Here if you don’t use navigateUp()
function instead of , you use navigate("first")
function to get back to the previous screen then it will add the first
and second
screen multiple times to the backstack .
launchSingleTop
:- If the instance of thecomposable
function already exits at the top of the backstack , and if we set thelaunchSingleTop
totrue
then it will not create the another instance of that composable function.
launchSingleTop
is an alternate of android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP of activity.
@Composable
fun FirstScreen(
navHostController: NavHostController
) {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 20.dp),
contentAlignment = Alignment.Center
) {
Button(onClick = {
navHostController.navigate("first") {
launchSingleTop = true
}
}) {
Text(text = "Screen one")
}
}
}
As you can see in the above code on button click we are navigating to first screen
( adding first screen to the backstack) and at the last parameter we have an Extension higher order function of NavOptionsBuilder
where we are setting the launchSingleTop
to true.
Now there will be only one instance of first screen
will add on the backstack.
Note :- If you set the
launchSingleTop
to false then multiple instances of thefirst screen
will be added on the backstack ! You can try it.
popUpTo()
:- This function is used to define thenavigation
behaviour of your app. Suppose If you want toremove
any composable function from thebackstack
, you can do with this function. Let’s understand through an example.
Suppose you have three screen A -> B -> C , and you are navigating from A to B , B to C and C to A
, and you want whenever i navigate from C to A , all the composable function in the backstack should be removed. If i press back from A screen
it should not moved to C again. This type of behaviour can be achieved with popUpTo()
function.
@Composable
fun FirstScreen(
navHostController: NavHostController
) {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 20.dp),
contentAlignment = Alignment.Center
) {
Button(onClick = {
navHostController.navigate("second")
}) {
Text(text = "Screen one")
}
}
}
@Composable
fun SecondScreen(
navHostController: NavHostController
) {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 20.dp),
contentAlignment = Alignment.Center
) {
Button(onClick = {
navHostController.navigate("third")
}) {
Text(text = "Screen Two")
}
}
}
@Composable
fun ThirdScreen(
navHostController: NavHostController
) {
Box(
modifier = Modifier
.fillMaxSize()
.padding(20.dp)
) {
IconButton(onClick = {
navHostController.navigate("first") {
launchSingleTop = true
popUpTo("first")
}
}) {
Icon(Icons.Default.ArrowBack, contentDescription = "", tint = Color.Black)
}
}
}
As you can see in the above code , on button click of ThirdScreen
composable function , we are navigating to FirstScreen
and popping up or removing all the composable function from the back stack upto FirstScreen
.
Note :- Here launchSingleTop = true
means there will be only one instance of FirstScreen
will be on top of the back stack.
inclusive
:- It states that the popUpTo destination should be popped from the back stack.
Did not understand anything ? not a issue.
inclusive always used with popUpto()
function. Basically popUpTo()
function takes two parameter , String/Int
and higher order extension function of PopUpToBuilder
class.
Let’s understand inclusive through an example !
Suppose if i remove launchSingleTop = true
from the above code then there will be two instance of FirstScreen
composable function in the back stack.
So if i use inclusive = true
with popUpTo
function then only one instance will be there. This is how we have to use.
popUpTo("first"){
inclusive = true
}
Passing data between screen
Now the main thing is that how we can pass data from one screen to another screen. So there are multiple ways to pass data between screens. Let’s understand each one by one.
1). Through Routes
The first way to pass data through routes , it is same as like api endpoints. Let’s see an example , first we have to define the routes through sealed class
.
sealed class Screen(val route: String) {
object First : Screen("first_screen")
object Second : Screen("{name}/second_screen") {
fun sendName(name: String) = "$name/second_screen"
}
}
As you can see in the above code we have defined the route (unique key) for first screen
and for the second screen
i want to pass the string type data from first screen to the second screen , that’s why second screen
is accepting the string type data. {name}
is a way to pass data.
@Composable
fun ComposeNavigation() {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = Screen.First.route) {
composable(Screen.First.route) {
FirstScreen(navController)
}
composable(Screen.Second.route) {
val name = it.arguments?.getString("name") ?: "no name"
SecondScreen(navController, name)
}
}
}
Now as you can see in the above code we have passed the sub classes (First and Second) of sealed class
in the NavHost
composable function that will uniquely identify the screens.
And in the second screen
we are getting the data through arguments
which is coming from NavBackStackEntry
class.
@Composable
fun FirstScreen(
navHostController: NavHostController
) {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 20.dp),
contentAlignment = Alignment.Center
) {
Button(onClick = {
navHostController.navigate(Screen.Second.sendName("Jayant"))
}) {
Text(text = "Screen one")
}
}
}
@Composable
fun SecondScreen(
navHostController: NavHostController,
name: String
) {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 20.dp),
contentAlignment = Alignment.Center
) {
Text(text = "My name is $name")
}
}
Now In the first screen
on button click we are passing the name
data to the second screen
.
As the above way is fine to pass data between screens but it’s little difficult to pass object of data , so for that we have to use another way.
2). Through NavHostController
As I told you above NavHostController
give us methods for passing data between screen. Let’s pass object of data
between screens.
plugins {
id 'kotlin-parcelize'
}
Add kotlin-parcelize
plugin in your build.gradle file , because first we have to parcelize the data class
@Parcelize
data class User(
val name: String,
val age: String
):Parcelable
Make the data class and parcelize it.
sealed class Screen(val route: String) {
object First : Screen("first_screen")
object Second : Screen("second_screen")
}
Define the routes through sealed class
and pass it in the NavHost
composable function.
@Composable
fun FirstScreen(
navHostController: NavHostController
) {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 20.dp),
contentAlignment = Alignment.Center
) {
Button(onClick = {
navHostController.currentBackStackEntry?.savedStateHandle?.set(
"data",
User("Jayant", "23")
)
navHostController.navigate(Screen.Second.route)
}) {
Text(text = "Screen one")
}
}
}
In the above code we set the data and passing data through navHostController
.
@Composable
fun SecondScreen(
navHostController: NavHostController,
) {
val data =
navHostController.previousBackStackEntry?.savedStateHandle?.get<User>("data") ?: User(
"",
""
)
Box(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 20.dp),
contentAlignment = Alignment.Center
) {
Text(text = "My name is ${data.name} and age is ${data.age}")
}
}
Now In the second screen we are extracting the data through navHostController
and make sure to use elvis operator
to get rid from null pointer exception.
3). Through ViewModel
You can also pass data through viewmodel , let’s see an example.
class NavigationViewModel : ViewModel() {
private val _data: MutableState<User?> = mutableStateOf(null)
var data: State<User?> = _data
private set
fun setData(user: User) {
_data.value = user
}
}
As you can see in the above code first we created a viewmodel
. data
variable will expose the data in the second composable function and setData()
function will get data from the first screen
.
@Composable
fun ComposeNavigation() {
val navController = rememberNavController()
val viewModel: NavigationViewModel = viewModel()
NavHost(navController = navController, startDestination = Screen.First.route) {
composable(Screen.First.route) {
FirstScreen(navController, viewModel)
}
composable(Screen.Second.route) {
SecondScreen(navController, viewModel)
}
}
}
Now In the navigation graph we created an object of NavigationViewModel
through viewModel()
composable function.
@Composable
fun FirstScreen(
navHostController: NavHostController,
viewModel: NavigationViewModel
) {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 20.dp),
contentAlignment = Alignment.Center
) {
Button(onClick = {
viewModel.setData(User("Jayant", "23"))
navHostController.navigate(Screen.Second.route)
}) {
Text(text = "Screen one")
}
}
}
@Composable
fun SecondScreen(
navHostController: NavHostController,
viewModel: NavigationViewModel
) {
val data = viewModel.data.value
Box(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 20.dp),
contentAlignment = Alignment.Center
) {
Text(text = "My name is ${data?.name} and age is ${data?.age}")
}
}
Now In the above code we are setting the data in the first screen
and observing the data in the second screen
.
And Make sure you pass the same object of viewmodel to both the screens.
From my point of views , ViewModel is the perfect way to pass data between composable functions.
That’s all for today my friends , Hope you enjoyed and learnt something new from this article.
If you feel this article informative , feel free to clap and follow us on medium to read such type of contents.