Android BottomNavigationView With Fragment (Kotlin)

February 26, 2019

We shall create an Activity with BottomNavigationView which switches the main view the fragments.

You can create a Bottom Navigation Activity using Android Studio wizard: File -> New -> Activity -> Bottom Navigation Activity.

MainActivity.kt

class MainActivity : AppCompatActivity() {

    private val onNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item ->
        /*
        when (item.itemId) {
            R.id.navigation_home -> {
                loadFragment(item.itemId)
                message.setText(R.string.title_home)
                return@OnNavigationItemSelectedListener true
            }
            R.id.navigation_dashboard -> {
                message.setText(R.string.title_dashboard)
                return@OnNavigationItemSelectedListener true
            }
            R.id.navigation_notifications -> {
                message.setText(R.string.title_notifications)
                return@OnNavigationItemSelectedListener true
            }
        }
         */
        loadFragment(item.itemId)
        true
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        navigation.setOnNavigationItemSelectedListener(onNavigationItemSelectedListener)
        loadFragment(navigation.selectedItemId)
        // loadFragment(R.id.navigation_dashboard)
    }

    private fun loadFragment(itemId: Int) {
        val tag = itemId.toString()
        var fragment = supportFragmentManager.findFragmentByTag(tag) ?: when (itemId) {
            R.id.navigation_home -> {
                TestFragment.newInstance()
            }
            R.id.navigation_dashboard -> {
                Test2Fragment.newInstance()
            }
            R.id.navigation_notifications -> {
                Test3Fragment.newInstance()
            }
            else -> {
                null
            }
        }

        if (fragment != null) {
            supportFragmentManager
                .beginTransaction()
                .replace(R.id.fragmentContainer, fragment, tag)
                .commit()
        }
    }    
}

NOTE: The above example replace the fragment upon navigation, so UI state will be lost (e.g. when you edit EditText and didn’t save/reload the value, the changes shall be lost). You probably need to implement your own onSaveInstanceState to save/restore UI state. The benefit of fragment replace is memory saving (as only one fragment is active at any one time).

NOTE: You can use Android Switch Fragment Without Losing Ui State (Kotlin) example to replace loadFragment.

res/layouts/activity_main.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:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

    <FrameLayout
            android:id="@+id/fragmentContainer"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintBottom_toTopOf="@+id/navigation"
            android:layout_marginLeft="0dp"
            android:layout_marginStart="0dp"
            android:layout_marginEnd="0dp"
            android:layout_marginRight="0dp"
            android:layout_marginBottom="0dp"
            app:layout_behavior="@string/appbar_scrolling_view_behavior"
            >

    </FrameLayout>

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

</androidx.constraintlayout.widget.ConstraintLayout>

NOTE: In the default generated layout, BottomNavigationView will overlap/cover part of the main view. The ConstraintLayout setup above solve this issue.

NOTE: The generated activity used traditional Activity without AppBarLayout. If you are implementing AppBarLayout, refer to Android Prevent BottomNavigationView Cover/Overlap Content/RecyclerView.

res/menu/navigation.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_black_24dp"
            android:title="@string/title_home"/>

    <item
            android:id="@+id/navigation_dashboard"
            android:icon="@drawable/ic_dashboard_black_24dp"
            android:title="@string/title_dashboard"/>

    <item
            android:id="@+id/navigation_notifications"
            android:icon="@drawable/ic_notifications_black_24dp"
            android:title="@string/title_notifications"/>

</menu>

TestFragment.kt.

class TestFragment : Fragment() {

    companion object {

        @JvmStatic
        fun newInstance() =
            TestFragment().apply {
                arguments = Bundle().apply {
                    // putString(ARG_PARAM1, param1)
                }
            }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        arguments?.let {
            // param1 = it.getString(ARG_PARAM1)
        }
    }

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

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

        // Initialize UI
    }
}

res/layouts/fragment_test.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=".view.TestFragment">
    
    <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Test"
            android:id="@+id/textView"/>

</androidx.constraintlayout.widget.ConstraintLayout>
This work is licensed under a
Creative Commons Attribution-NonCommercial 4.0 International License.