{"id":4593,"date":"2024-09-06T11:56:00","date_gmt":"2024-09-06T11:56:00","guid":{"rendered":"https:\/\/wp-dev.speednet.pl\/?p=4593"},"modified":"2024-09-17T08:48:46","modified_gmt":"2024-09-17T08:48:46","slug":"appyx-navigation","status":"publish","type":"post","link":"https:\/\/speednetsoftware.com\/appyx-navigation\/","title":{"rendered":"Appyx Navigation"},"content":{"rendered":"\n<p>This is a Kotlin Multiplatform project targeting Android and iOS where we will showcase the Appyx as the app navigation.<\/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<p>Assumptions:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Application should allow us to navigate from one screen to another.<\/li>\n\n\n\n<li>Application should allow to pass some parameters from first to second screen.<\/li>\n\n\n\n<li>Application should handle the screen rotation without loosing data.<\/li>\n\n\n\n<li>Application should handle the Tab Navigation.<\/li>\n\n\n\n<li>Application should handle the async operations with coroutines.<\/li>\n<\/ul>\n\n\n\n<p>In the next posts, I will also cover the\u00a0<a href=\"\/voyager-navigation\/\" target=\"_blank\" rel=\"noreferrer noopener\">Voyager<\/a>,\u00a0<a href=\"\/decompose-navigation\/\" target=\"_blank\" rel=\"noreferrer noopener\">Decompose<\/a>, and Composer navigation libraries.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The project:<\/h2>\n\n\n\n<p><a href=\"https:\/\/github.com\/mkonkel\/AppyxNavigation#the-project\"><\/a>Base project setup as always is made with\u00a0<a href=\"https:\/\/kmp.jetbrains.com\/\" target=\"_blank\" rel=\"noreferrer noopener nofollow\">Kotlin Multiplatform Wizard<\/a>, we also need to add\u00a0<a href=\"https:\/\/github.com\/bumble-tech\/appyx\" target=\"_blank\" rel=\"noreferrer noopener nofollow\">Appyx<\/a>\u00a0as it is the core thing that we would like to examine. Appyx consists of three main libraries that complement itself\u00a0<em><strong>navigation<\/strong><\/em>,\u00a0<em><strong>interactions<\/strong><\/em>, and\u00a0<em><strong>components<\/strong><\/em>\u00a0this allows us to create an application that is fully customized.<\/p>\n\n\n\n<p><em>libs.versions.toml<\/em><\/p>\n\n\n<div class=\"language-\">\n    \n<div class=\"language-\">\n    \n<\/div>\n\n\n\n<\/div>\n\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>&#91;versions]\nappyx = \"2.0.1\"\n\n&#91;libraries]\nappyx-navigation = { module = \"com.bumble.appyx:appyx-navigation\", version.ref = \"appyx\" }\nappyx-interactions = { module = \"com.bumble.appyx:appyx-interactions\", version.ref = \"appyx\" }\nappyx-components-backstack = { module = \"com.bumble.appyx:backstack\", version.ref = \"appyx\" }\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 <em><strong>build.gradle.kts<\/strong><\/em><\/p>\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>sourceSets {\n    commonMain.dependencies {\n        ...\n        implementation(libs.appyx.navigation)\n        implementation(libs.appyx.interactions)\n        api(libs.appyx.components.backstack)\n    }\n}<\/code><\/pre>\n\n\n<\/div>\n\n\n\n\n<p>With dependencies added we can start to create the navigation. Following the Appyx&nbsp;documentation&nbsp;we can notice one major thing, the Appyx gives us flexibility in the interpreting term &#8220;navigation&#8221;. Most modern libraries\/solutions focus on gow to get from one screen to another, but Appyx gives us the possibility to create a navigation that is not only about screens but a &#8220;viewport&#8221;. It can be what you can imagine, for example spinning the carousel. Nevertheless, we will focus on traditional&nbsp;<code>Stack<\/code>&nbsp;navigation. There are some basic blocks that we need to use. The first one is&nbsp;<code>Node<\/code>&nbsp;which is a representation of the structure (in our case the screen). Each node can hold other nodes, and they are called&nbsp;<em><strong>children<\/strong><\/em>. The&nbsp;<em><strong>node<\/strong><\/em>&nbsp;is a standalone unit with its own:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/bumble-tech.github.io\/appyx\/2.x\/components\/backstack\/\" target=\"_blank\" rel=\"noreferrer noopener nofollow\">AppyxComponent<\/a>\u00a0&#8211; in our case it will be\u00a0<em><strong>back stack<\/strong><\/em>, with simple linear navigation. Element at front is consider active, other as stashed. It can never be empty. It has some basic functions helper functions as\u00a0<em><strong>push<\/strong><\/em>,\u00a0<em><strong>pop<\/strong><\/em>,\u00a0<em><strong>replace<\/strong><\/em> and default back handler.<\/li>\n\n\n\n<li><a href=\"https:\/\/bumble-tech.github.io\/appyx\/navigation\/features\/lifecycle\/\" target=\"_blank\" rel=\"noreferrer noopener nofollow\">Lifecycl<\/a><a href=\"https:\/\/bumble-tech.github.io\/appyx\/2.x\/navigation\/features\/lifecycle\/\" rel=\"nofollow\">e<\/a>\u00a0&#8211; it&#8217;s a multiplatform interfaces that notifies the component about the state changes on the platform. For example on he Android platform it is implemented with\u00a0<em><strong>AndroidLifecycle<\/strong><\/em>.<\/li>\n\n\n\n<li>State Restoration after orientation changes<\/li>\n\n\n\n<li>The View, that is created with&nbsp;<code>@composable<\/code><\/li>\n\n\n\n<li>Business logic<\/li>\n\n\n\n<li>Plugins &#8211; since&nbsp;<em><strong>Nodes<\/strong><\/em>&nbsp;should be kept lean, the plugins are used to add additional functionality, for example&nbsp;<code>NodeLifecycleAware<\/code>&nbsp;that allows to listen to the lifecycle events.<\/li>\n<\/ul>\n\n\n\n<p>The\u00a0<em><strong>nodes<\/strong><\/em>\u00a0can be as small as you want to keep the complexity of your logic low and the encapsulated end is extracted to the\u00a0<strong>children<\/strong> to compose the process. With the nodes, your navigation can work as a tree with multiple branches responsible for different processes. Some parts of the tree are active &#8211; visible on the screen, other are stashed. To change what&#8217;s currently active we will use the component, the change will look like navigation. By adding or removing nodes of the node. Such an approach creates a <a href=\"https:\/\/bumble-tech.github.io\/appyx\/2.x\/navigation\/features\/scoped-di\/\" target=\"_blank\" rel=\"noreferrer noopener nofollow\">Scoped DI<\/a>\u00a0the situation where if the parent node is destroyed all of its children nodes and related objects are released. There is also\u00a0<a href=\"https:\/\/bumble-tech.github.io\/appyx\/2.x\/navigation\/features\/childaware\/\" target=\"_blank\" rel=\"noreferrer noopener nofollow\">ChildAwareAPI<\/a>\u00a0that helps with communication between parents and dynamically added children.<\/p>\n\n\n\n<p>After that short introduction it&#8217;s time to code. First thing that we need to create is <em><strong>RootNode<\/strong><\/em>.<\/p>\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>class RootNode(\n    nodeContext: NodeContext,\n) : LeafNode(nodeContext) {\n\n    @Composable\n    override fun Content(modifier: Modifier) {\n        Column(\n            modifier = Modifier.fillMaxSize().then(modifier),\n            verticalArrangement = Arrangement.Center,\n            horizontalAlignment = Alignment.CenterHorizontally\n        ) {\n            Text(\"Hello Appyx!\")\n        }\n    }\n}<\/code><\/pre>\n\n\n<\/div>\n\n\n\n\n<p>Similarly, as in the\u00a0<a href=\"https:\/\/github.com\/mkonkel\/DecomposeNavigation\" target=\"_blank\" rel=\"noreferrer noopener nofollow\">Decompose lib<\/a>\u00a0we need to provide some kind of context. The\u00a0<code>NodeContext<\/code>\u00a0is created on the host platform (ex. Androids MainActivity) and it is passed down to all the descendants. It ensures the support of the lifecycle and keeps the structured hierarchy of children nodes. The\u00a0<code>LeafNode<\/code>\u00a0uses the context and handles all the lifecycle events, manages plugins, provides the coroutines scope, and manages the children creation to keep the structured nodes hierarchy mentioned in\u00a0<em><strong>scoped DI<\/strong><\/em>. It also forces us to implement a <em><strong>@Composable<\/strong><\/em> function <code>Content<\/code> that will be used to create the view.<\/p>\n\n\n\n<p>Let&#8217;s connect the&nbsp;<em><strong>RootNode<\/strong><\/em>&nbsp;with the hosts. For Android we need to use&nbsp;<em><strong>MainActivity<\/strong><\/em>&nbsp;and inherit from the&nbsp;<code>NodeComponentActivity()<\/code>&nbsp;which under the hood integrates the android with Appyx (if you don&#8217;t want to inherit from ready solutions you can implement ActivityIntegrationPoint by yourself) . Then we need to create the&nbsp;<code>NodeHost<\/code>&nbsp;(that is responsible for providing&nbsp;<em><strong>nodeContext<\/strong><\/em>) and provide it with&nbsp;<em><strong>lifecycle<\/strong><\/em>.<\/p>\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>class MainActivity : NodeComponentActivity() {\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n\n        setContent {\n            MaterialTheme {\n                NodeHost(\n                    lifecycle = AndroidLifecycle(LocalLifecycleOwner.current.lifecycle),\n                    integrationPoint = appyxIntegrationPoint\n                ) { nodeContext -&gt;\n                    RootNode(nodeContext)\n                }\n            }\n        }\n    }\n}<\/code><\/pre>\n\n\n<\/div>\n\n\n\n\n<p>Now the iOS. As it is a compose function we just need to create proper host the&nbsp;<code>IosNodeHost<\/code>&nbsp;with default&nbsp;<code>IntegrationPoint<\/code>.<\/p>\n\n\n<div class=\"language-javascript\">\n    \n\n<p><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>fun MainViewController() = ComposeUIViewController {\n    IosNodeHost(\n        modifier = Modifier,\n        integrationPoint = MainIntegrationPoint(),\n        onBackPressedEvents = backEvents.receiveAsFlow()\n    ) { nodeContext -&gt;\n        RootNode(nodeContext)\n    }\n}<\/code><\/pre>\n\n\n<\/div>\n\n\n\n\n<p>The basic setup was done, and we were able to display the initial screen with the text. Now we can add children&#8217;s screens. The screen definition needs to be defined as\u00a0<code>Parcelable<\/code>\u00a0it will tell the node where we want to go. The\u00a0<code>Parcelable<\/code>\u00a0is a part of the Appyx library and it&#8217;s expect\/actual class so it has a common definition that is implemented differently on platforms. For the Android platform, we need to add the support of\u00a0<a href=\"https:\/\/developer.android.com\/kotlin\/parcelize\" target=\"_blank\" rel=\"noreferrer noopener nofollow\">kotlin-parcelize<\/a>\u00a0plugin to use\u00a0<code>@Parcelize<\/code>\u00a0annotation.<\/p>\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>plugins {\n    ...\n    id(\"kotlin-parcelize\")\n}<\/code><\/pre>\n\n\n<\/div>\n\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>sealed class NavTarget : Parcelable {\n    @Parcelize\n    data object FirstScreen : NavTarget()\n\n    @Parcelize\n    data object SecondScreen : NavTarget()\n}<\/code><\/pre>\n\n\n<\/div>\n\n\n\n\n<p>Targets are defined, the&nbsp;<em><strong>RootNode<\/strong><\/em>&nbsp;needs to be modified. It should handle the&nbsp;<code>BackStack<\/code>&nbsp;component, and know how to navigate from one screen to another. The first change that we need to do is to change the&nbsp;<em><strong>RootNode<\/strong><\/em>&nbsp;from being just a&nbsp;<code>LeafNode<\/code>&nbsp;to be a&nbsp;<code>Node&lt;&gt;<\/code>. First one was a simple node that can&#8217;t have children and was used to display the content. The second one is a node that can have children and can be used to navigate between them.<\/p>\n\n\n\n<p>The&nbsp;<em><strong>Node<\/strong><\/em>&nbsp;requires the&nbsp;<code>NavTarget<\/code>&nbsp;to be defined and&nbsp;<code>buildChildNode<\/code>&nbsp;function to be implemented &#8211; those two things will be responsible for handling creation of the children nodes.<\/p>\n\n\n\n<p>The second important thing is the&nbsp;<code>appyxComponent<\/code> &#8211; parameter that is used to define the way how the nodes will be handled.<\/p>\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>private fun backstack(nodeContext: NodeContext): BackStack&lt;NavTarget&gt; = BackStack(\n    model = BackStackModel(\n        initialTarget = NavTarget.FirstScreen,\n        savedStateMap = nodeContext.savedStateMap,\n    ),\n    visualisation = { BackStackFader(it) }\n)<\/code><\/pre>\n\n\n<\/div>\n\n\n\n\n<p>Now we need to add some destination nodes, they would be same with different texts only.<\/p>\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>class FirstNode(\n    nodeContext: NodeContext\n) : LeafNode(nodeContext = nodeContext) {\n    @Composable\n    override fun Content(modifier: Modifier) {\n        Column(\n            modifier = Modifier.fillMaxSize(),\n            verticalArrangement = Arrangement.Center,\n            horizontalAlignment = Alignment.CenterHorizontally\n        ) {\n            Text(\"Hello from the First Node!\")\n        }\n    }\n}<\/code><\/pre>\n\n\n<\/div>\n\n\n\n\n<p>To be able to navigate between the screens we will ad simple lambdas, that will be called in child nodes, but handled in root node.<\/p>\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>class FirstNode(\n    private val onButtonClick: () -&gt; Unit\n) : LeafNode(nodeContext = nodeContext) {\n    @Composable\n    override fun Content(modifier: Modifier) {\n        ...\n        TextButton(onClick = onButtonClick) {\n            Text(\"Go to Second Node\")\n        }\n    }\n}<\/code><\/pre>\n\n\n<\/div>\n\n\n\n\n<p>Going back to the&nbsp;<em><strong>RoodNode<\/strong><\/em><\/p>\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>class RootNode(\n    nodeContext: NodeContext,\n    private val backstack: BackStack&lt;NavTarget&gt; = backstack(nodeContext),\n) : Node&lt;NavTarget&gt;(\n    appyxComponent = backstack,\n    nodeContext = nodeContext,\n) {\n    override fun buildChildNode(navTarget: NavTarget, nodeContext: NodeContext): Node&lt;*&gt; =\n        when (navTarget) {\n            NavTarget.FirstScreen -&gt; FirstNode(nodeContext) {\n                backstack.push(NavTarget.SecondScreen)\n            }\n            NavTarget.SecondScreen -&gt; SecondNode(nodeContext) {\n                backstack.push(NavTarget.FirstScreen)\n            }\n        }\n}<\/code><\/pre>\n\n\n<\/div>\n\n\n\n\n<p>We need to add\u00a0<a href=\"https:\/\/bumble-tech.github.io\/appyx\/2.x\/interactions\/usage\/#in-the-scope-of-appyx-navigation\" target=\"_blank\" rel=\"noreferrer noopener nofollow\">AppyxNavigationContainer<\/a>\u00a0that will handle the navigation and render the added nodes content.<\/p>\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>@Composable\noverride fun Content(modifier: Modifier) {\n    AppyxNavigationContainer(appyxComponent = backstack)\n}<\/code><\/pre>\n\n\n<\/div>\n\n\n\n\n<p><\/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\/09\/1_navigation-2.gif\" preload=\"metadata\">\n                        Your browser does not support the video tag.\n    <\/video>\n<\/div>\n\n\n\n\n<p>You can experiment with different visualizations, for example, <code>BackStackFader<\/code>,\u00a0<code>BackStackSlider<\/code>,\u00a0<code>BackStackParallax<\/code>, or others mentioned in the\u00a0<a href=\"https:\/\/bumble-tech.github.io\/appyx\/2.x\/components\/backstack\/\" target=\"_blank\" rel=\"noreferrer noopener nofollow\">documentation<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Tab Navigation<\/h2>\n\n\n\n<p><a href=\"https:\/\/github.com\/mkonkel\/AppyxNavigation#tab-navigation\"><\/a>To handle the bottom navigation feature we need to use a\u00a0<a href=\"https:\/\/bumble-tech.github.io\/appyx\/2.x\/components\/spotlight\/\" target=\"_blank\" rel=\"noreferrer noopener nofollow\">Spotlight<\/a>\u00a0component, which behaves similarly to the view pager. It can hold multiple nodes at the same and keeps one of them active (visible). The rule is the same as with linear navigation, we just need to switch from\u00a0<em><strong>backstack<\/strong><\/em>\u00a0to\u00a0<em><strong>spotlight<\/strong><\/em>.<\/p>\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>appyx-components-spotlingh = { module = \"com.bumble.appyx:spotlight\", version.ref = \"appyx\" }<\/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    api(libs.appyx.components.spotlingh)\n}<\/code><\/pre>\n\n\n<\/div>\n\n\n\n\n<p>Lets create new navigation targets for tabbed navigation.<\/p>\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>sealed class SpotlightNavTarget : Parcelable {\n    @Parcelize\n    data object ThirdScreen : SpotlightNavTarget()\n\n    @Parcelize\n    data object FourthScreen : SpotlightNavTarget()\n}<\/code><\/pre>\n\n\n<\/div>\n\n\n\n\n<p>Now we need to create a parent node that will hold the spotlight component.<\/p>\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>class SpotlightNode(\n    nodeContext: NodeContext,\n    private val model: SpotlightModel&lt;SpotlightNavTarget&gt; = spotlightModel(nodeContext),\n    private val spotlight: Spotlight&lt;SpotlightNavTarget&gt; = Spotlight(\n        model = model,\n        visualisation = { SpotlightSlider(it, model.currentState) }\n    ),\n) : Node&lt;SpotlightNavTarget&gt;(\n    appyxComponent = spotlight,\n    nodeContext = nodeContext,\n)<\/code><\/pre>\n\n\n<\/div>\n\n\n\n\n<p>We have to provide&nbsp;<code>SpotlightModel<\/code>&nbsp;and&nbsp;<code>SpotlightVisualisation<\/code>&nbsp;one will handle navigation, other the animation. The model takes list of elements available to be displayed in the carousel, and initial index of default the active tab.<\/p>\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>private fun spotlightModel(nodeContext: NodeContext) = SpotlightModel(\n    items = listOf(SpotlightNavTarget.ThirdScreen, SpotlightNavTarget.FourthScreen),\n    initialActiveIndex = 0f,\n    savedStateMap = nodeContext.savedStateMap\n)<\/code><\/pre>\n\n\n<\/div>\n\n\n\n\n<p>Last thing to do is to create a UI representation of the screen, we will use&nbsp;<code>Scaffold<\/code>&nbsp;and default buttons.<\/p>\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>@Composable\noverride fun Content(modifier: Modifier) {\n    Scaffold(\n        bottomBar = {\n            Row(Modifier.background(Color.White)) {\n                TextButton(modifier = Modifier.weight(1f), onClick = { spotlight.first() }) {\n                    Text(text = \"Third\")\n                }\n                TextButton(modifier = Modifier.weight(1f), onClick = { spotlight.last() }) {\n                    Text(text = \"Fourth\")\n                }\n            }\n        }\n    ) { paddings -&gt;\n        AppyxNavigationContainer(modifier = Modifier.padding(paddings), appyxComponent = spotlight)\n    }\n}<\/code><\/pre>\n\n\n<\/div>\n\n\n\n\n<p>The nodes will be exactly the same but with a different text and a background.<\/p>\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>class ThirdNode(\n    nodeContext: NodeContext,\n) : LeafNode(nodeContext = nodeContext) {\n    @Composable\n    override fun Content(modifier: Modifier) {\n        Column(\n            modifier = Modifier.fillMaxSize().background(Color.Magenta),\n            verticalArrangement = Arrangement.Center,\n            horizontalAlignment = Alignment.CenterHorizontally\n        ) {\n            Text(\"Hello from the Third Node!\")\n        }\n    }\n}<\/code><\/pre>\n\n\n<\/div>\n\n\n\n\n<p>The solution is ready to use, as it was designed to work just out of the box we need to add only the entry point to our existing navigation. We need to add&nbsp;<code>TabScreen<\/code>as a&nbsp;<em><strong>NavTarget<\/strong><\/em>&nbsp;in the linear navigation, and a button in&nbsp;<code>FirstNode<\/code>&nbsp;that will run the&nbsp;<em><strong>tabbed navigation<\/strong><\/em>&nbsp;feature.<\/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\/09\/2_spotlight_tab_navigation.gif\" preload=\"metadata\">\n                        Your browser does not support the video tag.\n    <\/video>\n<\/div>\n\n\n\n\n<p>Appyx also provides\u00a0<a href=\"https:\/\/m3.material.io\/\" target=\"_blank\" rel=\"noreferrer noopener nofollow\">Material3<\/a>\u00a0support with out of a box solution for bottom navigation, so we can easily create it using built-in components. The material support library uses the <a href=\"https:\/\/bumble-tech.github.io\/appyx\/components\/spotlight\/\" target=\"_blank\" rel=\"noreferrer noopener nofollow\">spotlight<\/a>\u00a0component) under the hood, so the dependencies we need are:<\/p>\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>appyx-utils-material = { module = \"com.bumble.appyx:utils-material3\", version.ref = \"appyx\" }<\/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    api(libs.appyx.utils.material)\n}<\/code><\/pre>\n\n\n<\/div>\n\n\n\n\n<p>After syncing the project we will reach the&nbsp;<code>AppyxNavItem<\/code>&nbsp;which will be used in a bottom navigation and the&nbsp;<code>AppyxMaterial3NavNode<\/code>&nbsp;responsible for navigation. Creation of the&nbsp;<em><strong>TabNavigationItems<\/strong><\/em>&nbsp;is pretty straightforward and similar to the previously used linear navigation. We need to create&nbsp;<strong>destinations<\/strong>&nbsp;in our case these will be the enums that will represent the screens\/nodes.<\/p>\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>@Parcelize\nenum class TabNavigationItems : Parcelable {\n    FIRST_DESTINATION, SECOND_DESTINATION;\n}<\/code><\/pre>\n\n\n<\/div>\n\n\n\n\n<p>Now we can create the&nbsp;<em><strong>resolver<\/strong><\/em>&nbsp;that will be responsible for creating the bottom bar. It takes the defined&nbsp;<em><strong>destination<\/strong><\/em>&nbsp;and creates the proper navigation items, with&nbsp;<em><strong>text<\/strong><\/em>,&nbsp;<em><strong>icons<\/strong><\/em>&nbsp;and lambda that will create desired&nbsp;<em><strong>nodes<\/strong><\/em>.<\/p>\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>companion object {\n    val resolver: (TabNavigationItems) -&gt; AppyxNavItem = { navBarItem -&gt;\n        when (navBarItem) {\n            FIRST_DESTINATION -&gt; AppyxNavItem(\n                text = \"Third\",\n                unselectedIcon = Icons.Sharp.Home,\n                selectedIcon = Icons.Filled.Home,\n                node = { ThirdNode(it) }\n            )\n\n            SECOND_DESTINATION -&gt; AppyxNavItem(\n                text = \"Fourth\",\n                unselectedIcon = Icons.Sharp.AccountBox,\n                selectedIcon = Icons.Filled.AccountBox,\n                node = { FourthNode(it) }\n            )\n        }\n    }\n}<\/code><\/pre>\n\n\n<\/div>\n\n\n\n\n<p>The last thing to do is to create proper node.<\/p>\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>class TabNode(\n    nodeContext: NodeContext,\n) : AppyxMaterial3NavNode&lt;TabNavigationItems&gt;(\n    nodeContext = nodeContext,\n    navTargets = TabNavigationItems.entries,\n    navTargetResolver = TabNavigationItems.resolver,\n    initialActiveElement = TabNavigationItems.FIRST_DESTINATION,\n)<\/code><\/pre>\n\n\n<\/div>\n\n\n\n\n<p>At the end we need to add another entrypoint in the&nbsp;<em><strong>FirstNode<\/strong><\/em>&nbsp;to be able to react freshly created 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\/09\/3_material_tab_navigation.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\">Coroutines<\/h2>\n\n\n\n<p>There is no official approach how to handle coroutines inside the&nbsp;<em><strong>Nodes<\/strong><\/em>, but we can use for example the&nbsp;<em><strong>NodeLifecycle<\/strong><\/em>&nbsp;which provides the&nbsp;<code>lifecycle<\/code>&nbsp;and&nbsp;<code>lifecycleScope<\/code>&nbsp;and use it (as in the example). We can also use the lifecycle with PlatformLifecycleEventObserver where we create and manage the coroutineScope. We can also use the scope of the @Composable view&nbsp;<code>rememberCoroutineScope()<\/code>&nbsp;or we can mix approaches to fit all your needs.<\/p>\n\n\n\n<p>Moving further I will extend the&nbsp;<em><strong>SecondNode<\/strong><\/em>&nbsp;with a countdown timer that will be started on the screen creation and update the text value.<\/p>\n\n\n<div class=\"language-javascript\">\n    \n\n<pre class=\"wp-block-code\"><code>private val countDownText = mutableStateOf&lt;String&gt;(\"0\")\n\ninit {\n    lifecycle.coroutineScope.launch {\n        for (i in 10 downTo 0) {\n            countDownText.value = i.toString()\n            delay(1000)\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\noverride fun Content(modifier: Modifier) {\n    Column(...) {\n        ...\n        Spacer(modifier = Modifier.height(16.dp))\n        Text(\"Countdown: ${countDownText.value}\")\n    }\n}<\/code><\/pre>\n\n\n<\/div>\n\n\n\n\n<p>You should think about a better place to hold your business logic than\u00a0<code>Node<\/code>\u00a0a place that can handle the configuration changes and recreating of the view, a place that can retain the state. The\u00a0<code>ViewModel<\/code>\u00a0is a perfect place for that, but it&#8217;s not a part of the Appyx library, so you need to implement it by yourself. Appyx is currently in the development phase of\u00a0<em><strong>ViewModel<\/strong><\/em>\u00a0support, and you can vote for it\u00a0<a href=\"https:\/\/bumble-tech.github.io\/appyx\/2.x\/navigation\/features\/surviving-configuration-changes\/\" target=\"_blank\" rel=\"noreferrer noopener nofollow\">here<\/a>. If you would like to survive configuration changes there is an official\u00a0<a href=\"https:\/\/bumble-tech.github.io\/appyx\/2.x\/navigation\/features\/surviving-configuration-changes\/\" rel=\"nofollow\">guide<\/a>\u00a0for that.<\/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\/09\/4_coroutines_support.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>The&nbsp;<code>Appyx<\/code>&nbsp;library is a powerful tool that allows you to create a fully customized navigation in your Compose Multiplatform application. It&#8217;s a great solution for creating complex navigation structures, and it&#8217;s easy to use. The library is tightly coupled with te Jetpack Compose but doesn&#8217;t provide dedicated component to hold your business logic, you are free to use your own solutions. The library is still in the development phase waiting for example for the&nbsp;<code>ViewModel<\/code>&nbsp;support as mentioned in the post. Therefore, it doesn&#8217;t provide an out-of-the-box support for coroutines. You need to handle it by yourself, but it&#8217;s not a big deal.<\/p>\n\n\n\n<p>Compared to\u00a0<a href=\"\/decompose-navigation\/\" target=\"_blank\" rel=\"noreferrer noopener\">Decompose<\/a>\u00a0it was quicker to set up the basic navigation, but it needs to use third-party for holding logic and developing the whole app. On the other side compared to\u00a0<a href=\"\/voyager-navigation\/\" target=\"_blank\" rel=\"noreferrer noopener\">Voyager<\/a>\u00a0I can find a lot of similarities in how it was designed to use, and how handles navigation. Nevertheless, the\u00a0<em><strong>Voyager<\/strong><\/em>\u00a0was a bit more intuitive for me, and I liked the way how it was designed to use. All in all every library has its pros and cons, and it&#8217;s up to you to choose the best one for your project. I think you should try them all and decide which one fits your needs the best.<\/p>\n\n\n\n<p>I hope this post has given you a good overview of the&nbsp;<code>Appyx<\/code>&nbsp;library and how you can use it to create a navigation in your Compose Multiplatform application. If you have any questions or comments, please feel free to leave them below. I&#8217;d love to hear from you!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This is a Kotlin Multiplatform project targeting Android and iOS where we will showcase the Appyx as the app navigation. Assumptions: In the next posts, I will also cover the\u00a0Voyager,\u00a0Decompose, and Composer navigation libraries. The project: Base project setup as always is made with\u00a0Kotlin Multiplatform Wizard, we also need to add\u00a0Appyx\u00a0as it is the core [&hellip;]<\/p>\n","protected":false},"author":18,"featured_media":4613,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"content-type":"","footnotes":""},"categories":[38656],"tags":[],"class_list":["post-4593","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>Appyx Navigation - Speednet<\/title>\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\/appyx-navigation\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Appyx Navigation - Speednet\" \/>\n<meta property=\"og:description\" content=\"This is a Kotlin Multiplatform project targeting Android and iOS where we will showcase the Appyx as the app navigation. Assumptions: In the next posts, I will also cover the\u00a0Voyager,\u00a0Decompose, and Composer navigation libraries. The project: Base project setup as always is made with\u00a0Kotlin Multiplatform Wizard, we also need to add\u00a0Appyx\u00a0as it is the core [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/speednetsoftware.com\/appyx-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-09-06T11:56:00+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2024-09-17T08:48:46+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/speednetsoftware.com\/app\/uploads\/2024\/09\/ComposeMultiplatform_Appyx.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=\"8 minutes\" \/>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Appyx Navigation - Speednet","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\/appyx-navigation\/","og_locale":"en_US","og_type":"article","og_title":"Appyx Navigation - Speednet","og_description":"This is a Kotlin Multiplatform project targeting Android and iOS where we will showcase the Appyx as the app navigation. Assumptions: In the next posts, I will also cover the\u00a0Voyager,\u00a0Decompose, and Composer navigation libraries. The project: Base project setup as always is made with\u00a0Kotlin Multiplatform Wizard, we also need to add\u00a0Appyx\u00a0as it is the core [&hellip;]","og_url":"https:\/\/speednetsoftware.com\/appyx-navigation\/","og_site_name":"Speednet","article_publisher":"https:\/\/www.facebook.com\/speednetpl","article_published_time":"2024-09-06T11:56:00+00:00","article_modified_time":"2024-09-17T08:48:46+00:00","og_image":[{"width":1280,"height":720,"url":"https:\/\/speednetsoftware.com\/app\/uploads\/2024\/09\/ComposeMultiplatform_Appyx.webp","type":"image\/webp"}],"author":"Ewelina Kuczynska","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Ewelina Kuczynska","Est. reading time":"8 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/speednetsoftware.com\/appyx-navigation\/#article","isPartOf":{"@id":"https:\/\/speednetsoftware.com\/appyx-navigation\/"},"author":{"name":"Ewelina Kuczynska","@id":"https:\/\/speednetsoftware.com\/#\/schema\/person\/ddcb5139dcbd556ea3b625105e4af728"},"headline":"Appyx Navigation","datePublished":"2024-09-06T11:56:00+00:00","dateModified":"2024-09-17T08:48:46+00:00","mainEntityOfPage":{"@id":"https:\/\/speednetsoftware.com\/appyx-navigation\/"},"wordCount":1905,"commentCount":0,"publisher":{"@id":"https:\/\/speednetsoftware.com\/#organization"},"articleSection":["Software development"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/speednetsoftware.com\/appyx-navigation\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/speednetsoftware.com\/appyx-navigation\/","url":"https:\/\/speednetsoftware.com\/appyx-navigation\/","name":"Appyx Navigation - Speednet","isPartOf":{"@id":"https:\/\/speednetsoftware.com\/#website"},"datePublished":"2024-09-06T11:56:00+00:00","dateModified":"2024-09-17T08:48:46+00:00","breadcrumb":{"@id":"https:\/\/speednetsoftware.com\/appyx-navigation\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/speednetsoftware.com\/appyx-navigation\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/speednetsoftware.com\/appyx-navigation\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/speednetsoftware.com\/"},{"@type":"ListItem","position":2,"name":"Appyx 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\/4593"}],"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=4593"}],"version-history":[{"count":32,"href":"https:\/\/speednetsoftware.com\/wp-json\/wp\/v2\/posts\/4593\/revisions"}],"predecessor-version":[{"id":4914,"href":"https:\/\/speednetsoftware.com\/wp-json\/wp\/v2\/posts\/4593\/revisions\/4914"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/speednetsoftware.com\/wp-json\/wp\/v2\/media\/4613"}],"wp:attachment":[{"href":"https:\/\/speednetsoftware.com\/wp-json\/wp\/v2\/media?parent=4593"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/speednetsoftware.com\/wp-json\/wp\/v2\/categories?post=4593"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/speednetsoftware.com\/wp-json\/wp\/v2\/tags?post=4593"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}