How to Develop Multiplatform apps using Compose Multiplatform for iOS, Android, Desktop and Web

How to do cross platform and multiplatform app development using Compose Multiplatform which is part of Kotlin Multiplatform Shared UI and Shared logic template for developing iOS and Android Apps | Part 2 | Implement Bottom Navigation

Arunabh Das
Developers Inc
4 min readMar 11, 2024

--

Compose Multiplatform | Part 2 | Bottom Navigation

Introduction

In Part 1 of this codelab, we created the navigation between screens of Lithium CRM, our compose multiplatform app.

Compose Multiplatform and Voyager library

In this part, we continue implementing the bottom navigation for our project.

Step 1 — Implement bottom nav by implementing Tab interface

In order to implement tabs TabOne, TabTwo and TabThree, we will need to implement the Tab interface and provide implementations for the Content composable as well as the get implementation for options of type TabOptions as seen below

import androidx.compose.runtime.Composable
import cafe.adriel.voyager.navigator.tab.Tab
import cafe.adriel.voyager.navigator.tab.TabOptions

object TabOne: Tab{
@Composable
override fun Content() {
TODO("Not yet implemented")
}

override val options: TabOptions
@Composable
get() = TODO("Not yet implemented")

Step 2 — Implement TabOne

We may now implement TabOne as below


object TabOne: Tab{
@Composable
override fun Content() {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Text("Tab One")
}
}

override val options: TabOptions
@Composable
get() {
val index: UShort = 0u
val title = "Tab One"
val icon = rememberVectorPainter(Icons.Default.Home)

return TabOptions(
index, title, icon
)
}
}

Step 3 — Implement TabTwo

We may now implement TabTwo as below


object TabTwo: Tab {
@Composable
override fun Content() {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Text("Tab Two")
}
}

override val options: TabOptions
@Composable
get() {
val index: UShort = 1u
val title = "Tab Two"
val icon = rememberVectorPainter(Icons.Default.AccountBox)

return TabOptions(
index, title, icon
)
}
}

Step 4 — Implement TabThree

We may now implement TabThree as below


object TabThree: Tab {
@Composable
override fun Content() {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Text("Tab Three")
}
}

override val options: TabOptions
@Composable
get() {
val index: UShort = 0u
val title = "Tab Three"
val icon = rememberVectorPainter(Icons.Default.Notifications)

return TabOptions(
index, title, icon
)
}
}

Step 5 — Implement ScreenDetail to host TabOne, TabTwo, TabThree

We may now implement ScreenDetail.kt to host all three tabs as below


import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.BottomNavigation
import androidx.compose.material.BottomNavigationItem
import androidx.compose.material.Button
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Scaffold
import androidx.compose.material.Text
import androidx.compose.material.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.navigator.Navigator
import cafe.adriel.voyager.navigator.tab.CurrentTab
import cafe.adriel.voyager.navigator.tab.LocalTabNavigator
import cafe.adriel.voyager.navigator.tab.Tab
import cafe.adriel.voyager.navigator.tab.TabNavigator
import cafe.adriel.voyager.transitions.SlideTransition


data class ScreenDetail(
val navigator: Navigator,
val textString: String
): Screen {
@Composable
override fun Content() {
MaterialTheme {
TabNavigator(TabOne) {
Scaffold(
bottomBar = {
BottomNavigation {
TabItem(TabOne)
TabItem(TabTwo)
TabItem(TabThree)
}
}
) { innerPadding ->
CurrentTab()
}
}
}
}
}


@Composable
private fun RowScope.TabItem(tab: Tab) {
val tabNavigator = LocalTabNavigator.current

BottomNavigationItem(
selected = tabNavigator.current == tab,
onClick = {
tabNavigator.current = tab
},
icon = {
tab.options.icon?.let {painter ->
Icon(painter, contentDescription = tab.options.title)
}
}
)
}

Step 7 — Pass navigator in as argument to navigator.push in ScreenHome button onClick lambda

Pass navigator in as argument to ScreenDetail in ScreenHome as below


class ScreenHome: Screen {
@Composable
override fun Content() {
val navigator = LocalNavigator.currentOrThrow
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("Home")
Button(
onClick = {
navigator.push(ScreenDetail(
navigator = navigator,
textString = "Details About Lithium CRM"
))
}
) {
Text("Navigate to Detail")
}
}
}
}

Step 8 — Rebuild and run to validate

Upon rebuild and running, we can see that we have the 3 tabs implemented using Compose Multiplatform and Voyager as seen in the screenshots below

BottomNavigation with 3 tab items

--

--

Arunabh Das
Developers Inc

Sort of an executive-officer-of-the-week of a-techno-syndicalist commune. Cypherpunk, techno-idealist, peacenik, spiritual, humanist