I have screen like below which contain a navigation drawer and bottom navigation on same screen:
I am using Jetpack Navigation Architecture Component.
Current issue and What I have tried?
Clicking on the 2nd and 3rd bottom nav item shows back arrow on toolbar?
Tried: setting fragments associated with 2nd and 3rd bottom nav to top level destinations
appBarConfig = AppBarConfiguration(setOf(R.layout.fragment_star, R.layout.fragment_stats, R.layout.fragment_user))
instead of
appBarConfig = AppBarConfiguration(navController.graph, drawerLayout)
Did not worked.
Any help highly appreciated!
My code look like below.
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.drawerlayout.widget.DrawerLayout
android:id="#+id/drawerLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar"/>
<fragment
android:id="#+id/navHostFragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
app:defaultNavHost="true"
app:navGraph="#navigation/nav_graph" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/bottomNav"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="?android:attr/windowBackground"
app:menu="#menu/menu_bottom" />
</LinearLayout>
<!-- gives navDrawer material look-->
<com.google.android.material.navigation.NavigationView
android:id="#+id/navView"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
app:menu="#menu/nav_drawer_menu"
app:headerLayout="#layout/nav_header"
android:fitsSystemWindows="true"
/>
</androidx.drawerlayout.widget.DrawerLayout>
</layout>
menu_bottom.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="#+id/starFragment"
android:icon="#drawable/ic_star_green_48dp"
android:title="#string/bottom_nav_title_star"/>
<item
android:id="#+id/statsFragment"
android:icon="#drawable/ic_stats_green_48dp"
android:title="#string/bottom_nav_title_stats"/>
<item
android:id="#+id/userFragment"
android:icon="#drawable/ic_user_green_48dp"
android:title="#string/bottom_nav_title_user"/>
</menu>
nav_graph.xml
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" android:id="#+id/nav_graph_main"
app:startDestination="#id/starFragment">
<fragment
android:id="#+id/starFragment"
android:name="com.example.app.ui.StarrFragment"
android:label="Star"
tools:layout="#layout/fragment_star">
</fragment>
<fragment
android:id="#+id/statsFragment"
android:name="com.example.app.StatsFragment"
android:label="fragment_stats"
tools:layout="#layout/fragment_stats" />
<fragment
android:id="#+id/userFragment"
android:name="com.example.app.UserFragment"
android:label="fragment_user"
tools:layout="#layout/fragment_user" />
</navigation>
ActivityMain.kt
class MainActivity : AppCompatActivity() {
private lateinit var drawerLayout: DrawerLayout
private lateinit var appBarConfig: AppBarConfiguration
private lateinit var navController: NavController
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
setSupportActionBar(toolbar)
drawerLayout = binding.drawerLayout
navController = this.findNavController(R.id.navHostFragment)
binding.bottomNav.setupWithNavController(navController)
NavigationUI.setupActionBarWithNavController(this, navController, drawerLayout)
appBarConfig = AppBarConfiguration(navController.graph, drawerLayout)
// lock drawer when not in start destination
navController.addOnDestinationChangedListener { nc, nd, _ ->
if(nd.id == nc.graph.startDestination){
drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
}
else{
drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
}
}
NavigationUI.setupWithNavController(binding.navView, navController)
}
override fun onSupportNavigateUp(): Boolean {
// replace navigation up button with nav drawer button when on start destination
return NavigationUI.navigateUp(navController, appBarConfig)
}
}
No need of writing separate code to replace the back button with the drawer icon.
In AppBarConfiguration pass the fragment ids (from nav_graph) which you are using to navigate from both bottom navigation & navigation drawer. (P.S fragments and its associated icon should have same ids)
For your case the AppBarConfiguration should look like this :
appBarConfig = AppBarConfiguration.Builder(R.id.starFragment, R.id.statsFragment, R.id.userFragment)
.setDrawerLayout(drawerLayout)
.build()
Setup the action bar :
setSupportActionBar(toolbar)
setupActionBarWithNavController(navController, appBarConfig)
Now setup navcontroller for both bottom navigation & navigation view :
navView.setupWithNavController(navController)
bottomNav.setupWithNavController(navController)
onSupportNavigateUp function should should be :
override fun onSupportNavigateUp(): Boolean {
return navController.navigateUp(appBarConfig)
}
Back button press should be handled if drawer is open :
override fun onBackPressed() {
if (drawerLayout.isDrawerOpen(GravityCompat.START)) {
drawerLayout.closeDrawer(GravityCompat.START)
} else {
super.onBackPressed()
}
}
Bonus
By default when you click bottom navigation icon in the order icon_1 then icon_2 then icon_3 and from there you press back button it will navigate back to home icon that's icon_1
If you want to navigate back in the reverse order in which you have clicked the icons (back stack manner) then add android:menuCategory="secondary" to the item in the menu. So your menu will be like :
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="#+id/starFragment"
android:icon="#drawable/ic_star_green_48dp"
android:title="#string/bottom_nav_title_star"
android:menuCategory="secondary"/>
<item
android:id="#+id/statsFragment"
android:icon="#drawable/ic_stats_green_48dp"
android:title="#string/bottom_nav_title_stats"
android:menuCategory="secondary"/>
<item
android:id="#+id/userFragment"
android:icon="#drawable/ic_user_green_48dp"
android:title="#string/bottom_nav_title_user"
android:menuCategory="secondary"/>
</menu>
Hope the back button icon will be solved now :)
This project uses DrawerLayout and simulates BottomNavigationView using RadioButtons, this is the way I found to solve the problem
In the Google Codelab navigation project they do what Adithya T Raj mention but it only serves to show the DrawerLayout on landscape and BottomNavigationView on Portrait. The project link:
I try to force them to show me both on this branch but only the DrawerLayout is shown
First off all it is not a good design approach. If you want to something like that you should use bottom app bar (not bottom navigation view with navigation drawer)
Here is the guide for you : Article Link
You can easily adapt your app with that.
Related
I am attempting to use NavigationUI to create a navigation drawer in my app. I am trying to follow the Android Developers implementation at the above link to a tee. However, one issue I am facing is that I am unable to get the hamburger button in the action bar to open/close my navigation drawer. This probably has to do with the way I override onSupportNavigateUp() in my CategoryActivity, but I'm unsure how to fix.
I hate to throw this much code at someone, but can someone with experience with the API take a look if I am missing something? Much appreciated. Thanks.
My navigation graph (nav_graph_category.xml):
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/nav_graph_category"
app:startDestination="#id/people_fragment">
<fragment
android:id="#+id/people_fragment"
android:name="com.davidread.starwarsdatabase.view.EmptyFragment"
android:label="#string/people_category_label" />
<fragment
android:id="#+id/films_fragment"
android:name="com.davidread.starwarsdatabase.view.EmptyFragment"
android:label="#string/films_category_label" />
<fragment
android:id="#+id/starships_fragment"
android:name="com.davidread.starwarsdatabase.view.EmptyFragment"
android:label="#string/starships_category_label" />
<fragment
android:id="#+id/vehicles_fragment"
android:name="com.davidread.starwarsdatabase.view.EmptyFragment"
android:label="#string/vehicles_category_label" />
<fragment
android:id="#+id/species_fragment"
android:name="com.davidread.starwarsdatabase.view.EmptyFragment"
android:label="#string/species_category_label" />
<fragment
android:id="#+id/planets_fragment"
android:name="com.davidread.starwarsdatabase.view.EmptyFragment"
android:label="#string/planets_category_label" />
</navigation>
My drawer menu (drawer_menu_category.xml):
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:title="#string/categories_label">
<menu>
<group android:checkableBehavior="single">
<item
android:id="#+id/people_category_action"
android:title="#string/people_category_label" />
<item
android:id="#+id/films_category_action"
android:title="#string/films_category_label" />
<item
android:id="#+id/starships_category_action"
android:title="#string/starships_category_label" />
<item
android:id="#+id/vehicles_category_action"
android:title="#string/vehicles_category_label" />
<item
android:id="#+id/species_category_action"
android:title="#string/species_category_label" />
<item
android:id="#+id/planets_category_action"
android:title="#string/planets_category_label" />
</group>
</menu>
</item>
</menu>
My main layout (activity_category.xml):
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".view.CategoryActivity">
<androidx.drawerlayout.widget.DrawerLayout
android:id="#+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<androidx.fragment.app.FragmentContainerView
android:id="#+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="#navigation/nav_graph_category" />
<com.google.android.material.navigation.NavigationView
android:id="#+id/navigation_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
app:menu="#menu/drawer_menu_category" />
</androidx.drawerlayout.widget.DrawerLayout>
</layout>
My main activity (activity_category.xml):
package com.davidread.starwarsdatabase.view
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.NavController
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.*
import com.davidread.starwarsdatabase.R
import com.davidread.starwarsdatabase.databinding.ActivityCategoryBinding
class CategoryActivity : AppCompatActivity() {
private val binding: ActivityCategoryBinding by lazy {
ActivityCategoryBinding.inflate(layoutInflater)
}
private val navController: NavController by lazy {
val navHostFragment: NavHostFragment =
supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
navHostFragment.navController
}
private val appBarConfiguration: AppBarConfiguration by lazy {
AppBarConfiguration(navController.graph, binding.drawerLayout)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_category)
setupActionBarWithNavController(navController, appBarConfiguration)
binding.navigationView.setupWithNavController(navController)
}
override fun onSupportNavigateUp(): Boolean {
return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
}
}
A snippet from my build.gradle dependencies, in case it's a version thing:
dependencies {
// Material Components library. Contains modular and customizable Material Design UI components.
implementation 'com.google.android.material:material:1.7.0'
// Android Jetpack's Navigation API. Required for navigation drawer.
implementation "androidx.navigation:navigation-fragment-ktx:2.5.3"
implementation "androidx.navigation:navigation-ui-ktx:2.5.3"
}
Lastly, some screenshots:
Note, I am able to open the navigation drawer by swiping from the left side of the screen as expected. That is why navigation drawer is open in second screenshot.
Your problem is with this line:
setContentView(R.layout.activity_category)
What that means is that you are inflating a brand new copy of R.layout.activity_category - you aren't actually using your binding variable at all, which means that the binding.drawerLayout you've set on your AppBarConfiguration isn't actually the DrawerLayout that you've set as your content view.
Instead, you'll want to actually set your binding as the content view of your activity by using the code from the data binding documentation:
setContentView(binding.root)
Having passed through multiple solutions on this site, while being new to navigation Fragments and Android in general I have this issue:
I had 4 fragments that worked well together with the bottom navbar making the navigation;
I added a favourite slides button on HomeFragment that leads to a FavouriteSlides Fragment that works;
From the FavouriteSlides I still can access all 3 buttons, except Home. When I click the Home icon nothing happens. ( wrong)
From all the other Fragments I cannot access Home Fragment because it is replaced by Favourite Slides Fragment ( wrong)
BackArrow works Ok and all the Sliders are pointing ok to Home Fragment on Back Button pressed.
mobile_navigation.xml
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/mobile_navigation"
app:startDestination="#id/navigation_home">
<fragment
android:id="#+id/navigation_home"
android:name="com.example.spiritualvietnam.ui.fragments.HomeFragment"
android:label="HomeFragment"
tools:layout="#layout/home">
<action
android:id="#+id/action_navigation_home_to_favouritesFragment"
app:destination="#id/favouritesFragment" />
</fragment>
<fragment
android:id="#+id/navigation_places"
android:name="com.example.spiritualvietnam.ui.fragments.PlacesFragment"
android:label="PlacesFragment"
tools:layout="#layout/fragment_places_recyclerview">
<action
android:id="#+id/action_navigation_places_to_navigation_home"
app:destination="#id/navigation_home" />
</fragment>
<fragment
android:id="#+id/navigation_settings"
android:name="com.example.spiritualvietnam.ui.fragments.SettingsFragment"
android:label="SettingsFragment"
tools:layout="#layout/fragment_settings">
<action
android:id="#+id/action_navigation_settings_to_navigation_home"
app:destination="#id/navigation_home" />
</fragment>
<fragment
android:id="#+id/navigation_sliders"
android:name="com.example.spiritualvietnam.ui.fragments.SlidingPhotosFragment"
android:label="SlidingPhotosFragment"
tools:layout="#layout/fragment_sliding_photos">
<action
android:id="#+id/action_navigation_sliders_to_navigation_home"
app:destination="#id/navigation_home" />
</fragment>
<fragment
android:id="#+id/favouritesFragment"
android:name="com.example.spiritualvietnam.ui.fragments.FavouritesFragment"
android:label="FavouritesFragment" >
<action
android:id="#+id/action_favouritesFragment_to_navigation_home"
app:destination="#id/navigation_home" />
</fragment>
</navigation>
bottom_nav_menu.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="#+id/navigation_home"
android:icon="#drawable/ic_home_icon"
android:title="#string/home" />
<item
android:id="#+id/navigation_sliders"
android:icon="#drawable/ic_lampion_play"
android:title="#string/sliders" />
<item
android:id="#+id/navigation_places"
android:icon="#drawable/ic_baseline_place_24"
android:title="#string/places" />
<item
android:id="#+id/navigation_settings"
android:icon="#drawable/ic_baseline_add_alert_24"
android:title="#string/add_alerts" />
</menu>
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/nav_view"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
android:background="#drawable/gradient4navbar"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:menu="#menu/bottom_nav_menu" />
<fragment
android:id="#+id/nav_host_fragment_activity_main"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="#navigation/mobile_navigation" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Main Activity
class MainActivity : AppCompatActivity() {
// Binding nu uita - baga click pe layout la becul galben si zi sa adauge data binding k omu
private lateinit var binding: ActivityMainBinding
lateinit var tinyDBMain: TinyDB
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
requestWindowFeature(Window.FEATURE_NO_TITLE)
this.window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN)
supportActionBar?.hide() // hides title and stuff
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val navView: BottomNavigationView = binding.navView
val navController = findNavController(R.id.nav_host_fragment_activity_main)
val appBarConfiguration = AppBarConfiguration(
setOf(
R.id.navigation_home, R.id.navigation_sliders, R.id.navigation_places, R.id.navigation_settings,
)
)
setupActionBarWithNavController(navController, appBarConfiguration)
navView.setupWithNavController(navController)
// works here for all the fragments
tinyDBMain = TinyDB(this)
}
It's ok to answer in Java as well if you know the answer.
If I should include more code I will I wanted to keep it as short as I could.
Thank you
Your appBarConfiguration and your menu items might be the reason why its not working you need to have your favoritesFragment id there, like this:
val appBarConfiguration = AppBarConfiguration(
setOf(
R.id.navigation_home,
R.id.navigation_sliders,
R.id.navigation_places,
R.id.favouritesFragment
)
)
and like this:
<item
android:id="#+id/favouritesFragment"
android:icon="#drawable/ic_baseline_add_alert_24"
android:title="#string/add_alerts" />
Generally the id from the fragments inside the navigation graph and inside the menu items has to be the same so that it works properly.
You also don't need to have actions inside your navigation graph, since the library, when usign a bottom navigaiton will automatically create the navigation, by using this configuration.
Let me know if this worked for you.
I have found out that making the navbar disappear in the FavouritesFragment (that hasn't got an Icon in the navbar) is the best solution.
I implemented in Main Activity onCreate() the custom navbar behaviour for all the Fragments.
The answer that indirectly helped me, after days of searching, is here: how to hide BottomNavigationView on android-navigation lib
How do I set up navigation component with navigation drawer?
How do I use it in my app?
Can everything be done with one Activity?
How do I handle toolbar visibility with just one Activity and fragments which have a dynamic toolbar visibility. Also, there are fragments which I need to close the drawer and make it inaccessible.
This question is a self answered question and works more as a tutorial than a real-life QA.
How do I set up navigation component with navigation drawer?
How do I use it in my app?
The navigation drawer set up differs a little when it comes to Navigation component.
Note, if you create a new app with drawer navigation, the current tutorial is not needed. However I'm going to explain some things that might look strange around here and if you decide to add a drawer at a later stage of the app
First, you need to set up your activity_main.xml and MainActivity to be ready for the Navigation Architecture:
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:openDrawer="start">
<include
layout="#layout/app_bar_main"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.google.android.material.navigation.NavigationView
android:id="#+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
app:headerLayout="#layout/nav_header_main"
app:menu="#menu/activity_main_drawer" />
</androidx.drawerlayout.widget.DrawerLayout>
where app_bar_main is just:
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/AppTheme.AppBarOverlay">
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="#style/AppTheme.PopupOverlay" />
</com.google.android.material.appbar.AppBarLayout>
<include layout="#layout/content_main" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
And the content_main is where your fragments are going to be held:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
tools:showIn="#layout/app_bar_main">
<fragment
android:id="#+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="#navigation/mobile_navigation" />
</androidx.constraintlayout.widget.ConstraintLayout>
Things you should know: The activity mustn't have a AppBar set on AndroidManifest.xml:
android:theme="#style/AppTheme.NoActionBar"
If you notice app:menu="#menu/activity_main_drawer" in the NavigationView tag, the fragment names should be the same that they are inside the mobile_navigation.xml:
<group android:checkableBehavior="single">
<item
android:id="#+id/homeFragment"
android:icon="#drawable/ic_menu_camera"
android:title="#string/menu_home" />
<item
android:id="#+id/galleryFragment"
android:icon="#drawable/ic_menu_gallery"
android:title="#string/menu_gallery" />
<item
android:id="#+id/slideshowFragment"
android:icon="#drawable/ic_menu_slideshow"
android:title="#string/menu_slideshow" />
<item
android:id="#+id/toolsFragment"
android:icon="#drawable/ic_menu_manage"
android:title="#string/menu_tools" />
</group>
<item android:title="Communicate">
<menu>
<item
android:id="#+id/shareFragment"
android:icon="#drawable/ic_menu_share"
android:title="#string/menu_share" />
<item
android:id="#+id/sendFragment"
android:icon="#drawable/ic_menu_send"
android:title="#string/menu_send" />
</menu>
</item>
</menu>
This way, with what is going to be explained below there will be no need to call for onCreateOptionsMenu to detect the clicks. Android team has already solved that for us. Follow below.
Until now, this doesn't differ too much from the usual drawer set up we actually do. But, there are some configurations we would need to do in the logic part of the app. So let's open MainActivity.kt. First you will need these:
private var appBarConfiguration: AppBarConfiguration? = null
private var drawerLayout: DrawerLayout? = null
private var toolbar: Toolbar? = null
private var navController: NavController? = null
After that in your onCreate method:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
toolbar = findViewById(R.id.toolbar)
setSupportActionBar(toolbar) //set the toolbar
drawerLayout = findViewById(R.id.drawer_layout)
val navView: NavigationView = findViewById(R.id.nav_view)
navController = findNavController(R.id.nav_host_fragment)
appBarConfiguration = AppBarConfiguration(
setOf(
R.id.homeFragment,
R.id.galleryFragment,
R.id.slideShowFragment,
R.id.toolsFragment,
R.id.shareFragment,
R.id.sendFragment,
R.id.loginFragment,
R.id.phoneConfirmationFragment
), drawerLayout
)
setupActionBarWithNavController(navController!!, appBarConfiguration!!) //the most important part
navView.setupWithNavController(navController!!) //the second most important part
//other things unrelated
}
Let's see what's going on here:
First you would need a reference to the navController. The AppBarConfiguration is just a class which holds the fragments that are going to be opened as top level destinations. Which means that the fragment which is going to be opened after them would release the current one from the fragment back stack. It's important to tell to the AppBarConfiguration that we have a drawer also (passed as a parameter in the constructor).
Down below you would have a method called onSupportNavigateUp():
override fun onSupportNavigateUp(): Boolean {
val navController = findNavController(R.id.nav_host_fragment)
return navController.navigateUp(appBarConfiguration!!) || super.onSupportNavigateUp()
}
This method has to do with the up back button. But you won't need it too much if you have drawer navigation. This really comes in handy when you have a lot of fragments added on the backstack (or at least two).
Can everything be done with one Activity?
Yes, definitely! but still it requires a little bit more work when it comes to conditional navigation. Like when you want to show fragments which are not part of your drawer app. But still Google has done a huge progress with it. You can refer to conditional navigation here.
How do I handle toolbar visibility with just one Activity and
fragments which have a dynamic toolbar visibility. Also, there are
fragments which I need to close the drawer and make it inaccessible.
You can use addOnDestinationChangedListener from the navController:
navController.addOnDestinationChangedListener { _, destination, _ ->
when (destination.id) {
R.id.loginFragment, R.id.registerFragment, R.id.phoneConfirmationFragment -> {
toolbar?.visibility = View.GONE
drawerLayout?.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
}
else -> {
toolbar?.visibility = View.VISIBLE
drawerLayout?.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
}
}
}
Now you have a drawer and navigation component on your app.
I have an application which has a bottom menu bar which users can use to switch between 4 home page tabs. It's working fine like below.
The only problem I'm having is it showing back button when I switch between different fragment. Since all these fragments are at the same level I do not want it to behave like that.
This is my implementation.
MainNavigationActivity
class MainNavigationActivity : AppCompatActivity() {
private lateinit var navigationController: NavController
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initialization()
}
private fun initialization(){
navigationController = Navigation.findNavController(this, R.id.hostFragment)
setSupportActionBar(toolbar)
NavigationUI.setupWithNavController(bottomNavigationBar,navigationController)
NavigationUI.setupActionBarWithNavController(this,navigationController)
}
override fun onBackPressed() {
onSupportNavigateUp()
}
MainNavigationActivity Layout
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".activities.MainNavigationActivity">
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:background="#color/colorPrimary"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light"
app:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar"/>
<fragment
android:id="#+id/hostFragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#id/toolbar"
android:layout_above="#id/bottomNavigationBar"
app:defaultNavHost="true"
app:navGraph="#navigation/main_navigation_graph" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/bottomNavigationBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="#android:color/white"
app:menu="#menu/bottom_navigation_menu"
app:labelVisibilityMode="labeled"/>
</RelativeLayout>
bottom_navigation_menu.xml
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="#+id/navigation_home"
android:state_checked="true"
android:color="#color/colorPrimary"
android:title="#string/navigation_home"
android:icon="#drawable/ic_bottom_bar_menu_home"/>
<item
android:id="#+id/navigation_offers"
android:state_checked="false"
android:color="#color/gray"
android:title="#string/navigation_offers"
android:icon="#drawable/ic_bottom_bar_menu_offers"/>
<item
android:id="#+id/navigation_my_bookings"
android:state_checked="false"
android:color="#color/gray"
android:title="#string/navigation_my_bookings"
android:icon="#drawable/ic_bottom_bar_menu_bookings"/>
<item
android:id="#+id/navigation_my_account"
android:state_checked="false"
android:color="#color/gray"
android:title="#string/navigation_my_account"
android:icon="#drawable/ic_bottom_bar_menu_account"/>
</menu>
The Ids are given to the fragments in the navigation graph and the ids in the menu.xml are the same. that's how it identifies the correct fragment and switch to that fragment correctly.
How can I remove this back button on the toolbar in home screen level?
As per the NavigationUI documentation:
By default, the Navigation button is hidden when a user is at a top-level destination of a navigation graph and appears as an Up button in any other destination.
If you want to customize which destinations are considered top-level destinations, you can instead pass a set of destination IDs to the constructor, as shown below:
val appBarConfiguration = AppBarConfiguration(setOf(
R.id.navigation_home, R.id.navigation_offers,
R.id.navigation_my_bookings, R.id.navigation_my_account))
(Note that this constructor requires the navigation-ui-ktx artifact - the alternative is to use the AppBarConfiguration.Builder)
You can also use the extension function for AppBarConfiguration that takes the bottom navigation menu as its argument, just to make things a bit easier. Check here
It will set all the fragments in your bottom navigation bar as the top-level destinations.
It is as simple as this:
val appBarConfiguration = AppBarConfiguration(bottomNavigationBar.menu)
setupActionBarWithNavController(navigationController, appBarConfiguration)
And also as suggested by #ianhanniballake, you need to have navigation-ui-ktx for this to work.
//hide back arrow in toolbar inside fragment while using NavigationComponent
((AppCompatActivity)getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(false);
I am using the Android Navigation Architecture Components for navigating a Fragment-based-, Single-Activity-application. The main navigation is implemented via NavigationDrawer-pattern: DrawerLayout with NavigationView and NavHostFragment plus and additional Toolbar.
Everything is working fine so far - I am able to navigate to every Fragment -, except for the BackStack of the main-level Fragments. Any Fragment other than the startDestination seems to be added to the BackStack instead of being replaced like a regular NavigationDrawer would do.
The misbehavior can be identified via the Burger-icon that switches to a Back-icon as soon as I navigate to a Fragment other than the startDestination, and by navigating back which leads me to the startDestination instead of exiting the app.
How should the Navigation Architecture Components be used in order to replace the main Fragments in combination with a NavigationView?
I am initializing the Navigation Architecture Components like this:
class MainActivity : AppCompatActivity {
private val navigationHostFragment: NavHostFragment
get() = supportFragmentManager.findFragmentById(R.id.navigationHost) as NavHostFragment
private val navigationController: NavController
get() = navigationHostFragment.navController
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity)
// Views are bound via Kotlin Android Extensions
setSupportActionBar(toolbar)
NavigationUI.setupWithNavController(toolbar, navigationController, drawerLayout)
NavigationUI.setupWithNavController(navigationView, navigationController)
}
override fun onSupportNavigateUp(): Boolean = findNavController(R.id.navigationHost).navigateUp()
}
The layout for the activity (/res/layout/activity.xml) looks like this:
<androidx.drawerlayout.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/drawerLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"/>
<fragment
android:id="#+id/navigationHost"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:navGraph="#navigation/navigation"
app:defaultNavHost="true"/>
</LinearLayout>
<com.google.android.material.navigation.NavigationView
android:id="#+id/navigationView"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
app:menu="#menu/navigation"/>
</androidx.drawerlayout.widget.DrawerLayout>
The navigation graph (/res/navigation/navigation.xml) looks like this:
<navigation
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
app:startDestination="#id/first">
<fragment
android:id="#+id/first"
android:name="de.example.FirstFragment"
android:label="#string/first"/>
<fragment
android:id="#+id/second"
android:name="de.example.SecondFragment"
android:label="#string/second"/>
</navigation>
The menu for the NavigationView (/res/menu/navigation.xml) looks like this:
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<group android:checkableBehavior="single">
<item
android:id="#+id/first"
android:title="#string/first"/>
<item
android:id="#+id/second"
android:title="#string/second"/>
</group>
</menu>
The relevant dependencies look like this:
dependencies {
implementation 'com.google.android.material:material:1.0.0'
implementation 'androidx.core:core-ktx:1.0.0'
implementation 'android.arch.navigation:navigation-fragment-ktx:1.0.0-alpha06'
implementation 'android.arch.navigation:navigation-ui-ktx:1.0.0-alpha06'
}
As Alex posted, my original goal clashes with the Principles of Navigation:
Apps have a fixed destination which is the screen the user sees when they launch your app from the launcher. This destination should also be the last screen the user sees when they return to the launcher after pressing the back button.