getActionBar().setDisplayHomeAsUpEnabled(true) this i was using for normal android appCp,pact activity to switch between two or more activity.can any one tell me how to do this in jetpack Compose ?
The other answer is correct for showing the back button. This is a slight alternative that uses TopAppBar composable instead.
I also ran into a similar issue. The main thing I wanted to solve was hiding the back button when you are at the root or if there is nothing left in backstack, since setDisplayHomeAsUpEnabled took care of that as long as you specified your parent.
Assuming you are using the nav controller with compose, you can do something like this
val navController = rememberNavController()
Scaffold(
topBar = {
TopAppBar(
title = { Text(text = "app bar title") },
navigationIcon = if (navController.previousBackStackEntry != null) {
{
IconButton(onClick = { navController.navigateUp() }) {
Icon(
imageVector = Icons.Filled.ArrowBack,
contentDescription = "Back"
)
}
}
} else {
null
}
)
},
content = {
// Body content
}
)
The key here is to set navigationIcon argument of TopAppBar to null when there is nothing in the back stack. This way the back arrow will be hidden when you are at the root and shown otherwise.
You can wrap your main content inside an Scaffold composable and use the topBar to add the back button and handle back action like this:
import androidx.compose.material.Scaffold
.
.
Scaffold(
topBar = {
Row {
Icon(
imageVector = Icons.Filled.ArrowBack,
contentDescription = "Back",
modifier = Modifier
.padding(16.dp)
.clickable {
// Implement back action here
}
)
}
}
) {
BodyContent()
}
Both other solutions are fine and helped me refine my own variation that I have to extract because of usage on many screens.
#Composable
fun MyScaffold(#StringRes titleId: Int, upAvailable: Boolean, onUpClicked: () -> Unit, content: #Composable (PaddingValues) -> Unit) {
Scaffold(
topBar = {
TopAppBar(title = { MyText(textId = titleId) }, backgroundColor = Color.Black, navigationIcon = {
if (upAvailable) {
IconButton(onClick = { onUpClicked() }) {
Icon(imageVector = Icons.Filled.ArrowBack, contentDescription = "Back", tint = Color.White)
}
}
})
},
backgroundColor = Color.Transparent,
content = content
)
}
Where MyText is just my variant that accepts string res and has white text color.
Usage:
val isUpAvailable by viewModel.upAvailable.collectAsState()
MyScaffold(titleId = R.string.title, upAvailable = isUpAvailable, onUpClicked = { viewModel.navigateUp() }) {
// Content
}
Then my BaseViewModel provides upAvailable and navigateUp() via navigationManager dependency which handles navigation via navigationController:
if (destination.route == NAVIGATE_UP) { navController.navigateUp() }
...
// set on every navigation
navigationManager.setUpAvailable(navController.previousBackStackEntry != null)
Here a full Example of using compose app bar back button. https://github.com/pgatti86/toolbar-demo
val navController: NavHostController = rememberNavController()
val isBackButtonVisible by remember {
derivedStateOf {
navController.previousBackStackEntry != null
}
}
use derived state to show or hide your back button icon
Related
I have this drawer:
val drawerState = rememberDrawerState(DrawerValue.Closed)
val scope = rememberCoroutineScope()
val items = listOf(Icons.Default.Favorite, Icons.Default.Face, Icons.Default.Email)
val selectedItem = remember { mutableStateOf(items[0]) }
ModalNavigationDrawer(
drawerState = drawerState,
drawerContent = {
items.forEach { item ->
NavigationDrawerItem(
icon = { Icon(item, contentDescription = null) },
label = { Text(item.name) },
selected = item == selectedItem.value,
onClick = {
scope.launch { drawerState.close() }
selectedItem.value = item
//How to change the content of Scaffold's content section?
},
modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding)
)
}
},
content = {
Scaffold(
topBar = {
//
},
content = { padding ->
Text(
modifier = Modifier.padding(padding),
text = "Favorites"
)
}
)
}
)
Now, when I run the app, I get the correct TopBar and right under it a Text that holds the value of "Favorites". The problem is that I always want to have the same TopBar, and only change the Scaffold's content. So instead of "Favorites" I want to display "Face", when the second item is clicked.
I though to create some composable functions, to display the data, but I cannot call them from inside the onClick. Android Studio is complaining with:
#Composable invocations can only happen from the context of a #Composable function
I also thought on creating states and load that data according to that state. But isn't it so complicated to only display a single text?
In also thought on navigating to other screens but this means that each screen should have it own TopBar, which in my opinion requires adding more complexity to the app for no reason. I'm completely lost. How to solve this issue?
You should just simply update a viewmodel state value in onclick and listen to that value inside your composable. When the value changes you can simply call other composable. It isn't that complex.
Creating a State to change content is not that complicated same goes for puting a NavHost composable inside your content block.
object Routes {
const val Favorites = "Favorites"
const val Faces = "Faces"
}
var route by remember {mutableStateOf(Routes.Favorites)}
ModalNavigationDrawer(
drawerState = drawerState,
drawerContent = {
items.forEach { item ->
NavigationDrawerItem(
icon = { Icon(item, contentDescription = null) },
label = { Text(item.name) },
selected = item == selectedItem.value,
onClick = {
scope.launch { drawerState.close() }
selectedItem.value = item
// Change route
route = some new route...
},
modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding)
)
}
},
content = {
Scaffold(
topBar = {
//
},
content = { padding ->
if(route == Routes.Favorites) {
Text(
modifier = Modifier.padding(padding),
text = "Favorites"
)
}else {
// other routes
}
}
)
}
)
With NavHost
NavHost(
navController = navController,
startDestination = Routes.Favorites
) {
composable(Routes.Favorites) {
Favorites()
}
composable(Routes. Face) {
Faces()
}
}
I am writing an android application pure in compose and I am using scaffold in every screen to implement topBar, bottomBar, fab, etc.
My question is should I be using scaffold in every screen or just in MainActivity?
what are the best practices while using composables?
Can I use scaffold inside of scaffold ?
I have researched a lot but didn't find answer anywhere even jetpack compose sample apps do not provide anything about best practices to build an app in jetpack compose.
please can anyone help me.
My code looks like this
MainActivity
#AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
PasswordManagerApp()
}
}
}
#Composable
fun PasswordManagerApp() {
val mainViewModel: MainViewModel = hiltViewModel()
val navController = rememberNavController()
val systemUiController = rememberSystemUiController()
val scaffoldState = rememberScaffoldState()
val coroutineScope = rememberCoroutineScope()
Theme(
darkTheme = mainViewModel.storedAppTheme.value
) {
Scaffold(
scaffoldState = scaffoldState,
snackbarHost = { scaffoldState.snackbarHostState }
) {
Box(modifier = Modifier) {
AppNavGraph(
mainViewModel = mainViewModel,
navController = navController,
scaffoldState = scaffoldState
)
DefaultSnackbar(
snackbarHostState = scaffoldState.snackbarHostState,
onDismiss = { scaffoldState.snackbarHostState.currentSnackbarData?.dismiss() },
modifier = Modifier.align(Alignment.BottomCenter)
)
}
}
}
}
Screen 1:
#Composable
fun LoginsScreen(
...
) {
...
Scaffold(
topBar = {
HomeTopAppBar(
topAppBarTitle = LoginsScreen.AllLogins.label,
onMenuIconClick = {},
switchState = viewModel.switch.value,
onSwitchIconClick = { viewModel.setSwitch(it) },
onSettingsIconClick = {navigateToSettings()}
)
},
scaffoldState = scaffoldState,
snackbarHost = { scaffoldState.snackbarHostState },
floatingActionButton = {
MyFloatingBtn(
onClick = { navigateToNewItem() }
)
},
drawerContent = {
//MyDrawer()
},
bottomBar = {
MyBottomBar(
navController = navController,
currentRoute = currentRoute,
navigateToAllLogins = navigateToAllLogins,
navigateToAllCards = navigateToAllCards,
navigateToAllOthers = navigateToAllOthers,
)
},
floatingActionButtonPosition = FabPosition.End,
isFloatingActionButtonDocked = false,
) {
Box(modifier = Modifier.fillMaxSize()) {
Column(
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxSize()
.padding(bottom = 48.dp)
.verticalScroll(scrollState)
) {...}
}
}
Screen 2:
#Composable
fun CardsScreen(
...
) {
...
Scaffold(
topBar = {
HomeTopAppBar(
topAppBarTitle = CardsScreen.AllCards.label,
onMenuIconClick = {},
switchState = viewModel.switch.value,
onSwitchIconClick = { viewModel.setSwitch(it) },
onSettingsIconClick = {navigateToSettings()}
)
},
floatingActionButton = {
MyFloatingBtn(
onClick = { navigateToNewItem() })
},
drawerContent = {
//MyDrawer()
},
bottomBar = {
MyBottomBar(
navController = navController,
currentRoute = currentRoute,
navigateToAllLogins = navigateToAllLogins,
navigateToAllCards = navigateToAllCards,
navigateToAllOthers = navigateToAllOthers,
)
},
floatingActionButtonPosition = FabPosition.End,
isFloatingActionButtonDocked = false
) {
Column(
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxSize()
.padding(bottom = 48.dp)
.verticalScroll(scrollState)
) {...}
}
Like the others in comments, i didn't find any guidelines.
It looks like everything is possible :
Nested Scaffolds
One Scaffold per app
One Scaffold per page
At first sight, you should choose the solution that match your needs.
But...
Keep in mind that one Scaffold per app could be the "better" solution because :
It's the only way to keep Snackbars alive when navigating.
It's the easiest way to have advanced transition animation between screens. I mean controlling animation component by component the way you want during transition (top bar, bottom nav, floating action button, page content...). As well, default NavHost transition animations look better with this solution.
With that solution, you'll have to deal with having different top app bar, floating action button... per page. That's something you can solve by having a shared view state for these elements.
#Immutable
data class ScaffoldViewState(
#StringRes val topAppBarTitle: Int? = null,
#StringRes val fabText: Int? = null,
// TODO : ...etc (top app bar actions, nav icon...)
)
var scaffoldViewState by remember {
mutableStateOf(ScaffoldViewState())
}
Scaffold(
topBar = {
TopAppBar(
title = {
scaffoldViewState.topAppBarTitle?.let {
Text(text = stringResource(id = it))
}
}
)
},
floatingActionButton = {
// TODO : a bit like for topBar
}
) {
NavHost {
composable("a") {
scaffoldViewState = // TODO : choose the top app bar and fab appearance for this page
// TODO : your page here
}
composable("b") {
scaffoldViewState = // TODO : choose the top app bar and fab appearance for this page
// TODO : your page here
}
}
}
As it has been said in comments both approaches are valid. Sure, you can use Scaffold inside another Scaffold like in the sample Jetnews app by Google
If you want "nice" screen transitions while only content changes, then define the component in top level Scaffold (e.g. the Drawer menu is usually shared in the top level Scaffold). Toolbars / AppBars are easier to implement per screen (inner Scaffold), not only in Jetpack Compose, because usually they have different titles, action buttons, etc.
In a simple app without dedicated Toolbars only one Scaffold could be used.
so I am trying to create an app with Jetpack Compose. I have a Screen function which contains a Scaffold with no top app bar, a bottom bar for navigation and a floating action button set into the bottom bar. This all works fine.
However, when I add a NavHost to the scaffold's content, the whole thing stops working. It all works fine without a NavHost, and simply just the content being the composable function for a screen. I have tried with differing amounts of composable locations for the NavHost, different values for padding all to no avail.
What it looks like without a NavHost (i.e. how I want it to look)
Code:
sealed class Screen(val route: String, #DrawableRes val iconId: Int){
object Home : Screen("home", R.drawable.ic_home_24px)
object Stats : Screen("stats", R.drawable.ic_stats_icon)
object Add : Screen("add", R.drawable.ic_add_24px)
object Programs : Screen("programs", R.drawable.ic_programs_icon)
object Exercises : Screen("exercises", R.drawable.ic_exercises_icon)
}
#ExperimentalFoundationApi
#Preview
#Composable
fun Screen(){
val navController = rememberNavController()
Scaffold(
backgroundColor = OffWhite,
bottomBar = {
BottomBar(navController = navController)
},
floatingActionButton = {
FloatingActionButton(
onClick = {},
shape = CircleShape,
backgroundColor = Blue
) {
Icon(
painter = painterResource(id = R.drawable.ic_add_24px),
contentDescription = "Add",
tint = OffWhite,
modifier = Modifier
.padding(12.dp)
.size(32.dp)
)
}
},
isFloatingActionButtonDocked = true,
floatingActionButtonPosition = FabPosition.Center,
) {
HomeScreen()
// NavHost(
// navController = navController,
// startDestination = Screen.Home.route
// ){
// composable(Screen.Home.route){ HomeScreen() }
// composable(Screen.Stats.route){ HomeScreen() }
// composable(Screen.Programs.route){ HomeScreen() }
// composable(Screen.Exercises.route){ HomeScreen() }
// }
}
}
#Composable
fun BottomBar(
navController : NavController
){
val items = listOf(
Screen.Home,
Screen.Stats,
Screen.Add,
Screen.Programs,
Screen.Exercises
)
BottomAppBar(
backgroundColor = OffWhite,
cutoutShape = CircleShape,
content = {
BottomNavigation(
backgroundColor = OffWhite,
contentColor = OffWhite,
modifier = Modifier
.height(100.dp)
) {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry?.destination
items.forEach { screen ->
val selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true
BottomNavigationItem(
icon = {
val iconSize = if (selected) 32.dp else 20.dp
Icon(
painter = painterResource(id = screen.iconId),
contentDescription = screen.route,
tint = Blue,
modifier = Modifier
.padding(12.dp)
.size(iconSize)
)
},
selected = selected,
onClick = {
//Navigate to selected screen
navController.navigate(screen.route) {
//Pop all from stack
popUpTo(navController.graph.findStartDestination().id){
saveState = true
}
//Avoid multiple copies of same screen on stack
launchSingleTop = true
//Restore state when reselecting a previously selected item
restoreState = true
}
},
alwaysShowLabel = false
)
}
}
}
)
}
What it looks like with the NavHost. The boxes in that image are the BottomBar failing to draw, as each box can be clicked on which takes me to BottomBar, BottomNavigationItem, Icon etc. Anyone have any idea whats going on here, and what I can do to fix it? Thanks
Edit: one thing I thought of was changing the 'selected' boolean value in fun BottomBar -> BottomNavigationItem to always be true, just to see if null values were affecting it but this did not change anything.
Error Message says Preview does not suppport ViewModels creation. Since, NavHost create viewmodels, does the error. So what I did to somewhat preview the scaffold and some screen, I separate the content of the scaffold.
Example:
#Composable
fun MainApp(
navController: NavController,
content: #Composable (PaddingValues) -> Unit
) {
StoreTheme {
Scaffold(
bottomBar = { BottomAppNavigationBar(navController) },
content = content
)
}
}
On Real App:
...
val navController = rememberNavController()
MainApp(navController) { innerPadding ->
NavHost(
navController = navController,
startDestination = BottomNavMenu.Screen1.route,
modifier = Modifier.padding(innerPadding)
) {
composable(...) { Screen1... }
composable(...) { Screen2... }
}
}
...
On Preview:
#Preview(showBackground = true)
#Composable
fun MainAppPreview() {
val navController = rememberNavController()
MainApp(navController) {
Screen1(navController)
}
}
Turns out my studio theme was hiding the error notification. After changing theme it says
java.lang.IllegalStateException: ViewModels creation is not supported in Preview
so I guess I need to whack it in my phone to test.
I'm building Android app with Jetpack Compose. Got stuck while trying to implement BottomAppBar with BottomDrawer pattern.
Bottom navigation drawers are modal drawers that are anchored to the bottom of the screen instead of the left or right edge. They are only used with bottom app bars. These drawers open upon tapping the navigation menu icon in the bottom app bar.
Description on material.io, and direct link to video.
I've tried using Scaffold, but it only supports side drawer. BottomDrawer appended to Scaffold content is displayed in content area and BottomDrawer doesn't cover BottomAppBar when open. Moving BottomDrawer after Scaffold function doesn't help either: BottomAppBar is covered by some invisible block and prevents clicking buttons.
I've also tried using BottomSheetScaffold, but it doesn't have BottomAppBar slot.
If Scaffold doesn't support this pattern, what would be correct way to implement it? Is it possible to extend Scaffold component? I fear that incorrect implementation from scratch might create issues later, when I'll try to implement navigation and snackbar.
I think the latest version of scaffold does have a bottom app bar parameter
They (Google Devs) invite you in the Jetpack Compose Layouts pathway to try adding other Material Design Components such as BottomNavigation or BottomDrawer to their respective Scaffold slots, and yet do not give you the solution.
BottomAppBar does have its own slot in Scaffold (i.e. bottomBar), but BottomDrawer does not - and seems to be designed exclusively for use with the BottomAppBar explicitly (see API documentation for the BottomDrawer).
At this point in the Jetpack Compose pathway, we've covered state hoisting, slots, modifiers, and have been thoroughly explained that from time to time we'll have to play around to see how best to stack and organize Composables - that they almost always have a naturally expressable way in which they work best that is practically intended.
Let me get us set up so that we are on the same page:
class MainActivity : ComponentActivity() {
#ExperimentalMaterialApi
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
LayoutsCodelabTheme {
// A surface container using the 'background' color from the theme
Surface(color = MaterialTheme.colors.background) {
LayoutsCodelab()
}
}
}
}
}
That's the main activity calling our primary/core Composable. This is just like in the codelab with the exception of the #ExperimentalMaterialApi annotation.
Next is our primary/core Composable:
#ExperimentalMaterialApi
#Composable
fun LayoutsCodelab() {
val ( gesturesEnabled, toggleGesturesEnabled ) = remember { mutableStateOf( true ) }
val scope = rememberCoroutineScope()
val drawerState = rememberBottomDrawerState( BottomDrawerValue.Closed )
// BottomDrawer has to be the true core of our layout
BottomDrawer(
gesturesEnabled = gesturesEnabled,
drawerState = drawerState,
drawerContent = {
Button(
modifier = Modifier.align( Alignment.CenterHorizontally ).padding( top = 16.dp ),
onClick = { scope.launch { drawerState.close() } },
content = { Text( "Close Drawer" ) }
)
LazyColumn {
items( 25 ) {
ListItem(
text = { Text( "Item $it" ) },
icon = {
Icon(
Icons.Default.Favorite,
contentDescription = "Localized description"
)
}
)
}
}
},
// The API describes this member as "the content of the
// rest of the UI"
content = {
// So let's place the Scaffold here
Scaffold(
topBar = {
AppBarContent()
},
//drawerContent = { BottomBar() } // <-- Will implement a side drawer
bottomBar = {
BottomBarContent(
coroutineScope = scope,
drawerState = drawerState
)
},
) {
innerPadding ->
BodyContent( Modifier.padding( innerPadding ).fillMaxHeight() )
}
}
)
}
Here, we've leveraged the Scaffold exactly as the codelab in the compose pathway suggests we should. Notice my comment that drawerContent is an auto-implementation of the side-drawer. It's a rather nifty way to bypass directly using the [respective] Composable(s) (material design's modal drawer/sheet)! However, it won't work for our BottomDrawer. I think the API is experimental for BottomDrawer, because they'll be making changes to add support for it to Composables like Scaffold in the future.
I base that on how difficult it is to use the BottomDrawer, designed for use solely with BottomAppBar, with the Scaffold - which explicitly contains a slot for BottomAppBar.
To support BottomDrawer, we have to understand that it is an underlying layout controller that wraps the entire app's UI, preventing interaction with anything but its drawerContent when the drawer is open. This requires that it encompasses Scaffold, and that requires that we delegate necessary state control - to the BottomBarContent composable which wraps our BottomAppBar implementation:
#ExperimentalMaterialApi
#Composable
fun BottomBarContent( modifier: Modifier = Modifier, coroutineScope: CoroutineScope, drawerState: BottomDrawerState ) {
BottomAppBar{
// Leading icons should typically have a high content alpha
CompositionLocalProvider( LocalContentAlpha provides ContentAlpha.high ) {
IconButton(
onClick = {
coroutineScope.launch { drawerState.open() }
}
) {
Icon( Icons.Filled.Menu, contentDescription = "Localized description" )
}
}
// The actions should be at the end of the BottomAppBar. They use the default medium
// content alpha provided by BottomAppBar
Spacer( Modifier.weight( 1f, true ) )
IconButton( onClick = { /* doSomething() */ } ) {
Icon( Icons.Filled.Favorite, contentDescription = "Localized description" )
}
IconButton( onClick = { /* doSomething() */ } ) {
Icon( Icons.Filled.Favorite, contentDescription = "Localized description" )
}
}
}
The result shows us:
The TopAppBar at top,
The BottomAppBar at bottom,
Clicking the menu icon in the BottomAppBar opens our BottomDrawer, covering the BottomAppBar and entire content space appropriately while open.
The BottomDrawer is properly hidden, until either the above referenced button click - or gesture - is utilized to open the bottom drawer.
The menu icon in the BottomAppBar opens the drawer partway.
Gesture opens the bottom drawer partway with a quick short swipe, but as far as you guide it to otherwise.
You may have to do something as shown below..
Notice how the Scaffold is called inside the BottomDrawer().
It's confusing though how the documentation says "They (BottomDrawer) are only used with bottom app bars". It made me think I have to look for a BottomDrawer() slot inside Scaffold or that I have to call BottomDrawer() inside BottomAppBar(). In both cases, I experienced weird behaviours. This is how I worked around the issue. I hope it helps someone especially if you are attempting the code lab exercise in Module 5 of Layouts in Jetpack Compose from the Jetpack Compose course.
#ExperimentalMaterialApi
#Composable
fun MyApp() {
var selectedItem by rememberSaveable { mutableStateOf(1)}
BottomDrawer(
modifier = Modifier.background(MaterialTheme.colors.onPrimary),
drawerShape = Shapes.medium,
drawerContent = {
Column(Modifier.fillMaxWidth()) {
for(i in 1..6) {
when (i) {
1 -> Row(modifier = Modifier.clickable { }.padding(16.dp)){
Icon(imageVector = Icons.Rounded.Inbox, contentDescription = null)
Text(text = "Inbox")
}
2 -> Row(modifier = Modifier.clickable { }.padding(16.dp)){
Icon(imageVector = Icons.Rounded.Outbox, contentDescription = null)
Text(text = "Outbox")
}
3 -> Row(modifier = Modifier.clickable { }.padding(16.dp)){
Icon(imageVector = Icons.Rounded.Archive, contentDescription = null)
Text(text = "Archive")
}
}
}
}
},
gesturesEnabled = true
) {
Scaffold(
topBar = {
TopAppBar(
title = {
Text(text = "Learning Compose Layouts" )
},
actions = {
IconButton(onClick = { /*TODO*/ }) {
Icon(Icons.Filled.Favorite, contentDescription = null)
}
}
)
},
bottomBar = { BottomAppBar(cutoutShape = CircleShape, contentPadding = PaddingValues(0.dp)) {
for (item in 1..4) {
BottomNavigationItem(
modifier = Modifier.clipToBounds(),
selected = selectedItem == item ,
onClick = { selectedItem = item },
icon = {
when (item) {
1 -> { Icon(Icons.Rounded.MusicNote, contentDescription = null) }
2 -> { Icon(Icons.Rounded.BookmarkAdd, contentDescription = null) }
3 -> { Icon(Icons.Rounded.SportsBasketball, contentDescription = null) }
4 -> { Icon(Icons.Rounded.ShoppingCart, contentDescription = null) }
}
}
)
}
}
}
) { innerPadding -> BodyContent(
Modifier
.padding(innerPadding)
.padding(8.dp))
}
}
}
I have a BottomNavBar which is called inside the bottomBar of a Scaffold.
Every screen contains a LazyColumn which displays a bunch of images.
For some reason when I finish scrolling, the BottomNavBar overlaps the lower part of the items list.
How can I fix this?
Here the set content of MainActivity
SetContent{
Scaffold(
topBar = {
TopAppBar(
title = { Text(text = "tartufozon") },
actions = {
IconButton(onClick = { Timber.d("Mail clicked") }) {
Icon(Icons.Default.Email, contentDescription = null)
}
}
)
},
bottomBar = {
BottomNavBar(navController = navController)
}
) {
BottomNavScreensController(navController = navController)
}
}
As per the API definition for Scaffold, your inner content (the trailing lambda you have your BottomNavScreensController in), is given a PaddingValues object that gives you the right amount of padding for your top app bar, bottom bar, etc.
Right now, you're not referencing that padding at all and hence, your content is not padded in. This is what is causing your overlap.
You can add a Box around your BottomNavScreensController to apply the padding, or pass the padding directly into your BottomNavScreensController so that each individual screen can correctly apply the padding; either solution works.
Scaffold(
topBar = {
//
},
bottomBar = {
//
}
) { innerPadding ->
// Apply the padding globally to the whole BottomNavScreensController
Box(modifier = Modifier.padding(innerPadding)) {
BottomNavScreensController(navController = navController)
}
}
}
Following ianhanniballake's answer and its comments and just to save you few minutes. The code would be something like:
Scaffold(
topBar = {
//
},
bottomBar = {
//
}
) { innerPadding ->
Box(modifier = Modifier.padding(
PaddingValues(0.dp, 0.dp, 0.dp, innerPadding.calculateBottomPadding()) {
BottomNavScreensController(navController = navController)
}
}
}
You no longer need to do any calculations. In the scaffold content, do:
content = { padding ->
Column(
modifier = Modifier.padding(padding)
) {...
Scaffold(
bottomBar = {
BottomNavigationBar(navController = navController)
}
) { innerPadding ->
Box(modifier = Modifier.padding(innerPadding)) {
DestinationsNavHost(
navGraph = NavGraphs.root,
navController = navController,
engine = navHostEngine
)
}
}
Add your layouts and views in content with its padding like in the example:
Scaffold(
content = {
Box(modifier = Modifier.padding(it)) {
//add your layout here
}
},
topBar = {
//your top bar
},
floatingActionButton = {
//your floating action bar
},
bottomBar = {
//your bottom navigation
}
)
Following ianhanniballake answer, if you have a list in your main screen and want to show/hide the bottom bar. It will perform unexpected behavior when you back to list screen (the scroll state is in very bottom) and show the navigation bar.The list item will scroll up a bit which cause the list doesn't fully scrolled to the bottom.
This is because of when you back to the list screen and want to show the bottom bar, the list already there. Then the bottom bar will show and list will not calculate the innerPadding for more.
To handle this, just pass the innerPadding into the list screen like this:
Scaffold(
bottomBar = {
BottomNavBar(navController)
}) { innerPadding ->
NavHost(
navController = navController,
startDestination = MovieListDirections.destination
) {
MovieListDirections.screenWithPaddingBottomBar(
this,
innerPadding.calculateBottomPadding() // pass here
)
}
Then in the list screen
#Composable
fun MovieListMainView(viewModel: MovieListViewModel = hiltViewModel(),
bottomBarHeight: Float) {
val movieList by viewModel.movieList.collectAsState()
var navBarHeight by rememberSaveable { mutableStateOf(0f) }
if (bottomBarHeight != 0f) {
navBarHeight = bottomBarHeight
}
MovieListLayout().MovieGridLayout(
modifier = Modifier.padding(0.dp, 0.dp, 0.dp, navBarHeight.dp),
movies = movieList.movieList.movieItems,
onItemClick = {
viewModel.onMovieClicked(it)
})
}