I have setup the toolbar title in NavGraph with
`private void setUpNavigationToolbar(){
final NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.container_fragment;
NavController navController = navHostFragment.getNavController();
AppBarConfiguration appBarConfiguration =
new AppBarConfiguration.Builder(navController.getGraph()).build();
NavigationUI.setupWithNavController(
toolbar, navController, appBarConfiguration);
}`
and toolbar title is set using
android:label="Sample title" inside the Nav graph
When the fragment is launched the title appears and disappear in a moment.
I am looking for a solution for the toolbar title persists even after launching the fragment
Related
I am using navigation-compose along with bottom bar in jetpack compose. I want to show different bottom bar for different type of user. For that, I need to set conditional startDestination in NavHost. How do i do that?
I've tried below but it change startDestination but does not reflect in UI.
val user by remember { mutableStateOf(viewModel.user) }.collectAsState()
var startDestination = Screens.UserType1.route
LaunchedEffect(user) {
startDestination = if (loggedInUser?.userType == UserType.Seller) Screens.SellerHome.route else Screens.BuyerHome.route
}
While below code throws java.util.NoSuchElementException: List contains no element matching the predicate.
when (user?.userType) {
UserType.Seller -> {
startDestination = Screens.SellerHome.route
}
UserType.Buyer -> {
startDestination = Screens.BuyerHome.route
}
else -> {}
}
My NavHost
NavHost(
modifier = modifier,
navController = navController,
startDestination = startDestination
) {
...
}
Change this
var startDestination = Screens.UserType1.route
To this
var startDestination by remember { mutableStateOf(Screens.UserType1.route) }
Before Jetpack Compose I was using Navigation Component in the projects in View system world.
Apps had only one activity - toolbar, bottom bar and drawer were added only to this activity once.
Apps could have many screens (fragments) and only top destination fragments were displaying bottom bar and allowed drawer, for other fragments it was hidden.
All of that was handled with Navigation Component like this from the activity:
fun initNavigation() {
val topLevelDestinationFragments = setOf(R.id.homeFragment, R.id.photosFragment)
appBarConfiguration = AppBarConfiguration(
topLevelDestinationFragments,
binding.drawerLayout
)
setupActionBarWithNavController(navController, appBarConfiguration)
binding.drawerNavigationView.setupWithNavController(navController)
binding.bottomNavigationView.setupWithNavController(navController)
navController.addOnDestinationChangedListener { _, destination, _ ->
// don't change bars if a dialog fragment
if (destination is FloatingWindow) {
return#addOnDestinationChangedListener
}
// Google solution to hide navigation bars
// https://developer.android.com/guide/navigation/navigation-ui#listen_for_navigation_events
val allowBottomAndDrawerNavigation = destination.id in topLevelDestinationFragments
binding.bottomNavigationView.isVisible = allowBottomAndDrawerNavigation
binding.drawerLayout.setDrawerLockMode(
if (allowBottomAndDrawerNavigation) {
DrawerLayout.LOCK_MODE_UNLOCKED
} else {
DrawerLayout.LOCK_MODE_LOCKED_CLOSED
}
)
binding.toolbar.isVisible = // if we need to hide toolbar for specific fragments
}
}
override fun onSupportNavigateUp(): Boolean {
return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
}
Quite easy, no need to add separately toolbar for each fragment and so on (so it was added only to the layout of the activity, it wasn't added to each layout of each fragment)
Navigation Component automatically handles back and menu (for drawer) buttons on the toolbar, automatically switches between them because it knows top destinations
Is there something similar for Compose?
Because I checked official Google sample "JetNews" from CodeLabs git https://github.com/googlecodelabs/android-compose-codelabs/tree/end/AccessibilityCodelab/app/src/main/java/com/example/jetnews/ui
They use compose navigation there but they separately added Scaffold for each compose screen.
For example "Home" compose screen has it own Scaffold
Scaffold(
scaffoldState = scaffoldState,
topBar = {
val title = stringResource(id = R.string.app_name)
InsetAwareTopAppBar(
title = { Text(text = title) },
navigationIcon = {
IconButton(onClick = { coroutineScope.launch { openDrawer() } }) {
Icon(
painter = painterResource(R.drawable.ic_jetnews_logo),
contentDescription = stringResource(R.string.cd_open_navigation_drawer)
)
}
}
)
}
)
And "Article" compose screen has it own Scaffold
Scaffold(
topBar = {
InsetAwareTopAppBar(
title = {
Text(
text = "Published in: ${post.publication?.name}",
style = MaterialTheme.typography.subtitle2,
color = LocalContentColor.current
)
},
navigationIcon = {
IconButton(onClick = onBack) {
Icon(
imageVector = Icons.Filled.ArrowBack,
// Step 4: Content descriptions
contentDescription = stringResource(
R.string.cd_navigate_up
)
)
}
}
)
}
)
So basically here we duplicate the code and define manually different logic for navigationIcon (icon and action) of toolbar
Does it mean that if we have some details screens with back arrow button and without bottom bar then we define a separate Scaffold and can't just use one Scaffold for all compose screens of the app?
Or can we implement the same logic as well for all compose screens as we did with Navigation Component in View system? Also setting top destinations, hiding bottom bar and locking drawer for non top destinations.
I guess the correct way is to set top level Scaffold (where we define theme and navigation) with AppDrawer and BottomBar and add some logic if it should be added to a screen by checking the current navigation route:
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
e.g.:
drawerContent = if (isTopLevelDestination) {
{
AppDrawer(
...
)
}
} else {
null
},
bottomBar = {
if (isTopLevelDestination) {
AppBottomBar(
...
)
}
}
But with TopAppBar it's not that easy. We can't really add it to top level Scaffold in case when toolbar can have actions for specific screens
That's why each screen additionally defines its Scaffold with TopAppBar configured as needed.
Only if an app is very simple and toolbar doesn't have any actions for all screens of the app, just hardcoded titles for all screens, then we can define TopAppBar in top level Scaffold but for more complicated apps each screen should define its own toolbar
In this guide, it says:
AppBarConfiguration appBarConfiguration =
new AppBarConfiguration.Builder(navController.getGraph())
.setDrawerLayout(drawerLayout)
.build();
but it does not say how navController is obtained. In the next paragraph, it says
call setupWithNavController() from your main activity's onCreate() method, as shown below:
NavHostFragment navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment);
NavController navController = navHostFragment.getNavController();
NavigationView navView = findViewById(R.id.nav_view);
NavigationUI.setupWithNavController(navView, navController);
which indicates that the 2 snippets are in different files. So where should I place the first snippet (initializing appBarConfiguration)? Currently, I put them all in my main activity, and it seems to work fine.
you have to attach the appBarConfiguration to the NavigationUI with NavController using setupActionBarWithNavController() method. Code below shows an example,
AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(
R.id.navigation_home, R.id.navigation_dashboard, R.id.navigation_notifications)
.build();
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
setupWithNavController() method is to attach NavigationView to NavigationUI
im trying to remove the hamburguer icon because I already have one in my custom Toolbar, for remove it I have this on the activity:
Toolbar toolbar = findViewById(R.id.toolbar_main);
setSupportActionBar(toolbar);
final DrawerLayout drawer = findViewById(R.id.drawer_layout);
NavigationView navigationView = findViewById(R.id.nav_view);
// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
mAppBarConfiguration = new AppBarConfiguration.Builder(
R.id.navigation_dieta, R.id.navigation_calendario, R.id.navigation_alimentos, R.id.navigation_perfil)
.setDrawerLayout(drawer)
.build();
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
NavigationUI.setupActionBarWithNavController(this, navController, mAppBarConfiguration);
NavigationUI.setupWithNavController(navigationView, navController);
BottomNavigationView bottomNavigationView = findViewById(R.id.bottom_nav_view);
NavigationUI.setupWithNavController(bottomNavigationView, navController);
toolbar.setNavigationIcon(null);
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
getSupportActionBar().setHomeButtonEnabled(false);
drawerIcon = findViewById(R.id.toolbar_main_menu_icon);
drawerIcon.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
drawer.openDrawer(GravityCompat.START);
}
});
The icon is hidden at startup, but when I select a menu item it reappears.
After more than 10 hours I found the answer, just delete this:
NavigationUI.setupActionBarWithNavController(this, navController, mAppBarConfiguration);
my image
Hello guys
I use templater Bottom Navigation Activity and dont know how to scroll to top RecyclerView in my Home Fragment
My MainActivity code:
AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(
R.id.navigation_home, R.id.navigation_dashboard, R.id.navigation_notifications)
.build();
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
NavigationUI.setupWithNavController(navView, navController);
The question is quit old, but maybe it helps others.
This is the code you have to put in the fragment (in Kotlin):
val nav = activity?.findViewById<BottomNavigationView>(R.id.bottom_navigation)
nav?.setOnItemReselectedListener { item ->
if (item.itemId == R.id.mainFragment) {
recyclerView.smoothScrollToPosition(position)
}
}