Android Navigation Architecture Components: Wrong BackStack with NavigationView - android

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.

Related

Hamburger button not opening NavigationUI navigation drawer on Android

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)

How to set up Navigation Component with Navigation Drawer in Android?

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.

How to remove back button from toolbar when using bottom menu bar with navigation architecture components

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);

How to use Navigation Drawer and Bottom Navigation simultaneously - Navigation Architecture Component

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.

Wrong animation when using navigation architecture component

I am building an app that uses BottomNavigationView and Navigation Architecture Component (https://github.com/fkshiba/POCNavigation).
I have one graph.xml for each tab and the transition between tabs is done by the Activity without Navigation AC due to the multiple backstack issue. I have an action with a transition animation on home_graph.xml from Home2 to Home3.
The problem is, once I run this transition and pop back to Home2 then navigate to another tab. It runs the pop animation again for the Home2 fragment when I transition from another tab even though there wasn't any animation specified for it.
Does anyone know a solution for this issue?
I see your code on GitHub and i assume that you are using android navigation wrongly. First you don't need to create different graphs(in your case four) for
different fragments. Just create one graph and add all fragments in it that you have to use within your bottom navigation and for others transactions.
And secondly you don't need to perform fragment transactions manually (in your activity code) you can but not necessary.
Your updated code:
only one graph:
<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/home_graph"
app:startDestination="#id/home2Fragment">
<fragment
android:id="#+id/home2Fragment"
android:name="com.felipeshiba.pocnavigation.Home2Fragment"
android:label="fragment_home2"
tools:layout="#layout/fragment_home2" >
<action
android:id="#+id/action_home2Fragment_to_home3Fragment"
app:destination="#id/home3Fragment"
app:enterAnim="#anim/nav_default_enter_anim"
app:exitAnim="#anim/nav_default_exit_anim"
app:popEnterAnim="#anim/nav_default_pop_enter_anim"
app:popExitAnim="#anim/nav_default_pop_exit_anim"/>
</fragment>
<fragment
android:id="#+id/home3Fragment"
android:name="com.felipeshiba.pocnavigation.Home3Fragment"
android:label="fragment_home3"
tools:layout="#layout/fragment_home3" >
<deepLink
android:id="#+id/deepLink"
app:uri="ifood://home/" />
</fragment>
<fragment
android:id="#+id/orders2Fragment"
android:name="com.felipeshiba.pocnavigation.Orders2Fragment"
android:label="fragment_orders2"
tools:layout="#layout/fragment_orders2" />
<fragment
android:id="#+id/profile2Fragment"
android:name="com.felipeshiba.pocnavigation.Profile2Fragment"
android:label="fragment_profile2"
tools:layout="#layout/fragment_profile2" />
<fragment
android:id="#+id/search2Fragment"
android:name="com.felipeshiba.pocnavigation.Search2Fragment"
android:label="fragment_search2"
tools:layout="#layout/fragment_search2" /></navigation>
Your updated activity code:
class NavigationActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_navigation)
val host: NavHostFragment = supportFragmentManager
.findFragmentById(R.id.navigation_container) as NavHostFragment? ?: return
val navController = host.navController
bottom_navigation.setupWithNavController(navController)
}}
Now you can just call your botton navigation items(fragments) within your menu by replacing menu item id with fragments id like below:
updated menu.xml:
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="#+id/home2Fragment"
android:icon="?android:attr/actionModeCopyDrawable"
android:title="Home" />
<item
android:id="#+id/search2Fragment"
android:icon="#android:drawable/ic_menu_search"
android:title="Search" />
<item
android:id="#+id/orders2Fragment"
android:icon="?android:attr/actionModePasteDrawable"
android:title="Orders" />
<item
android:id="#+id/profile2Fragment"
android:icon="?android:attr/actionModeCutDrawable"
android:title="Profile" /></menu>
Your activity.xml:
<android.support.constraint.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"
tools:context=".NavigationActivity">
<fragment
android:id="#+id/navigation_container"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
android:visibility="gone"
app:defaultNavHost="true"
app:navGraph="#navigation/home_graph"
app:layout_constraintBottom_toTopOf="#id/bottom_navigation"
app:layout_constraintTop_toTopOf="parent" />
<android.support.design.widget.BottomNavigationView
android:id="#+id/bottom_navigation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:menu="#menu/bottom_navigation"></android.support.constraint.ConstraintLayout>
For other updated(working) code like manifest check here
NavigationUI will always pop to start destination before going to other tab in BottomNavigationView, it's just how the nav lib works. Set custom OnNavigationItemSelectedListener and navigate yourself, and pass your animations through NavOptions.

Categories

Resources