Abhimanyu
Abhimanyu

Reputation: 14757

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

Upvotes: 6

Views: 12954

Answers (3)

dnhyde
dnhyde

Reputation: 1305

For whatever reason, findNavController(R.id.nav_host_fragment) and setupActionBarWithNavController() are methods you'd call in an Activity, not in a Fragment. (I could not find any reference to it in the docs, so if anyone does, please add it in the comments, only stated on SO for now).

I was doing just as you to setup my bottomNavigation

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

// or with kotlin synthetic
bottomNavigation.setupWithNavController(findNavController())

Note: I expected the kotlin synthetic way to work since I call findNavController() from the fragment's onActivityCreated() and the fragment contains a default navHost in its layout.

But nothing happened... and could not grasp why. Then this question and this great article, gave me hints on how to reach my navHost.

Workaround:
Reach for the activity, then you can specify the id of your navHost in the findNavController(R.id.nav_host_fragment)

// find navController using navHostFragment
val navController = requireActivity().findNavController(R.id.main_fragment_nav_host_fragment)

// setup navigation with root destinations and toolbar
NavigationUI.setupWithNavController(bottomNavigation, navController)

Upvotes: 0

ianhanniballake
ianhanniballake

Reputation: 199805

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.

Upvotes: 5

B Best
B Best

Reputation: 1154

NOTE: I don't have experience with Kotlin but I'm assuming the implementation is similar enough in Java.

It seems odd that you are doing the bottom navigation inside a fragment where in my experience the bottom nav bar goes in the activity and you programmatically set the first fragment. The fragments that come into view using the navigation bar populate a FrameLayout that sits above the nav bar in the activity.

Here is an example in Java.

MainActivity.java

public class HomeActivity extends AppCompatActivity {

    private String currentFragmentTag;

    private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener
            = new BottomNavigationView.OnNavigationItemSelectedListener() {

        @Override
        public boolean onNavigationItemSelected(@NonNull MenuItem item) {
            /* I just put some filler code but each fragment type will probably 
 be different */
            switch (item.getItemId()) {
                case R.id.fragment_one:
                    CustomFragment fragment = new CustomFragment();
                    if (!currentFragmentTag.equals(fragment.fragmentTag)) {
                        switchFragment(customFragment, null);
                    }
                    return true;
                case R.id.fragment_two:
                    CustomFragment fragment = new CustomFragment();
                    if (!currentFragmentTag.equals(fragment.fragmentTag)) {
                        switchFragment(customFragment, null);
                    }
                    return true;
                 case ....
            }
            return false;
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_home);
        currentFragmentTag = "FIRST_FRAGMENT";
        BottomNavigationView navigation = (BottomNavigationView) findViewById(R.id.navigation);
        navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener);
    }


    /**
     * Select fragment.
     */
    public void switchFragment(Fragment fragment, String tag) {
        getSupportFragmentManager().beginTransaction().replace(R.id.home_frame, fragment, tag)
                .addToBackStack(tag).setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE).commit();


    }

MaiActivity layout file


<?xml version="1.0" encoding="utf-8"?>
<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:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".activities.MainActivity">

    <android.support.design.widget.BottomNavigationView
        android:id="@+id/navigation"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:background="@color/background_grey"
        app:itemIconTint="@color/white"
        app:itemTextColor="@color/white"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:menu="@menu/navigation" />

    <FrameLayout
        android:id="@+id/home_frame"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginBottom="8dp"
        app:layout_constraintBottom_toTopOf="@+id/navigation"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

    </FrameLayout>

</android.support.constraint.ConstraintLayout>

Upvotes: 2

Related Questions