Search code examples
androidkotlinandroid-architecture-navigation

Bottom Navigation View setup with fragment navigation component not working


I tried implementing the bottom navigation view to set up with fragment transition using navigation components by referring Navigation Codelab.

The fragments are not changing when clicking on the bottom navigation view.

Note:
I am trying to implement the bottom navigation view inside another fragment. (Not an activity like in the codelab example)

MainActivity.kt:

class MainActivity : AppCompatActivity() {

    // Data binding
    private lateinit var mainActivityBinding: MainActivityBinding

    // On activity creation starting
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Set activity layout
        mainActivityBinding = DataBindingUtil.setContentView(this, R.layout.main_activity)
    }
}

main_activity.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=".activity.MainActivity">

    <data>

    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/main_activity_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <fragment
            android:id="@+id/main_activity_nav_host_fragment"
            android:name="androidx.navigation.fragment.NavHostFragment"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:defaultNavHost="true"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:navGraph="@navigation/activity_navigation" />

    </androidx.constraintlayout.widget.ConstraintLayout>

</layout>

activity_navigation.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/activity_navigation"
    app:startDestination="@id/mainFragment">

    <fragment
        android:id="@+id/mainFragment"
        android:name="com.appz.abhi.moneytracker.view.main.MainFragment"
        android:label="main_fragment"
        tools:layout="@layout/main_fragment" />

</navigation>

MainFragment.kt:

class MainFragment : Fragment() {

    // Data binding
    private var mainFragmentBinding: MainFragmentBinding? = null

    // On fragment view creation starting
    override fun onCreateView(
            inflater: LayoutInflater, container: ViewGroup?,
            savedInstanceState: Bundle?
    ): View? {

        // Inflate the fragment layout
        mainFragmentBinding = DataBindingUtil
                .inflate(inflater, R.layout.main_fragment, container, false)

        // Return root view
        return mainFragmentBinding!!.root
    }


    // On fragment view creation completion
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        // Initialize UI
        initUI()
    }


    // Initialize UI
    private fun initUI() {

        // Setup action bar
        (activity as AppCompatActivity).setSupportActionBar(mainFragmentBinding?.mainFragmentToolbar)

        // Setup bottom navigation view
        setUpBottomNavigationView()
    }

    // Setup bottom navigation view
    private fun setUpBottomNavigationView() {

        // Nav host fragment
        val host: NavHostFragment = activity?.supportFragmentManager
                ?.findFragmentById(R.id.main_fragment_nav_host_fragment) as NavHostFragment?
                ?: return

        // Set up Action Bar
        val navController = host.navController

        // Setup bottom navigation view
        mainFragmentBinding?.mainFragmentBottomNavigationView?.setupWithNavController(navController)
    }
}

main_fragment.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.main.MainFragment">

    <data>

    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/main_fragment_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <com.google.android.material.appbar.AppBarLayout
            android:id="@+id/main_fragment_app_bar_layout"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent">

            <androidx.appcompat.widget.Toolbar
                android:id="@+id/main_fragment_toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                android:background="@drawable/toolbar_background"
                android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
                app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
                tools:title="Money Tracker" />

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

        <fragment
            android:id="@+id/main_fragment_nav_host_fragment"
            android:name="androidx.navigation.fragment.NavHostFragment"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:defaultNavHost="true"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/main_fragment_app_bar_layout"
            app:navGraph="@navigation/bottom_navigation_view_navigation" />

        <com.google.android.material.bottomnavigation.BottomNavigationView
            android:id="@+id/main_fragment_bottom_navigation_view"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:background="@color/white"
            app:itemIconTint="@color/bottom_navigation"
            app:itemTextColor="@color/bottom_navigation"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:menu="@menu/bottom_navigation_view_menu"/>

    </androidx.constraintlayout.widget.ConstraintLayout>

</layout>

bottom_navigation_view_navigation.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/bottom_navigation_view_navigation"
    app:startDestination="@id/settingsFragment">

    <fragment
        android:id="@+id/homeFragment"
        android:name="com.appz.abhi.moneytracker.view.home.HomeFragment"
        android:label="Home"
        tools:layout="@layout/home_fragment" />

    <fragment
        android:id="@+id/settingsFragment"
        android:name="com.appz.abhi.moneytracker.view.settings.SettingsFragment"
        android:label="Settings"
        tools:layout="@layout/settings_fragment" />

</navigation>

bottom_navigation_view_menu:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <item
        android:id="@id/homeFragment"
        android:icon="@drawable/ic_home_black_24dp"
        android:title="@string/home"
        app:showAsAction="ifRoom" />

    <item
        android:id="@id/settingsFragment"
        android:icon="@drawable/ic_settings_black_24dp"
        android:title="@string/settings"
        app:showAsAction="ifRoom" />

</menu>

HomeFragment.kt:

class HomeFragment : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.home_fragment, container, false)
    }
}

SettingsFragment.kt:

class SettingsFragment : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.settings_fragment, container, false)
    }
}

Solution

  • It doesn't seem like you ever get to the setupWithNavController line. You use findFragmentById(R.id.main_fragment_nav_host_fragment) with the activity's FragmentManager, but the NavHostFragment in the activity is under the id main_activity_nav_host_fragment. You should be using childFragmentManager if you want to get the nested NavHostFragment in your MainFragment's layout:

    // Nav host fragment
    val host: NavHostFragment = childFragmentManager
            .findFragmentById(R.id.main_fragment_nav_host_fragment) as NavHostFragment?
            ?: return
    

    Note that there are very few cases where you actually want or need a nested NavHostFragment like this. As per the Listen for navigation events documentation, generally if you want global navigation such as a BottomNavigationView to only appear on some screens, you'd add an OnDestinationChangedListener and change its visibility there.