Setup 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() {

    // optional
    companion object {
        const val PARAM_NAVIGATION_ID = "navigation_id"

        fun newInstance(context: Context, navigationId: Int) =  Intent(context, MainActivity::class.java).apply {
            flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
            putExtra(PARAM_NAVIGATION_ID, navigationId)
        }
    }

    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)
        val navigationId = intent.getIntExtra(PARAM_NAVIGATION_ID, R.id.navigation_dashboard)
        navigation.selectedItemId = navigationId
    }

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

        // replace fragment
        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).

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

Replace code after // replace fragment with the following code

// show/hide fragment
if (fragment != null) {
    val transaction = supportFragmentManager.beginTransaction()

    if (viewModel.lastActiveFragmentTag != null) {
        val lastFragment = supportFragmentManager.findFragmentByTag(viewModel.lastActiveFragmentTag)
        if (lastFragment != null)
            transaction.hide(lastFragment)
    }

    if (!fragment.isAdded) {
        transaction.add(R.id.fragmentContainer, fragment, tag)
    }
    else {
        transaction.show(fragment)
    }

    transaction.commit()
    viewModel.lastActiveFragmentTag = tag
}

NOTE: ViewModel is required to store lastActiveFragmentTag, else the state is lost upon configuration change/screen rotation.

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.