Jetpack Compose TopAppBar flickers after setting BottomNavView with Navigation component - android

Working on a Jetpack Compose app with a BottomNavigationView and Navigation component, I did the usual setup for the bottom bar inside the activity:
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
val navController = navHostFragment.navController
navigationView.setupWithNavController(navController)
In each screen I am using a composable TopAppBar.
Unfortunately, after adding the setupWithNavController call, the TopAppBar started flickering in all the navigation bar destinations (but not in deeper destinations).
How can I fix the flickering?

I got the same issue. What helped, to my surprise, was lowering androidx.navigation:navigation-compose version to 2.4.0-alpha04.

I cannot answer you exactly how to fix the flickering, but I can tell you that the recommended way to work with a Navigation component, TopBars and BottomBars in Jetpack Compose, is to use the Scaffold component and the navigation-compose component.
Compose Navigation component dependency:
// Navigation
implementation "androidx.navigation:navigation-compose:1.0.0-alpha09"
It is still in alpha, but so far it has been working really well. Just try to keep posted about all updates.
Example usage:
#Composable
fun App() {
val navController = rememberNavController()
Scaffold(
topBar = {
TopAppBar {
Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) {
Text(text = "App Title")
}
}
},
bottomBar = {
BottomAppBar {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.CenterVertically
) {
TextButton(onClick = { navController.navigate("home") }) {
Text(text = "Home")
}
TextButton(onClick = { navController.navigate("favorites") }) {
Text(text = "Favorites")
}
TextButton(onClick = { navController.navigate("profile") }) {
Text(text = "Profile")
}
}
}
}
) {
NavHost(navController, startDestination = "home") {
composable("home") {
Home()
}
composable("favorites") {
Favorites()
}
composable("profile") {
Profile()
}
}
}
}

Related

Should I use Scaffold in every screen ? what are best practices while using topBar, bottomBar, drawer, etc. in compose

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.

Jetpack Compose - Navigation - Scaffold + NavHost not working

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.

How do I make Bottom Sheet cover whole screen in Jetpack Compose

I am using Jetpack Compose and trying to make a Login Screen cover the whole screen when the user clicks the login button in the TopAppBar.
I am using a combination of ModalBottomSheetLayout and a Scaffold so I can have a TopAppBar and a BottomAppBar.
Currently when the login screen is displayed it only covers half of the screen.
val coroutineScope = rememberCoroutineScope()
val bottomState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
ModalBottomSheetLayout(
sheetState = bottomState,
sheetShape = MaterialTheme.shapes.large,
sheetContent = {
FullScreen()
}
) {
Scaffold(
topBar = {
TopAppBar(
...
content = {
NavHost(navController = navController,
startDestination = "journey") {
composable("journey") { JourneyScreen() }
...
bottomBar = {
BottomAppBar(
content = {
BottomNavigation() {
val navBackStackEntry by navController.currentBackStackEntryAsState()
...
#Composable
fun FullScreen() {
Box(modifier = Modifier
.fillMaxSize()
) {
Text("Full Screen")
}
}
Have been stuck on this for way too long and any help is appreciated.
To have a fullscreen ModalBottomSheetLayout, instead of state.show() use:
scope.launch { state.animateTo(ModalBottomSheetValue.Expanded) }

Cannot find a way to use both at same time, bottom sheet and bottom navigation bar in android compose

In android compose, there is Scaffold composable function to create a layout that contains bottom navigation bar and there is another function named BottomSheetScaffold to create a layout that contains bottom navigation bar.
My question is how to achieve both the bottom sheet and Bottom Navigation bar in same layout?
I treid using BottomSheetScaffold and adding the bottom navigation bar in the layout but I failed when I used NavHost besides it in the same column.
Simplified code for the case:
BottomSheetScaffold(
{ BottomSheetComposable },
topBar = {
TopAppBar()
},
sheetPeekHeight = 0.dp
) {
Column() {
NavHost(
navController,
startDestination = "route"
) {
Composable("route") {}
}
Box(
modifier = Modifier
.fillMaxWidth()
.height(60.dp)
) {
BottomNavigationBar()
}
}
}
A temporary solution would to use scaffold inside BottomSheetScaffold content
BottomSheetScaffold(
sheetShape = BottomSheetShape,
sheetContent = {
currentBottomSheetScreen?.let {
MainBottomSheetLayout(
it, navController, textToSpeech,
closeSheet
)
}
}) {
Scaffold(
topBar = { LinguisticTopAppBar(navController, mainViewModel, openSheet) },
bottomBar = {
BottomNavigationBar(mainViewModel, navController)
},
) {
Column(modifier = Modifier.padding(it)) {
MainContent(
navController,
mainViewModel,
openSheet
)
}
}
}

What is Scaffold? Jetpack compose

I want to know what is Scaffold in jetpack compose with a BottomAppBar example can anyone help me
Scaffold
Scaffold allows you to implement a UI with the basic Material Design layout structure. Scaffold provides slots for the most common top-level Material components, such as TopAppBar, BottomAppBar, FloatingActionButton, and Drawer.
Something like:
val scaffoldState = rememberScaffoldState()
// Create a coroutineScope for the animation
val coroutineScope = rememberCoroutineScope()
Scaffold(
scaffoldState = scaffoldState,
drawerContent = { Text("Drawer content") },
bottomBar = {
BottomAppBar(cutoutShape = CircleShape) {
IconButton(
onClick = {
coroutineScope.launch { scaffoldState.drawerState.open() }
}
) {
Icon(Icons.Filled.Menu, contentDescription = "....")
}
}
},
floatingActionButton = {
ExtendedFloatingActionButton(
text = { Text("Action") },
onClick = { /* .... */ }
)
},
floatingActionButtonPosition = FabPosition.Center,
isFloatingActionButtonDocked = true,
content = { innerPadding ->
//....
}
)

Categories

Resources