{"id":5064,"date":"2024-10-16T15:35:00","date_gmt":"2024-10-16T15:35:00","guid":{"rendered":"https:\/\/wp-dev.speednet.pl\/?p=5064"},"modified":"2024-11-06T10:43:54","modified_gmt":"2024-11-06T10:43:54","slug":"jetpackcompose-navigation","status":"publish","type":"post","link":"https:\/\/speednetsoftware.com\/jetpackcompose-navigation\/","title":{"rendered":"JetpackCompose Navigation"},"content":{"rendered":"\n<p>This is a Kotlin Multiplatform project targeting Android and iOS where I will showcase Jetpack Compose as the app navigation. Since the navigation is moved from Android to multiplatform project we definitely should give it a try&nbsp;<a href=\"https:\/\/www.jetbrains.com\/help\/kotlin-multiplatform-dev\/compose-navigation-routing.html\" target=\"_blank\" rel=\"noreferrer noopener nofollow\">link<\/a>.<\/p>\n\n\n<nav id=\"toc\" class=\"table-of-content\" role=\"doc-toc\">\n    <h3>\n                    Table of Contents\n            <\/h3>\n    <div class=\"loader\">\n    <div class=\"loader__dot\"><\/div>\n    <div class=\"loader__dot\"><\/div>\n    <div class=\"loader__dot\"><\/div>\n<\/div><\/nav>\n\n\n\n\n<div class=\"wp-block-group\"><div class=\"wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained\">\n<div class=\"wp-block-group is-vertical is-layout-flex wp-container-core-group-is-layout-1 wp-block-group-is-layout-flex\">\n<p>Assumptions:<br>&#8211; Application should allow us to navigate from one screen to another.<br>&#8211; Application should allow to pass some parameters from first to second screen.<br>&#8211; Application should handle the screen rotation without loosing data.<br>&#8211; Application should handle the Tab Navigation.<br>&#8211; Application should handle the async operations with coroutines.<\/p>\n<\/div>\n<\/div><\/div>\n\n\n\n<p>In the next posts I will also cover the&nbsp;<a href=\"\/decompose-navigation\/\" target=\"_blank\" rel=\"noreferrer noopener\">Voyager<\/a>,&nbsp;<a href=\"\/appyx-navigation\/\" target=\"_blank\" rel=\"noreferrer noopener\">Appyx<\/a>&nbsp;and&nbsp;<a href=\"\/decompose-navigation\/\" target=\"_blank\" rel=\"noreferrer noopener\">Decompose<\/a>&nbsp;navigation libraries.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">The project:<a href=\"https:\/\/github.com\/mkonkel\/JetpackComposeNavigation#the-project\"><\/a><\/h3>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p><em>Note:<br>The recent release of&nbsp;<a href=\"https:\/\/developer.android.com\/jetpack\/androidx\/releases\/navigation#2.8.0-alpha08\" target=\"_blank\" rel=\"noreferrer noopener nofollow\">JetpackNavigation<\/a>&nbsp;adds&nbsp;<code>Safe Args<\/code>&nbsp;which is a convenient way of defining routes with usage of&nbsp;&nbsp;<a href=\"https:\/\/kotlinlang.org\/docs\/serialization.html\" target=\"_blank\" rel=\"noreferrer noopener nofollow\">Kotlin Serialization<\/a>&nbsp;&#8211; but it&#8217;s not available yet in compose multiplatform&nbsp;<code>1.6.11<\/code>. I hope it changes soon, but for now we need to define routes as plain strings.<\/em><br><br>05.07.2024 edit:<br><em>Good news are that the new update of compose multiplatform is available. The version&nbsp;<code>1.7.0-alpha01<\/code>that brings the&nbsp;<code>Safe Args<\/code>!<\/em><\/p>\n<\/blockquote>\n\n\n\n<p>Base project setup as always is made with&nbsp;<a href=\"https:\/\/kmp.jetbrains.com\/\" target=\"_blank\" rel=\"noreferrer noopener nofollow\">Kotlin Multiplatform Wizard<\/a>, we also need to add some&nbsp;<a href=\"https:\/\/developer.android.com\/develop\/ui\/compose\/navigation\" target=\"_blank\" rel=\"noreferrer noopener nofollow\">navigation-compose<\/a>&nbsp;as it is the core thing that we would like to examine, according to the&nbsp;<a href=\"https:\/\/www.jetbrains.com\/help\/kotlin-multiplatform-dev\/compose-navigation-routing.html#sample-project\" target=\"_blank\" rel=\"noreferrer noopener nofollow\">documentation<\/a>&nbsp;we should use&nbsp;<code>navigation<\/code>&nbsp;in version&nbsp;<code>2.8.0-alpha08<\/code>&nbsp;and kotlinx.serialization.<\/p>\n\n\n\n<p><em>libs.versions.toml<\/em><\/p>\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>&#91;versions]\ncompose-plugin = \"1.7.0-alpha01\"\nnavigation-compose = \"2.8.0-alpha08\"\nserialization = \"1.6.3\"\n\n&#91;libraries]\nnavigation-compose = { module = \"org.jetbrains.androidx.navigation:navigation-compose\", version.ref = \"navigation-compose\" }\nserialization-json = { module = \"org.jetbrains.kotlinx:kotlinx-serialization-json\", version.ref = \"serialization\" }\n\n&#91;plugins]\njetbrainsCompose = { id = \"org.jetbrains.compose\", version.ref = \"compose-plugin\" }\nkotlinSerialization = { id = \"org.jetbrains.kotlin.plugin.serialization\", version.ref = \"kotlin\" }\n<\/code><\/pre>\n\n\n<\/div>\n\n\n\n\n<p>Freshly added dependencies needs to be synced with the project and added to the&nbsp;<em><strong>build.gradle.kts<\/strong><\/em><\/p>\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>plugins {\n    alias(libs.plugins.kotlinSerialization)\n}\n\nsourceSets {\n    commonMain.dependencies {\n        ...\n        implementation(libs.navigation.compose)\n        implementation(libs.serialization.json)\n    }\n}<\/code><\/pre>\n\n\n<\/div>\n\n\n\n\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Linear Navigation<\/h2>\n\n\n\n<p>Getting started. The question is how do the navigation know where to go &#8211; it&#8217;s simple, every destination has its own unique identification that defines current screen. The destination in most cases will be a composable function that will be displayed on the screen.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>Note:<br><em>In previous versions of navigation the route was defined as a string (you can think of it as the URL address). But now we have more robust approach where we can pass an Object\/Class\/KClass as a destination as log as they are serializable.<\/em><\/p>\n<\/blockquote>\n\n\n\n<p>Let&#8217;s start with the&nbsp;<code>Screen<\/code>&nbsp;sealed class that will hold the destinations.<\/p>\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>@Serializable\nsealed class Screen {\n    @Serializable\n    data object First : Screen()\n\n    @Serializable\n    data object Second : Screen()\n}<\/code><\/pre>\n\n\n<\/div>\n\n\n\n\n<p>Now we can create the&nbsp;<code>Navigation<\/code>&nbsp;composable function. Which will hold the&nbsp;<code>NavHost<\/code>&nbsp;and&nbsp;<code>navigationController<\/code>. The&nbsp;<code>NavHost<\/code>&nbsp;is the container for displaying the current destination and the&nbsp;<code>navigationController<\/code>&nbsp;is the object that manages the navigation between destinations (screens). The last thing is the&nbsp;<code>NavGraph<\/code>&nbsp;that maps composable destinations and routes.<\/p>\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>@Composable\nfun Navigation() {\n    val navController = rememberNavController()\n\n    NavHost(\n        navController = navController,\n        startDestination = Screen.First,\n    ) {\n        composable&lt;Screen.First&gt; {\n            FirstScreen(navController)\n        }\n\n        composable&lt;Screen.Second&gt; {\n            SecondScreen(navController)\n        }\n    }\n}<\/code><\/pre>\n\n\n<\/div>\n\n\n\n\n<p>With the navigation frame built we should implement some screens. The&nbsp;<code>First<\/code>&nbsp;screen will be really simple with a single button that will navigate to the&nbsp;<code>Second<\/code>&nbsp;screen. The&nbsp;<code>Second<\/code>&nbsp;screen will be also simple with a button that will navigate back.<\/p>\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>@Composable\nfun FirstScreen() {\n    Column(\n        modifier = Modifier.fillMaxSize(),\n        horizontalAlignment = Alignment.CenterHorizontally,\n        verticalArrangement = Arrangement.Center\n    ) {\n        Text(\"First screen\")\n        Button(onClick = { \/*TODO navigate to the second screen*\/ }) {\n            Text(\"Second Screen\")\n        }\n    }\n}<\/code><\/pre>\n\n\n<\/div>\n\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>@Composable\nfun SecondScreen() {\n    Column(\n        modifier = Modifier.fillMaxSize(),\n        horizontalAlignment = Alignment.CenterHorizontally,\n        verticalArrangement = Arrangement.Center\n    ) {\n        Text(\"Second screen\")\n        Spacer(modifier = Modifier.height(16.dp))\n        Button(onClick = { \/*TODO navigate back to first screen*\/ }) {\n            Text(\"Go Back\")\n        }\n    }\n}<\/code><\/pre>\n\n\n<\/div>\n\n\n\n\n<p>Now, let&#8217;s fill the gaps in&nbsp;<code>Navigation()<\/code>&nbsp;function with created screens. We left some TODOs in the screens. If we want to navigate from screen to screen we need to pass the&nbsp;<code>navConroler<\/code>&nbsp;as an input to our composable and then call&nbsp;<code>navigate()<\/code>&nbsp;method and&nbsp;<code>popBackStack()<\/code>&nbsp;to go back.<\/p>\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>Button(onClick = { navController.navigate(Screen.Second) }) { Text(\"Second Screen\") }<\/code><\/pre>\n\n\n<\/div>\n\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>Button(onClick = { navController.popBackStack() }) { Text(\"Go Back\") }<\/code><\/pre>\n\n\n<\/div>\n\n\n\n\n<p>With the initial setup made, all that left is to use&nbsp;<code>Navigation()<\/code>&nbsp;function in the application entrypoint. The&nbsp;<code>MainActivity.kt<\/code>&nbsp;for android and for iOS it is the&nbsp;<code>MainViewController.kt<\/code>.<\/p>\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>class MainActivity : ComponentActivity() {\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n\n        setContent {\n            Navigation()\n        }\n    }\n}<\/code><\/pre>\n\n\n<\/div>\n\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>fun MainViewController() = ComposeUIViewController { Navigation() }<\/code><\/pre>\n\n\n<\/div>\n\n\n\n\n<p>After running the application we should see the&nbsp;<code>First<\/code>&nbsp;scree with a button that navigates to the&nbsp;<code>Second<\/code>&nbsp;screen and a button that navigates back to the&nbsp;<code>First<\/code>&nbsp;screen.<\/p>\n\n\n<div class=\"video-block\">\n    <video class=\"video-block__video\" controls muted disablePictureInPicture controlsList=\"nodownload noplaybackrate noremoteplayback\" poster=\"https:\/\/speednetsoftware.com\/app\/uploads\/2024\/10\/linear_navigation_1.gif\" preload=\"metadata\">\n                        Your browser does not support the video tag.\n    <\/video>\n<\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\">Passing parameters<\/h2>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>Note:<br><em>Now with Safe Args passing values is easy, but with the previous release passing arguments was tricky. Since the route looks like the URL address required arguments should be passed as a&nbsp;<code>path<\/code>&nbsp;in route and the optional as&nbsp;<code>query<\/code><\/em><\/p>\n<\/blockquote>\n\n\n\n<p>With the&nbsp;<code>Safe Args<\/code>&nbsp;we can pass parameters as the part of the destination object which is easy and convenient, there are two types of arguments&nbsp;<strong>required<\/strong>&nbsp;and&nbsp;<strong>optional<\/strong><\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Required Arguments<\/strong><\/h3>\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>@Serializable\nsealed class Screen {\n    ...\n    @Serializable\n    data class Third(val greeting: String) : Screen()\n}<\/code><\/pre>\n\n\n<\/div>\n\n\n\n\n<p>Now we need to create the&nbsp;<code>ThirdScreen<\/code>&nbsp;composable function that will accept the&nbsp;<code>greetings<\/code>&nbsp;parameter and provide a way to pass the arguments. Since the&nbsp;<code>composable&lt;T&gt;()<\/code>&nbsp;is a typed function where&nbsp;<code>T<\/code>&nbsp;is route from a&nbsp;<code>KClass<\/code>&nbsp;for the destination we can use the&nbsp;<code>.toRoute&lt;T&gt;()<\/code>&nbsp;function. This extension function returns route as an object of type&nbsp;<code>T<\/code>. From now, we can extract the arguments from the passed class. and as we know what type of the class it is we also know what type the arguments are.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>Note:<br><em>Before&nbsp;<code>Safe Args<\/code>&nbsp;we needed to use&nbsp;<code>NavArgumentBuilder<\/code>&nbsp;and explicit define the type of the argument and the key for the argument.<\/em><\/p>\n<\/blockquote>\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>composable(\n  route = Screen.ThirdScreen.route,\n  arguments = listOf(navArgument(\"greetings\") { type = NavType.StringType }\n) {\n     ThirdScreen(navController, it.arguments?.getString(\"greetings\").orEmpty())\n }<\/code><\/pre>\n\n\n<\/div>\n\n\n\n\n<p><em>And you can clearly see that it was a mess<\/em>.<\/p>\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>@Composable\nfun Navigation() {\n    NavHost(...) {\n        ...\n        composable&lt;Screen.Third&gt; {\n            val args = it.toRoute&lt;Screen.Third&gt;()\n\n            ThirdScreen(\n                navController = navController,\n                greetings = args.greeting\n            )\n        }\n    }\n}<\/code><\/pre>\n\n\n<\/div>\n\n\n\n\n<p>Since we know that the passing argument is a\u00a0<code>String<\/code>\u00a0we can extract it from the route and pass it to the\u00a0<code>Third<\/code> screen composable.<\/p>\n\n\n\n<p>Let&#8217;s modify the&nbsp;<code>First<\/code>&nbsp;screen to navigate to the&nbsp;<code>Third<\/code>&nbsp;screen with the greetings&#8217; parameter as an argument of the data class.<\/p>\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>@Composable\nfun FirstScreen(navController: NavHostController) {\n    ...\n    Button(\n        onClick = {\n            val greetings = \"Hello from First Screen\"\n            navController.navigate(Screen.Third(greetings))\n        }\n    ) {\n        Text(\"Third Screen\")\n    }\n}<\/code><\/pre>\n\n\n<\/div>\n\n\n\n\n<p>The&nbsp;<code>Third<\/code>&nbsp;screen should be built in a same way as the&nbsp;<code>Second<\/code>&nbsp;screen but with a proper parameters passed.<\/p>\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>@Composable\nfun ThirdScreen(navController: NavHostController, greetings: String) {\n    Column(\n        modifier = Modifier.fillMaxSize(),\n        horizontalAlignment = Alignment.CenterHorizontally,\n        verticalArrangement = Arrangement.Center\n    ) {\n        Text(\"Third screen\")\n        Spacer(modifier = Modifier.height(16.dp))\n        Text(\"Greetings: $greetings\")\n        Spacer(modifier = Modifier.height(16.dp))\n        Button(onClick = { navController.popBackStack() }) {\n            Text(\"Go Back\")\n        }\n    }\n}<\/code><\/pre>\n\n\n<\/div>\n\n\n\n<div class=\"video-block\">\n    <video class=\"video-block__video\" controls muted disablePictureInPicture controlsList=\"nodownload noplaybackrate noremoteplayback\" poster=\"https:\/\/speednetsoftware.com\/app\/uploads\/2024\/10\/arguments_2.gif\" preload=\"metadata\">\n                        Your browser does not support the video tag.\n    <\/video>\n<\/div>\n\n\n\n\n<h3 class=\"wp-block-heading\">Optional Arguments<\/h3>\n\n\n\n<p>For optional arguments we will follow same idea as with required arguments.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>Note :<br><em>With the\u00a0<code>Safe Args<\/code>\u00a0it&#8217;s easy to pass optional arguments. In previous versions we needed to use\u00a0<code>query<\/code> parameters. Where arguments should be passed in the\u00a0<code>route<\/code>\u00a0and preceded by a\u00a0<code>?<\/code>\u00a0character following the pattern\u00a0<code>?key=value<\/code>, and if you want to pass multiple optional parameters they have to be separated with\u00a0<code>&amp;<\/code>character\u00a0<code>?key1=value1&amp;key2=value2<\/code>. Also, the optional parameters has to be provided with the default value<\/em><\/p>\n<\/blockquote>\n\n\n\n<p>Let&#8217;s create the&nbsp;<code>Fourth<\/code>&nbsp;screen that will have two optional arguments&nbsp;<code>name<\/code>&nbsp;and&nbsp;<code>surname<\/code>.<\/p>\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>@Serializable\ndata class Fourth(val name: String, val surname: String? = null) : Screen()<\/code><\/pre>\n\n\n<\/div>\n\n\n\n\n<p>Now we need to create the\u00a0<code>Fourth<\/code>\u00a0screen composable function that will accept the\u00a0<code>name<\/code>\u00a0and\u00a0<code>surname<\/code> parameters.<\/p>\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>composable&lt;Screen.Fourth&gt; {\n    val args = it.toRoute&lt;Screen.Fourth&gt;()\n\n    FourthScreen(\n        navController = navController,\n        name = args.name,\n        surname = args.surname\n    )\n}<\/code><\/pre>\n\n\n<\/div>\n\n\n\n\n<p>The FourthScreen should be built in a same way as ThordScreen with proper parameters passed.<\/p>\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>@Composable\nfun FourthScreen(navController: NavHostController, name: String, surname: String?) {\n    ...\n}<\/code><\/pre>\n\n\n<\/div>\n\n\n\n\n<p>The FourthScreen should be built in a same way as ThordScreen with proper parameters passed.<\/p>\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>@Composable\nfun FourthScreen(navController: NavHostController, name: String, surname: String?) {\n    ...\n}<\/code><\/pre>\n\n\n<\/div>\n\n\n\n\n<p>Navigation is as simple as it can possibly be:<\/p>\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>fun FirstScreen(navController: NavHostController) {\n    Button(onClick = { navController.navigate(Screen.Fourth(name = \"John\", surname = \"Doe\")) }) {\n        Text(\"John Doe Screen\")\n    }\n\n    Button(onClick = { navController.navigate(Screen.Fourth(name = \"Michael\")) }) {\n        Text(\"Michael Screen\")\n    }\n}<\/code><\/pre>\n\n\n<\/div>\n\n\n\n<div class=\"video-block\">\n    <video class=\"video-block__video\" controls muted disablePictureInPicture controlsList=\"nodownload noplaybackrate noremoteplayback\" poster=\"https:\/\/speednetsoftware.com\/app\/uploads\/2024\/10\/arguments_2-1.gif\" preload=\"metadata\">\n                        Your browser does not support the video tag.\n    <\/video>\n<\/div>\n\n\n\n\n<h3 class=\"wp-block-heading\">Nested Navigation<\/h3>\n\n\n\n<p>In case of complex applications splitting navigation into smaller parts is a good idea. Currently, we have one&nbsp;<code>NavHost<\/code>&nbsp;with all screens originating from the same place. We can divide the navigation into smaller parts that will be encapsulated according to their purpose. Let&#8217;s create a&nbsp;<code>Fifth<\/code>&nbsp;and&nbsp;<code>Sixth<\/code>&nbsp;screen that will separate from main navigation and will be accessible only from the&nbsp;<code>Third<\/code>&nbsp;screen. The graph for such screens will look like this:<\/p>\n\n\n<figure class=\"image-block --is-center --is-full\">\n    <picture class=\"responsive-image--full_width\"><source media=\"all and (min-width: 87.50em)\" srcset=\"https:\/\/speednetsoftware.com\/app\/uploads\/2024\/10\/Zrzut-ekranu-2024-10-2-o-14.56.21.webp\" type=\"image\/webp\"><source media=\"all and (min-width: 87.50em)\" srcset=\"https:\/\/speednetsoftware.com\/app\/uploads\/2024\/10\/Zrzut-ekranu-2024-10-2-o-14.56.21.png\" type=\"image&#x2F;png\"><source media=\"all and (min-width: 75em)\" srcset=\"https:\/\/speednetsoftware.com\/app\/uploads\/2024\/10\/Zrzut-ekranu-2024-10-2-o-14.56.21.webp\" type=\"image\/webp\"><source media=\"all and (min-width: 75em)\" srcset=\"https:\/\/speednetsoftware.com\/app\/uploads\/2024\/10\/Zrzut-ekranu-2024-10-2-o-14.56.21.png\" type=\"image&#x2F;png\"><source media=\"all and (min-width: 62em)\" srcset=\"https:\/\/speednetsoftware.com\/app\/uploads\/2024\/10\/Zrzut-ekranu-2024-10-2-o-14.56.21-1280x0-c-default.webp\" type=\"image\/webp\"><source media=\"all and (min-width: 62em)\" srcset=\"https:\/\/speednetsoftware.com\/app\/uploads\/2024\/10\/Zrzut-ekranu-2024-10-2-o-14.56.21-1280x0-c-default.png\" type=\"image&#x2F;png\"><source media=\"all and (min-width: 36em)\" srcset=\"https:\/\/speednetsoftware.com\/app\/uploads\/2024\/10\/Zrzut-ekranu-2024-10-2-o-14.56.21-768x0-c-default.webp\" type=\"image\/webp\"><source media=\"all and (min-width: 36em)\" srcset=\"https:\/\/speednetsoftware.com\/app\/uploads\/2024\/10\/Zrzut-ekranu-2024-10-2-o-14.56.21-768x0-c-default.png\" type=\"image&#x2F;png\"><source media=\"all\" srcset=\"https:\/\/speednetsoftware.com\/app\/uploads\/2024\/10\/Zrzut-ekranu-2024-10-2-o-14.56.21-480x0-c-default.webp\" type=\"image\/webp\"><source media=\"all\" srcset=\"https:\/\/speednetsoftware.com\/app\/uploads\/2024\/10\/Zrzut-ekranu-2024-10-2-o-14.56.21-480x0-c-default.png\" type=\"image&#x2F;png\"><img decoding=\"async\" src=\"https:\/\/speednetsoftware.com\/app\/uploads\/2024\/10\/Zrzut-ekranu-2024-10-2-o-14.56.21-480x0-c-default.png\" alt=\"\" loading=\"lazy\"><\/picture>\n<\/figure>\n\n\n\n\n<p>With such structured navigation we can easily manage the navigation and the screens. We can create a&nbsp;<code>NestedNavigation<\/code>&nbsp;composable function that will hold the&nbsp;<code>NavHost<\/code>&nbsp;and&nbsp;<code>navigationController<\/code>&nbsp;for the nested navigation. When we close the&nbsp;<code>Third<\/code>&nbsp;screen navigation will remove all its children from the backstack and they won&#8217;t be accessible anymore. It&#8217;s a great tool for structuring processes in the application &#8211; when a process is finished (for example a signup, a payment or a tutorial).<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>If you read my post about navigation&nbsp;<a href=\"https:\/\/github.com\/mkonkel\/DecomposeNavigation\">Decompose<\/a>&nbsp;you can see similarities in the approach. In Decompose every component can have its own stack and manage it.<\/p>\n<\/blockquote>\n\n\n\n<p>Adding the nested navigation graph is done by using the&nbsp;<code>navigation()<\/code>&nbsp;function in the&nbsp;<code>NavHost<\/code>&nbsp;composable. The&nbsp;<code>navigation()<\/code>&nbsp;function takes the&nbsp;<code>startDestination<\/code>&nbsp;and the&nbsp;<code>route<\/code>. The&nbsp;<code>route<\/code>&nbsp;is a unique name (or object) of the nested navigation graph to distinguish it from other graphs. The&nbsp;<code>startDestination<\/code>&nbsp;is the screen that will be displayed when the nested graph is opened.<\/p>\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>@Serializable\nsealed class Route {\n    @Serializable\n    data object Root : Route()\n\n    @Serializable\n    data object Main : Route()\n\n    @Serializable\n    data object Nested : Route()\n}\n\n@Composable\nfun Navigation() {\n\n    NavHost(\n        navController = navController,\n        startDestination = Screen.First,\n        route = Route.Root::class\n    ) {\n        ...\n        navigation(\n            startDestination = Screen.Fifth,\n            route = Route.Nested::class\n        ) {\n            composable&lt;Screen.Fifth&gt; {\n                FifthScreen(navController)\n            }\n\n            composable&lt;Screen.Sixth&gt; {\n                SixthScreen(navController)\n            }\n\n            composable&lt;Screen.Seventh&gt; {\n                SeventhScreen(navController)\n            }\n        }\n    }\n}<\/code><\/pre>\n\n\n<\/div>\n\n\n\n\n<p>To clarify the navigation we can split the&nbsp;<code>Navigation()<\/code>&nbsp;function into separate components, first will handle&nbsp;<code>maib<\/code>graph and second the&nbsp;<code>nested<\/code>&nbsp;graph. To do so we need to create extension functions for&nbsp;<code>NavGraphBuilder<\/code>&nbsp;that will hold specific screens which will result in such graph changes:<\/p>\n\n\n<figure class=\"image-block --is-center --is-full\">\n    <picture class=\"responsive-image--full_width\"><source media=\"all and (min-width: 87.50em)\" srcset=\"https:\/\/speednetsoftware.com\/app\/uploads\/2024\/10\/Zrzut-ekranu-2024-10-2-o-14.58.11.webp\" type=\"image\/webp\"><source media=\"all and (min-width: 87.50em)\" srcset=\"https:\/\/speednetsoftware.com\/app\/uploads\/2024\/10\/Zrzut-ekranu-2024-10-2-o-14.58.11.png\" type=\"image&#x2F;png\"><source media=\"all and (min-width: 75em)\" srcset=\"https:\/\/speednetsoftware.com\/app\/uploads\/2024\/10\/Zrzut-ekranu-2024-10-2-o-14.58.11.webp\" type=\"image\/webp\"><source media=\"all and (min-width: 75em)\" srcset=\"https:\/\/speednetsoftware.com\/app\/uploads\/2024\/10\/Zrzut-ekranu-2024-10-2-o-14.58.11.png\" type=\"image&#x2F;png\"><source media=\"all and (min-width: 62em)\" srcset=\"https:\/\/speednetsoftware.com\/app\/uploads\/2024\/10\/Zrzut-ekranu-2024-10-2-o-14.58.11-1280x0-c-default.webp\" type=\"image\/webp\"><source media=\"all and (min-width: 62em)\" srcset=\"https:\/\/speednetsoftware.com\/app\/uploads\/2024\/10\/Zrzut-ekranu-2024-10-2-o-14.58.11-1280x0-c-default.png\" type=\"image&#x2F;png\"><source media=\"all and (min-width: 36em)\" srcset=\"https:\/\/speednetsoftware.com\/app\/uploads\/2024\/10\/Zrzut-ekranu-2024-10-2-o-14.58.11-768x0-c-default.webp\" type=\"image\/webp\"><source media=\"all and (min-width: 36em)\" srcset=\"https:\/\/speednetsoftware.com\/app\/uploads\/2024\/10\/Zrzut-ekranu-2024-10-2-o-14.58.11-768x0-c-default.png\" type=\"image&#x2F;png\"><source media=\"all\" srcset=\"https:\/\/speednetsoftware.com\/app\/uploads\/2024\/10\/Zrzut-ekranu-2024-10-2-o-14.58.11-480x0-c-default.webp\" type=\"image\/webp\"><source media=\"all\" srcset=\"https:\/\/speednetsoftware.com\/app\/uploads\/2024\/10\/Zrzut-ekranu-2024-10-2-o-14.58.11-480x0-c-default.png\" type=\"image&#x2F;png\"><img decoding=\"async\" src=\"https:\/\/speednetsoftware.com\/app\/uploads\/2024\/10\/Zrzut-ekranu-2024-10-2-o-14.58.11-480x0-c-default.png\" alt=\"\" loading=\"lazy\"><\/picture>\n<\/figure>\n\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>fun NavGraphBuilder.nested(navController: NavHostController) {\n    navigation(\n        startDestination = Screen.Fifth,\n        route = Route.Nested::class\n    ) {\n        \/\/ code for nested navigation\n    }\n}<\/code><\/pre>\n\n\n<\/div>\n\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>fun NavGraphBuilder.main(navController: NavHostController) {\n    navigation(\n        startDestination = Screen.First,\n        route = Route.Main::class\n    ) {\n        \/\/ code for main navigation\n    }\n}<\/code><\/pre>\n\n\n<\/div>\n\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>@Composable\nfun Navigation() {\n    val navController = rememberNavController()\n\n    NavHost(\n        navController = navController,\n        startDestination = Route.Main,\n        route = Route.Root::class\n    ) {\n        main(navController)\n        nested(navController)\n    }\n}<\/code><\/pre>\n\n\n<\/div>\n\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>fun FirstScreen(navController: NavHostController) {\n    ...\n    Button(onClick = { navController.navigate(Route.Nested) }) {\n        Text(\"Nested\")\n    }\n}<\/code><\/pre>\n\n\n<\/div>\n\n\n\n\n<p>With such changes we still can navigate between graphs. There is no problem calling the&nbsp;<code>Fourth<\/code>&nbsp;screen from nested graph. Let&#8217;s try to achieve that by adding a way for the&nbsp;<code>Sixth<\/code>&nbsp;screen to open a&nbsp;<code>Fourth<\/code>&nbsp;screen.<\/p>\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>@Composable\nfun SixthScreen(navController: NavHostController) {\n    ...\n    Button(onClick = {\n        navController.navigate(Screen.Fourth(\"John\", \"Doe\")) {\n            Text(\"John Doe Screen\")\n        }\n    }<\/code><\/pre>\n\n\n<\/div>\n\n\n\n\n<p>We can now modify the&nbsp;<code>Fourth<\/code>&nbsp;screen and add a button that will navigate back to&nbsp;<code>MAIN<\/code>&nbsp;graph instead of the popping back the stack, so we can close the&nbsp;<code>NESTED<\/code>&nbsp;graph immediately and dispose all its children screens from the backstack. The&nbsp;<code>navigate()<\/code>&nbsp;builder has a&nbsp;<code>popUpTo()<\/code>&nbsp;method that allows to remove the destinations from the backstack. We can pass the destination to which we want to pop back to. There is also the&nbsp;<code>inclusive<\/code>&nbsp;parameter to remove the passed destination from the backstack as well.<\/p>\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>@Composable\nfun FourthScreen(...) {\n    ...\n    Button(\n        onClick = {\n            navController.navigate(Route.Main) {\n                popUpTo(Route.Main)\n            }\n        }\n    ) {\n        Text(\"MAIN\")\n    }\n}<\/code><\/pre>\n\n\n<\/div>\n\n\n\n\n<p>We can close the&nbsp;<code>NESTED<\/code>&nbsp;graph even quicker, while opening the&nbsp;<code>Fourth<\/code>&nbsp;screen from the&nbsp;<code>Sixth<\/code>&nbsp;all we need to do is use&nbsp;<code>popUpTo()<\/code>&nbsp;method with the&nbsp;<code>ROUTE.NESTED<\/code>&nbsp;parameter.<\/p>\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>@Composable\nfun SixthScreen(...) {\n    ...\n    Button(onClick = {\n        navController.navigate(Screen.Fourth(\"John\", \"Doe\")) {\n            popUpTo(Route.Nested)\n        }\n    }) {\n        Text(\"John Doe Screen\")\n    }\n}<\/code><\/pre>\n\n\n<\/div>\n\n\n\n\n<p>You can mix the functions as much as you want to achieve desired behaviour, for example you can&nbsp;<code>pop<\/code>&nbsp;screen before entering a new one, drop whole graphs and more &#8211; it&#8217;s a flexible solution.<\/p>\n\n\n<div class=\"video-block\">\n    <video class=\"video-block__video\" controls muted disablePictureInPicture controlsList=\"nodownload noplaybackrate noremoteplayback\" poster=\"https:\/\/speednetsoftware.com\/app\/uploads\/2024\/10\/nested_graphs_4.gif\" preload=\"metadata\">\n                        Your browser does not support the video tag.\n    <\/video>\n<\/div>\n\n\n\n\n<h3 class=\"wp-block-heading\">Bottom Navigation<\/h3>\n\n\n\n<p>Yet another thing that is widely common in mobile apps nowadays is the bottom navigation. Let&#8217;s extend the project with one more feature! We need to add three new screens.\u00a0<code>Eighth<\/code>\u00a0screen which will be the main screen that holds bottom menu, and it&#8217;s a container for the tabs:\u00a0<code>Ninth<\/code>\u00a0screen and\u00a0<code>Tenth<\/code>\u00a0screen. Inside the\u00a0<code>Eighth<\/code> screen we will add a new\u00a0<code>NavHost<\/code>\u00a0that will build its own graph and will handle switching tabs. We will be also using the\u00a0<code>BottomNavigation<\/code>\u00a0jetpack compose control to create the bottom bar view and it&#8217;s items.<\/p>\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>@Composable\nfun EighthScreen() {\n    val navController = rememberNavController()\n\n    Scaffold(\n        modifier = Modifier.fillMaxSize(),\n        bottomBar = {\n            \/\/ TODO: add bottom navigation\n        },\n    ) { innerPadding -&gt;\n        NavHost(\n            modifier = Modifier.padding(innerPadding),\n            navController = navController,\n            startDestination = Screen.Eighth.Tab.Home,\n        ) {\n            composable&lt;Screen.Eighth.Tab.Home&gt; {\n                NinthScreen()\n            }\n\n            composable&lt;Screen.Eighth.Tab.Edit&gt; {\n                TenthScreen()\n            }\n        }\n    }\n}<\/code><\/pre>\n\n\n<\/div>\n\n\n\n\n<p>The nev&nbsp;<code>NavHost<\/code>&nbsp;has its own&nbsp;<code>navController<\/code>&nbsp;and&nbsp;<code>startDestination<\/code>. When we enter the screen the item displayed as a first tab will always be the&nbsp;<code>Ninth<\/code>&nbsp;screen. The local&nbsp;<code>navController<\/code>&nbsp;is used to navigate between tabs. The&nbsp;<code>BottomNavigation<\/code>&nbsp;control its quite helpful. It will render the bottom bar with necessary elements such as&nbsp;<strong>icon<\/strong>,&nbsp;<strong>label<\/strong>&nbsp;and&nbsp;<strong>selected<\/strong>&nbsp;state, and even adds slight dim to the selected item. But to do so we need to provide the information about the tabs. Like in every other type of navigation the displayed screens need their own&nbsp;<code>route<\/code>\/<code>destination<\/code>, so we can to create a new&nbsp;<code>sealed class<\/code>&nbsp;for the tabs inside current&nbsp;<code>Screen.kt<\/code>file.<\/p>\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>@Serializable\nsealed class Screen {\n    ...\n    @Serializable\n    data object Eighth : Screen() {\n        @Serializable\n        sealed class Tab(val icon: ICON, val label: String) : Screen() {\n            @Serializable\n            data object Home : Tab(icon = ICON.HOME, label = \"Home\")\n\n            @Serializable\n            data object Edit : Tab(icon = ICON.EDIT, label = \"Edit\")\n\n            @Serializable\n            enum class ICON {\n                HOME, EDIT\n            }\n        }\n    }\n}<\/code><\/pre>\n\n\n<\/div>\n\n\n\n\n<p>Now we can create the bottom navigation tabs.<\/p>\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>@Composable\nprivate fun BottomBar(navController: NavHostController) {\n    val tabs = listOf(\n        Screen.Eighth.Tab.Home,\n        Screen.Eighth.Tab.Edit,\n    )\n\n    val backstackEntry by navController.currentBackStackEntryAsState()\n    val currentDestination = backstackEntry?.destination\n\n    BottomNavigation {\n        tabs.forEach { tab -&gt;\n            TabItem(tab, currentDestination, navController)\n        }\n    }\n}<\/code><\/pre>\n\n\n<\/div>\n\n\n\n\n<p>The&nbsp;<code>BottomBar<\/code>&nbsp;is a composable function responsible for handling the elements inside tabs container. We need to specify the elements (in our case&nbsp;<code>tabs<\/code>) that are available in the bottom bar. We are also using the&nbsp;<code>currentBackStackEntryAsState()<\/code>&nbsp;to get the current destination &#8211; the value is updated with every&nbsp;<code>navControler<\/code>&nbsp;changes due to&nbsp;<code>navigate()<\/code>&nbsp;or&nbsp;<code>pop()<\/code>&nbsp;functions calls which are triggering the recomposition. As a result the top entry on the backstack is returned &#8211; so we will know what is currently displayed, and we can retrieve the&nbsp;<code>destination<\/code>&nbsp;that contains information about the screen.<\/p>\n\n\n\n<p>The&nbsp;<code>BottomNavigation<\/code>&nbsp;control takes a few parameters, and the last one is the&nbsp;<code>content: @Composable RowScope.() -&gt; Unit<\/code>&nbsp;which will be responsible for creating the bottom navigation view. For each tab that we want to display we should create proper UI element. We can create an extension function for&nbsp;<code>RowScope<\/code>&nbsp;that will be responsible for providing the&nbsp;<code>BottomNavigationItem<\/code>&nbsp;for each tab.<\/p>\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>@Composable\nprivate fun RowScope.TabItem(\n    tab: Screen.Eighth.Tab,\n    currentDestination: NavDestination?,\n    navController: NavHostController,\n) {\n    BottomNavigationItem(\n        icon = { Icon(imageVector = tab.icon.toVector(), contentDescription = \"navigation_icon_${tab.label}\") },\n        label = { Text(tab.label) },\n        selected = currentDestination?.hierarchy?.any { it == tab } == true,\n        onClick = {\n            navController.navigate(tab) {\n                navController.graph.startDestinationRoute?.let { popUpTo(it) }\n                launchSingleTop = true\n            }\n        },\n    )\n}\n\n\/\/ helper function for transforming enum to vectorIcon\nprivate fun Screen.Eighth.Tab.ICON.toVector() = when (this) {\n    Screen.Eighth.Tab.ICON.HOME -&gt; Icons.Default.Home\n    Screen.Eighth.Tab.ICON.EDIT -&gt; Icons.Default.Edit\n}<\/code><\/pre>\n\n\n<\/div>\n\n\n\n\n<p>The&nbsp;<code>selected<\/code>&nbsp;state is calculated by checking if the current destination is the same as the tab the current item. The&nbsp;<code>onClick<\/code>&nbsp;action is responsible for navigating to the clicked tab.<\/p>\n\n\n\n<p>Since we want only one active screen inside the tabs container we need to&nbsp;<code>pop<\/code>&nbsp;it. This will cause dropping other element from the back stack. We can also add the&nbsp;<code>launchSingleTop<\/code>&nbsp;which will ensure that the tab is not preserved, and will be recreated with every click.<\/p>\n\n\n\n<p>Last thing to do is to add an entrypoint in the&nbsp;<code>main<\/code>&nbsp;graph.<\/p>\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>fun NavGraphBuilder.main(navController: NavHostController) {\n    ...\n    composable&lt;Screen.Eighth&gt; {\n        EighthScreen()\n    }\n}<\/code><\/pre>\n\n\n<\/div>\n\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>@Composable\nfun FirstScreen(navController: NavHostController) {\n    ...\n    Button(onClick = { navController.navigate(Screen.Eighth) }) {\n        Text(\"Bottom\")\n    }\n}<\/code><\/pre>\n\n\n<\/div>\n\n\n\n<div class=\"video-block\">\n    <video class=\"video-block__video\" controls muted disablePictureInPicture controlsList=\"nodownload noplaybackrate noremoteplayback\" poster=\"https:\/\/speednetsoftware.com\/app\/uploads\/2024\/10\/bottom_navigation_5.gif\" preload=\"metadata\">\n                        Your browser does not support the video tag.\n    <\/video>\n<\/div>\n\n\n\n\n<h3 class=\"wp-block-heading\">Async Operations<\/h3>\n\n\n\n<p>The last thing that we will cover are this post is async operations. Previously discussed navigation libraries (<a href=\"\/decompose-navigation\/\" target=\"_blank\" rel=\"noreferrer noopener\">Decompose<\/a>,&nbsp;<a href=\"\/appyx-navigation\/\" target=\"_blank\" rel=\"noreferrer noopener\">Appyx<\/a>,&nbsp;<a href=\"\/voyager-navigation\/\" target=\"_blank\" rel=\"noreferrer noopener\">Voyager<\/a>) provided they own business logic container object where async operation can be handled. In the case of JetpackCompose we will be using&nbsp;<code>ViewModels<\/code>&nbsp;that recently were moved to compose multiplatform library. Please keep in mind that Appyx and Voyager still allow to use&nbsp;<code>ViewModels<\/code>&nbsp;if you want to do so.<\/p>\n\n\n\n<p>First thing to add is the proper dependency according to the\u00a0<a href=\"https:\/\/www.jetbrains.com\/help\/kotlin-multiplatform-dev\/compose-viewmodel.html\" target=\"_blank\" rel=\"noreferrer noopener nofollow\">documentation<\/a>.<\/p>\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>&#91;versions]\ncommon-viewmodels = \"2.8.0\"\n\n&#91;libraries]\nviewmodels-compose = { module = \"org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose\", version.ref = \"common-viewmodels\" }<\/code><\/pre>\n\n\n<\/div>\n\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>commonMain.dependencies {\n    ...\n    implementation(libs.viewmodels.compose)\n}<\/code><\/pre>\n\n\n<\/div>\n\n\n\n\n<p>The general usage of the&nbsp;<code>ViewModel<\/code>&nbsp;is quite simple and similar to the android approach. We need to create a class that extends the&nbsp;<code>ViewModel<\/code>&nbsp;and then put the logic there. The&nbsp;<code>ViewModel<\/code>&nbsp;offer us a&nbsp;<code>viewModelScope<\/code>which is a&nbsp;<code>CoroutineScope<\/code>&nbsp;that is bound to the lifecycle of the&nbsp;<code>ViewModel<\/code>. It means that when the&nbsp;<code>ViewModel<\/code>is destroyed all the coroutines that are launched in the&nbsp;<code>viewModelScope<\/code>&nbsp;will be cancelled. Therefore, we can easily use it to handle the async operations.<\/p>\n\n\n\n<p>Let&#8217;s create a simple&nbsp;<code>ViewModel<\/code>&nbsp;that will handle the countdown timer and corresponding&nbsp;<code>Eleventh<\/code>&nbsp;screen for displaying the values.<\/p>\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>class EleventhViewModel : ViewModel() {\n    private val _countDownText = MutableStateFlow(\"\")\n    val countDownText: StateFlow&lt;String&gt; = _countDownText.asStateFlow()\n\n    init {\n        viewModelScope.launch {\n            for (i in 10 downTo 0) {\n                _countDownText.value = i.toString()\n                delay(1000)\n            }\n        }\n    }\n}<\/code><\/pre>\n\n\n<\/div>\n\n\n\n\n<p>To use the\u00a0<code>ViewModel<\/code>\u00a0we can use the\u00a0<code>viewModel<\/code>\u00a0function that is provided by the\u00a0<code>lifecycle-viewmodel-compose<\/code> library. The function returns existing view model or creates new one in the scope. The crated\u00a0<code>ViewModel<\/code> is bound to the\u00a0<code>viewModelStoreOwner<\/code>\u00a0and will be retained as long as the scope is alive.<\/p>\n\n\n\n<p>The source code of the&nbsp;<code>Eleventh<\/code>&nbsp;screen is quite simple and looks as all previously created screens.<\/p>\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>@Composable\nfun EleventhScreen(\n    navController: NavHostController,\n    viewModel: EleventhViewModel = viewModel { EleventhViewModel() },\n) {\n    Column(...) {\n        ..\n        Countdown(viewModel)\n    }\n}\n\n@Composable\nprivate fun Countdown(viewModel: EleventhViewModel) {\n    val countdownText = viewModel.countDownText.collectAsState().value\n    Text(\"COUNTDOWN: $countdownText\")\n}<\/code><\/pre>\n\n\n<\/div>\n\n\n\n\n<p>After adding the created screen to the navigation, we can launch it from the&nbsp;<code>First<\/code>&nbsp;screen end examine the countdown functionality.<\/p>\n\n\n<div class=\"video-block\">\n    <video class=\"video-block__video\" controls muted disablePictureInPicture controlsList=\"nodownload noplaybackrate noremoteplayback\" poster=\"https:\/\/speednetsoftware.com\/app\/uploads\/2024\/10\/coroutines_support_6.gif\" preload=\"metadata\">\n                        Your browser does not support the video tag.\n    <\/video>\n<\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\">Summary<\/h2>\n\n\n\n<p>In this post we covered the basics of the JetpackCompose navigation. The library is well documented and for sure you will find a lot of posts\/videos and other resources that will help you to understand the navigation better. The JetpackCompose navigation is a powerful tool that allows you to create complex navigation structures with ease. The library was available for android for a long time, and now it&#8217;s available for multiplatform projects. The multiplatform version of the library is still in alpha version, but in my opinion It&#8217;s ready to use in production. The library is a great choice for developers who want to create modern and complex navigation structures in their applications.<\/p>\n\n\n\n<p>In my previous posts I covered the&nbsp;<a href=\"\/decompose-navigation\/\" target=\"_blank\" rel=\"noreferrer noopener\">Decompose<\/a>,&nbsp;<a href=\"\/appyx-navigation\/\" target=\"_blank\" rel=\"noreferrer noopener\">Appyx<\/a>,&nbsp;<a href=\"\/voyager-navigation\/\" target=\"_blank\" rel=\"noreferrer noopener\">Voyager<\/a>&nbsp;navigation libraries. Now I can say that my personal favourite is the&nbsp;<code>JetpackCompose<\/code>&nbsp;navigation as it will be the most popular and widely used in the future, with great support from the community and the Jetbrains team. The Compose Multiplatform is growing rapidly, and I&#8217;m sure that in the near future we will see a lot of great libraries and tools that will help us to create modern and complex applications. But all of them are great, and you should choose what fits your needs the best.<\/p>\n\n\n\n<p>When I started to write post about&nbsp;<a href=\"https:\/\/github.com\/mkonkel\/GameShop\" target=\"_blank\" rel=\"noreferrer noopener nofollow\">Fullstack Kotlin Developer<\/a>&nbsp;I was thinking about the navigation library that I will use in the project. As there were no official\/suggested way of handling the navigation in the multiplatform project I chose the&nbsp;<code>Decompose<\/code>&nbsp;library, but after the adoption of the&nbsp;<code>JetpackCompose<\/code>&nbsp;navigation, and the&nbsp;<code>Viewmodels<\/code>&nbsp;to&nbsp;<code>Compose Multiplatform<\/code>&nbsp;I would definitely recommend it as the best choice your project, and I&#8217;m sure that I will use it in all of my future projects as well.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This is a Kotlin Multiplatform project targeting Android and iOS where I will showcase Jetpack Compose as the app navigation. Since the navigation is moved from Android to multiplatform project we definitely should give it a try&nbsp;link. Assumptions:&#8211; Application should allow us to navigate from one screen to another.&#8211; Application should allow to pass some [&hellip;]<\/p>\n","protected":false},"author":18,"featured_media":5080,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"content-type":"","footnotes":""},"categories":[38656],"tags":[],"class_list":["post-5064","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-software-development"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v20.13 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Jetpack Compose Navigation - Speednet<\/title>\n<meta name=\"description\" content=\"If you&#039;re aiming to improve your app&#039;s user experience with better navigation patterns, check out this article on Jetpack Compose Navigation.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/speednetsoftware.com\/jetpackcompose-navigation\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Jetpack Compose Navigation - Speednet\" \/>\n<meta property=\"og:description\" content=\"If you&#039;re aiming to improve your app&#039;s user experience with better navigation patterns, check out this article on Jetpack Compose Navigation.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/speednetsoftware.com\/jetpackcompose-navigation\/\" \/>\n<meta property=\"og:site_name\" content=\"Speednet\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/speednetpl\" \/>\n<meta property=\"article:published_time\" content=\"2024-10-16T15:35:00+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2024-11-06T10:43:54+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/speednetsoftware.com\/app\/uploads\/2024\/10\/ComposeMultiplatform_Jetpack.webp\" \/>\n\t<meta property=\"og:image:width\" content=\"1280\" \/>\n\t<meta property=\"og:image:height\" content=\"720\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/webp\" \/>\n<meta name=\"author\" content=\"Ewelina Kuczynska\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Ewelina Kuczynska\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"10 minutes\" \/>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Jetpack Compose Navigation - Speednet","description":"If you're aiming to improve your app's user experience with better navigation patterns, check out this article on Jetpack Compose Navigation.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/speednetsoftware.com\/jetpackcompose-navigation\/","og_locale":"en_US","og_type":"article","og_title":"Jetpack Compose Navigation - Speednet","og_description":"If you're aiming to improve your app's user experience with better navigation patterns, check out this article on Jetpack Compose Navigation.","og_url":"https:\/\/speednetsoftware.com\/jetpackcompose-navigation\/","og_site_name":"Speednet","article_publisher":"https:\/\/www.facebook.com\/speednetpl","article_published_time":"2024-10-16T15:35:00+00:00","article_modified_time":"2024-11-06T10:43:54+00:00","og_image":[{"width":1280,"height":720,"url":"https:\/\/speednetsoftware.com\/app\/uploads\/2024\/10\/ComposeMultiplatform_Jetpack.webp","type":"image\/webp"}],"author":"Ewelina Kuczynska","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Ewelina Kuczynska","Est. reading time":"10 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/speednetsoftware.com\/jetpackcompose-navigation\/#article","isPartOf":{"@id":"https:\/\/speednetsoftware.com\/jetpackcompose-navigation\/"},"author":{"name":"Ewelina Kuczynska","@id":"https:\/\/speednetsoftware.com\/#\/schema\/person\/ddcb5139dcbd556ea3b625105e4af728"},"headline":"JetpackCompose Navigation","datePublished":"2024-10-16T15:35:00+00:00","dateModified":"2024-11-06T10:43:54+00:00","mainEntityOfPage":{"@id":"https:\/\/speednetsoftware.com\/jetpackcompose-navigation\/"},"wordCount":2589,"commentCount":0,"publisher":{"@id":"https:\/\/speednetsoftware.com\/#organization"},"articleSection":["Software development"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/speednetsoftware.com\/jetpackcompose-navigation\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/speednetsoftware.com\/jetpackcompose-navigation\/","url":"https:\/\/speednetsoftware.com\/jetpackcompose-navigation\/","name":"Jetpack Compose Navigation - Speednet","isPartOf":{"@id":"https:\/\/speednetsoftware.com\/#website"},"datePublished":"2024-10-16T15:35:00+00:00","dateModified":"2024-11-06T10:43:54+00:00","description":"If you're aiming to improve your app's user experience with better navigation patterns, check out this article on Jetpack Compose Navigation.","breadcrumb":{"@id":"https:\/\/speednetsoftware.com\/jetpackcompose-navigation\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/speednetsoftware.com\/jetpackcompose-navigation\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/speednetsoftware.com\/jetpackcompose-navigation\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/speednetsoftware.com\/"},{"@type":"ListItem","position":2,"name":"JetpackCompose Navigation"}]},{"@type":"WebSite","@id":"https:\/\/speednetsoftware.com\/#website","url":"https:\/\/speednetsoftware.com\/","name":"Speednet","description":"We build software","publisher":{"@id":"https:\/\/speednetsoftware.com\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/speednetsoftware.com\/?s={search_term_string}"},"query-input":"required name=search_term_string"}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/speednetsoftware.com\/#organization","name":"Speednet","alternateName":"Speednet","url":"https:\/\/speednetsoftware.com\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/speednetsoftware.com\/#\/schema\/logo\/image\/","url":"https:\/\/wp-dev.speednet.pl\/app\/uploads\/2015\/07\/speednetpl_logo.jpg","contentUrl":"https:\/\/wp-dev.speednet.pl\/app\/uploads\/2015\/07\/speednetpl_logo.jpg","width":200,"height":200,"caption":"Speednet"},"image":{"@id":"https:\/\/speednetsoftware.com\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/speednetpl","https:\/\/www.instagram.com\/speednet.pl\/"]},{"@type":"Person","@id":"https:\/\/speednetsoftware.com\/#\/schema\/person\/ddcb5139dcbd556ea3b625105e4af728","name":"Ewelina Kuczynska","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/speednetsoftware.com\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/f36b6598abece91caaa9e87a3a456efe?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/f36b6598abece91caaa9e87a3a456efe?s=96&d=mm&r=g","caption":"Ewelina Kuczynska"}}]}},"_links":{"self":[{"href":"https:\/\/speednetsoftware.com\/wp-json\/wp\/v2\/posts\/5064"}],"collection":[{"href":"https:\/\/speednetsoftware.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/speednetsoftware.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/speednetsoftware.com\/wp-json\/wp\/v2\/users\/18"}],"replies":[{"embeddable":true,"href":"https:\/\/speednetsoftware.com\/wp-json\/wp\/v2\/comments?post=5064"}],"version-history":[{"count":25,"href":"https:\/\/speednetsoftware.com\/wp-json\/wp\/v2\/posts\/5064\/revisions"}],"predecessor-version":[{"id":5324,"href":"https:\/\/speednetsoftware.com\/wp-json\/wp\/v2\/posts\/5064\/revisions\/5324"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/speednetsoftware.com\/wp-json\/wp\/v2\/media\/5080"}],"wp:attachment":[{"href":"https:\/\/speednetsoftware.com\/wp-json\/wp\/v2\/media?parent=5064"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/speednetsoftware.com\/wp-json\/wp\/v2\/categories?post=5064"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/speednetsoftware.com\/wp-json\/wp\/v2\/tags?post=5064"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}