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
Introduction
In Part 1 of this codelab, we created the navigation between screens of Lithium CRM, our compose multiplatform app.
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
Demo
Source
The full source code for the above project can be found on Github at the repo below