androidkotlinandroid-jetpack-composevoyager

Android Jetpack Compose and voyager pass topBar height and navigation bar height between screens


I'm developing an app using jetpack compose and I ran into issues while positioning my content inside of the space left in between the top bar and the bottom navigation bar, what I'm looking for is a way to send their height between screens to use as padding.

In my MainActivity I'm using a custom AppBar and the voyager navigation bar, with the Content of my screens between them.

class MainActivity : ComponentActivity() {


    companion object {
        var navigationHeight = mutableStateOf(0.dp)
        var topAppBarHeight = mutableStateOf(0.dp)
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        R2DroidApplication.defaultEnvironment.initEnvironment()
        super.onCreate(savedInstanceState)
        setContent {
            Rr2DroidDynamicTheme {
                Content()
            }
        }
    }

    @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
    @Composable
    fun Content() {
        TabNavigator(HomeTab) {
            Scaffold(
                content = {
                    CurrentTab()
                },
                topBar = {
                    AppBar(title = stringResource(id = R.string.app_name), canGoBack = false,
                        modifier = Modifier.onGloballyPositioned { layoutCoordinates ->
                            topAppBarHeight.value = layoutCoordinates.size.height.dp
                        })
                },
                bottomBar = {
                    NavigationBar(modifier = Modifier.onGloballyPositioned { layoutCoordinates ->
                        navigationHeight.value = layoutCoordinates.size.height.dp
                    }) {
                        TabNavigationItem(tab = HomeTab)
                        TabNavigationItem(tab = PackageManagerTab)
                    }
                }
            )
        }
    }

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

        NavigationBarItem(
            selected = tabNavigator.current.key == tab.key,
            alwaysShowLabel = true,
            label = { Text(text = tab.options.title) },
            onClick = { tabNavigator.current = tab },
            icon = { Icon(painter = tab.options.icon!!, contentDescription = tab.options.title) }
        )
    }

}

My AppBar is a simple Box that contains a CenterAlignedTopBar and a linear progress bar to show the progress:

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AppBar(title: String, canGoBack: Boolean = true, contentLoaded: Boolean = false, modifier: Modifier = Modifier) {
    val navigator = LocalNavigator.currentOrThrow

    Box() {
        Column() {
            CenterAlignedTopAppBar(
                colors = TopAppBarDefaults.topAppBarColors(
                    containerColor = LocalDynamicThemeState.current.colorTuple.value.surface!!,
                    titleContentColor = LocalDynamicThemeState.current.colorTuple.value.primary,
                ),
                title = {
                    Text(title)
                },
                navigationIcon = {
                    if (canGoBack) {
                        IconButton(onClick = {navigator.pop()}) {
                            Icon(
                                painter = painterResource(id = R.drawable.back),
                                contentDescription = stringResource(id = R.string.back)
                            )
                        }
                    }
                }
            )
            IndeterminateLinearIndicator(isLoading = contentLoaded)
            Divider()
        }
    }
}

@Composable
fun IndeterminateLinearIndicator(isLoading: Boolean = false) {
    var loading by remember { mutableStateOf(isLoading) }

    if (!loading) return

    LinearProgressIndicator(
        modifier = Modifier.fillMaxWidth(),
        color = MaterialTheme.colorScheme.secondary,
        trackColor = MaterialTheme.colorScheme.surfaceVariant,
    )
}

My Screen content is rendered in a Tab

object PackageManagerTab : Tab {

    val packageManagerScreen = PackageManagerScreen()
    override val options: TabOptions
        @Composable
        get() {
            val title = stringResource(R.string.package_manager)
            val icon = rememberVectorPainter(image = FeatherIcons.Package)

            return remember {
                TabOptions(
                    index = 0u,
                    title = title,
                    icon = icon
                )
            }
        }

    @Composable
    override fun Content() {
        Box() {
            packageManagerScreen.Content()
        }
    }
}

The screen is a simple BottomSheetScaffold with content and sheet content.

How do I proceed?


Solution

  • The Scaffold Composable passes a innerPadding: PaddingValues parameter to the content Composable. This parameter holds the padding that should be applied to the content in order to make sure that the content is not overlapped by the topBar and bottomBar. You can wrap your CurrentTab() in a Box Composable any apply the padding there:

    Scaffold(
        content = { innerPadding ->  // parameter containing top and bottom padding
            Box(
                modifier = Modifier
                    .fillMaxSize()
                    .padding(innerPadding)  // apply padding
            ) {
                CurrentTab()
            }
        },
        topBar = {
            //...
        },
        bottomBar = {
            //...
        }
    )
    

    Side Note:
    Please also check your dependencies in your module's build.gradle file. Because since a fairly long time, you would be getting a compiler error saying

    Jetpack Compose: Content padding parameter it is not used

    This might indicate that you are using deprecated components.