0

So, I am using Navigation Drawer created with Android Studio but in one of the created fragments I need to implement a BottomNavigationBar. I was searching on the web but could not find anything.

fragment_wm.xml

<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"
    tools:context=".ui.weightmanagement.WMFragment">

    <!--<fragment
        android:id="@+id/wm_bottomNavFragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true"
        app:layout_constraintBottom_toTopOf="@id/wm_bottomNav"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navGraph="@navigation/mobile_navigation2" />-->
    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/wm_bottomNavFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:name="androidx.navigation.fragment.NavHostFragment"
        app:navGraph="@navigation/mobile_navigation2"
        app:defaultNavHost="true"/>

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/wm_bottomNav"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="0dp"
        android:layout_marginEnd="0dp"
        android:background="?android:attr/windowBackground"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:menu="@menu/bottom_nav_menu" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hola"/>

</androidx.constraintlayout.widget.ConstraintLayout>

WMFragment.kt

class WMFragment : Fragment() {

    private var _binding: FragmentWmBinding? = null

    private val binding get() = _binding!!

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        _binding = FragmentWmBinding.inflate(inflater, container, false)
        val root: View = binding.root


        return root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        val nestedNavHostFragment = childFragmentManager.findFragmentById(R.id.wm_bottomNavFragment) as? NavHostFragment
        val navController = nestedNavHostFragment?.navController
        val bottomNavView = view.findViewById<BottomNavigationView>(R.id.wm_bottomNav)

        if (navController != null)
            bottomNavView.setupWithNavController(navController)
        else
            throw RuntimeException("Controller not found")
    }
}

As you can see I use the and tags with no luck. Every time I try to click the button in the Navigation Drawer, the app restarts and I cannot see what the problem is but if I delete the fragment code in the Kotlin class and XML, everything works fine.

Hope you can help me. Thanks in advance.

1 Answers1

1

In this answer, you will find a complete solution for that. we gonna start with the NavigationComponent of the Drawer and how we can embed another navigation component for an inner NavHost.

I'll add a sample of code includes all the files needed.

First - MainActivity

MainActivty()

class MainActivity : AppCompatActivity() {

private lateinit var appBarConfiguration: AppBarConfiguration
private lateinit var binding: ActivityMainBinding

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    binding = ActivityMainBinding.inflate(layoutInflater)
    setContentView(binding.root)

    setSupportActionBar(binding.appBarMain.toolbar)

    binding.appBarMain.fab.setOnClickListener { view ->
        Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
            .setAction("Action", null).show()
    }
    val drawerLayout: DrawerLayout = binding.drawerLayout
    val navView: NavigationView = binding.navView
    val navController = findNavController(R.id.nav_host_fragment_content_main)
    // Passing each menu ID as a set of Ids because each
    // menu should be considered as top level destinations.
    appBarConfiguration = AppBarConfiguration(
        setOf(
            R.id.nav_home, R.id.nav_gallery, R.id.nav_slideshow
        ), drawerLayout
    )
    setupActionBarWithNavController(navController, appBarConfiguration)
    navView.setupWithNavController(navController)
}

override fun onCreateOptionsMenu(menu: Menu): Boolean {
    // Inflate the menu; this adds items to the action bar if it is present.
    menuInflater.inflate(R.menu.main, menu)
    return true
}

override fun onSupportNavigateUp(): Boolean {
    val navController = findNavController(R.id.nav_host_fragment_content_main)
    return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
}

}

The MainActivity includes 3 fragments for the Navigation Drawer.

activity_main.xml

<?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
    android:id="@+id/app_bar_main"
    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>

app_bar_main.xml

<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/Theme.MyApplication.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/Theme.MyApplication.PopupOverlay" />

</com.google.android.material.appbar.AppBarLayout>

<include layout="@layout/content_main" />

<com.google.android.material.floatingactionbutton.FloatingActionButton
    android:id="@+id/fab"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom|end"
    android:layout_marginEnd="@dimen/fab_margin"
    android:layout_marginBottom="16dp"
    app:srcCompat="@android:drawable/ic_dialog_email" />

</androidx.coordinatorlayout.widget.CoordinatorLayout>

content_main.xml

This file includes the fragment container where the NavController placed.

<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_content_main"
    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>

mobile_navigation.xml

This is the navGraph for the Navigation Drawer

<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/nav_home">

<fragment
    android:id="@+id/nav_home"
    android:name="com.example.myapplication.ui.home.HomeFragment"
    android:label="@string/menu_home"
    tools:layout="@layout/fragment_home" />

<fragment
    android:id="@+id/nav_gallery"
    android:name="com.example.myapplication.ui.gallery.GalleryFragment"
    android:label="@string/menu_gallery"
    tools:layout="@layout/fragment_gallery" />

<fragment
    android:id="@+id/nav_slideshow"
    android:name=
    "com.example.myapplication.ui.slideshow.SlideshowFragment"
    android:label="@string/menu_slideshow"
    tools:layout="@layout/fragment_slideshow" />

   </navigation>

Second: GalleryFragment inner NavController

Now, we are going to add a new NavGraph to GalleryFragment, it will hold 2 BottomNavigation fragments.

GalleryFragment.xml

class GalleryFragment : Fragment() {

private var _binding: FragmentGalleryBinding? = null
private lateinit var appBarConfiguration: AppBarConfiguration
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!

override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
): View {
    _binding = FragmentGalleryBinding.inflate(inflater, container, false)
    appBarConfiguration = AppBarConfiguration(
        setOf(
            R.id.nav_home, R.id.nav_gallery
        )
    )
    val navHostFragment = childFragmentManager.findFragmentById(R.id.container) as NavHostFragment
    val navController = navHostFragment.navController
    val navView: BottomNavigationView = binding.innerBottomNavigation
    navView.setupWithNavController(navController)
    return binding.root
}

override fun onDestroyView() {
    super.onDestroyView()
    _binding = null
}

}

We added a new FragmentContainerView its id is container, this view will be the navHost.

fragment_gallery.xml

<?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"
tools:context=".ui.gallery.GalleryFragment">

<androidx.fragment.app.FragmentContainerView
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:textAlignment="center"
    android:textSize="20sp"
    android:name="androidx.navigation.fragment.NavHostFragment"
    app:defaultNavHost="true"
    app:navGraph="@navigation/inner_bottom_navigation"
    app:layout_constraintBottom_toTopOf="@+id/inner_bottom_navigation"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

<com.google.android.material.bottomnavigation.BottomNavigationView
    android:id="@+id/inner_bottom_navigation"
    android:layout_width="0dp"
    android:layout_height="60dp"
    app:menu="@menu/inner_bottom_nav_menu"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

We used a new menu file for the new BottomNavigation,

inner_bottom_nav_menu.xml

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:showIn="navigation_view">
<group android:checkableBehavior="single">
    <item
        android:id="@+id/fragment1"
        android:icon="@drawable/ic_menu_camera"
        android:title="@string/menu_home" />
    <item
        android:id="@+id/fragmen2"
        android:icon="@drawable/ic_menu_gallery"
        android:title="@string/menu_gallery" />
  </group>
 </menu>

The new navigation graph for the bottom navigation will be as the following:

<?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/inner_bottom_navigation"
    app:startDestination="@id/fragment1">

<fragment
    android:id="@+id/fragment1"
    android:name="com.example.myapplication.ui.gallery.inner_fragments.Fragment1"
    android:label="fragment_1"
    tools:layout="@layout/fragment_1" />
<fragment
    android:id="@+id/fragmen2"
    android:name="com.example.myapplication.ui.gallery.inner_fragments.Fragment2"
    android:label="fragment_2"
    tools:layout="@layout/fragment_2" />
</navigation>

Note: Fragment1 and Fragment2 are normal fragments.

By applying the inner navController in the GalleryFragment, when you navigate to GalleryFragment, you will find a BottomNavigation of 2 Fragments(Fragment1 and Fragment2). You will be able to navigate between them smoothly.

Also, if the Back button will be handled automatically,

Suppose you're in follow this scenario: Home> Gallery (The 2 fragments BottomNavigation will appear)> Fragment 1> Fragment2(Inside Gallery).

When you press back, you will navigate back to Fragment 1. Pressing another time, you will navigate to Home Fragment of the Navigation Drawer.

MustafaKhaled
  • 1,343
  • 9
  • 25