Android BottomNavigationView with fragments

The following is an example for using BottomNavigationView alone with Fragments in Android. On each button click on the BottomNavigationView, the old fragment in the framelayout will be replaced by a new fragment.

Complete project in github

navigation.xml, the menu for the bottom navigation bar.

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

    <item
        android:id="@+id/navigation_favorites"
        android:icon="@drawable/ic_favorite_black_24dp"
        android:title="@string/title_favorites" />

    <item
        android:id="@+id/navigation_settings"
        android:icon="@drawable/ic_settings_black_24dp"
        android:title="@string/title_settings" />

</menu>

activity_main.xml, the layout for the main activity, containing a framelayout for housing the fragments and a BottomNavigationView.

<?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="com.example.buttonnavifragment.MainActivity">

    <FrameLayout
        android:id="@+id/fl_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginLeft="@dimen/activity_horizontal_margin"
        android:layout_marginStart="@dimen/activity_horizontal_margin"
        android:layout_marginTop="@dimen/activity_vertical_margin"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <android.support.design.widget.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" />

</android.support.constraint.ConstraintLayout>

dashboard.xml, the layout file for the dashboard fragment. The layout files for favorites, home, notifications and settings are the same but with different text string in the TextView.

<?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="com.example.buttonnavifragment.MainActivity">

    <TextView
        android:id="@+id/message"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/activity_vertical_margin"
        android:gravity="center"
        android:text="@string/title_dashboard"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

strings.xml, String resource file.

<resources>
    <string name="app_name">Button Navi Fragment</string>
    <string name="title_home">Home</string>
    <string name="title_dashboard">Dashboard</string>
    <string name="title_notifications">Notifications</string>
    <string name="title_favorites">Favorites</string>
    <string name="title_settings">Settings</string>
</resources>

BaseActivity.kt, the base activity with activity life cycle callbacks for logging the callback calls.

import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.util.Log

abstract class BaseActivity : AppCompatActivity() {
    abstract val TAG : String
    abstract val LAYOUT_ID : Int


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.d(TAG, "onCreate")
        setContentView(LAYOUT_ID)
    }

    override fun onRestart() {
        super.onRestart()
        Log.d(TAG, "onRestart")
    }

    override fun onStart() {
        super.onStart()
        Log.d(TAG, "onStart")
    }

    override fun onResume() {
        super.onResume()
        Log.d(TAG, "onResume")
    }

    override fun onPause() {
        super.onPause()
        Log.d(TAG, "onPause")
    }

    override fun onStop() {
        super.onStop()
        Log.d(TAG, "onStop")
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.d(TAG, "onDestroy")
    }
}

ABaseFrag.kl, the base fragment with fragment life cycle callbacks for logging callback calls.

import android.content.Context
import android.os.Bundle
import android.support.v4.app.Fragment
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup

abstract class ABaseFrag : Fragment() {

    abstract val TAG : String
    abstract val LAYOUT_ID : Int

    override fun onAttach(context: Context?) {
        super.onAttach(context)
        Log.d(TAG, "onAttach")
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.d(TAG, "onCreate")
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        Log.d(TAG, "onCreateView")
        return inflater.inflate(LAYOUT_ID, container, false)
    }

    override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        Log.d(TAG, "onViewCreated")
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        Log.d(TAG, "onActivityCreated")
    }

    override fun onStart() {
        super.onStart()
        Log.d(TAG, "onStart")
    }

    override fun onResume() {
        super.onResume()
        Log.d(TAG, "onResume")
    }

    override fun onPause() {
        super.onPause()
        Log.d(TAG, "onPause")
    }

    override fun onStop() {
        super.onStop()
        Log.d(TAG, "onStop")
    }

    override fun onDestroyView() {
        super.onDestroyView()
        Log.d(TAG, "onDestroyView")
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.d(TAG, "onDestroy")
    }

    override fun onDetach() {
        super.onDetach()
        Log.d(TAG, "onDetach")
    }
}

MainActivity.kt, the main activity for initializing the fragments and bottom navigation bar.

import android.os.Bundle
import android.support.design.widget.BottomNavigationView
import android.util.Log
import com.example.buttonnavifragment.fragments.*
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : BaseActivity() {
    override val TAG = MainActivity::class.java.simpleName
    override val LAYOUT_ID = R.layout.activity_main

    val FRAG_HOME = "home_frag"
    val FRAG_DASHBOARD = "dashboard_frag"
    val FRAG_NOTIFICATIONS = "notifications_frag"
    val FRAG_FAVORITES = "favorites_frag"
    val FRAG_SETTINGS = "settings_frag"

    private val mOnNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item ->
        when (item.itemId) {
            R.id.navigation_home -> {
                loadFragByTag(FRAG_HOME)
                return@OnNavigationItemSelectedListener true
            }
            R.id.navigation_dashboard -> {
                loadFragByTag(FRAG_DASHBOARD)
                return@OnNavigationItemSelectedListener true
            }
            R.id.navigation_notifications -> {
                loadFragByTag(FRAG_NOTIFICATIONS)
                return@OnNavigationItemSelectedListener true
            }
            R.id.navigation_favorites -> {
                loadFragByTag(FRAG_FAVORITES)
                return@OnNavigationItemSelectedListener true
            }
            R.id.navigation_settings -> {
                loadFragByTag(FRAG_SETTINGS)
                return@OnNavigationItemSelectedListener true
            }
        }
        false
    }

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

        navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener)
        BottomNavigationViewHelper.disableShiftMode(navigation)

        loadFragByTag(FRAG_HOME)
    }

    private fun loadFragByTag(tag : String) {
        var frag = supportFragmentManager.findFragmentByTag(tag)
        if (frag == null) {
            Log.d(TAG, "$tag not found, creating a new one.")
            when (tag) {
                FRAG_HOME -> {
                    frag = HomeFrag()
                }
                FRAG_DASHBOARD -> {
                    frag = DashboardFrag()
                }
                FRAG_NOTIFICATIONS -> {
                    frag = NotificationsFrag()
                }
                FRAG_FAVORITES -> {
                    frag = FavoritesFrag()
                }
                FRAG_SETTINGS -> {
                    frag = SettingsFrag()
                }
            }

        } else {
            Log.d(TAG, "$tag found.")
        }

        supportFragmentManager
                .beginTransaction()
                .replace(R.id.fl_main, frag, tag)
                .commit()
    }

}

HomeFrag.kt, the home fragment for showing home contents.

import com.example.buttonnavifragment.R

class HomeFrag : ABaseFrag() {
    override val TAG = HomeFrag::class.java.simpleName
    override val LAYOUT_ID = R.layout.fragment_home
}

BottomNavigationViewHelper.kt, a bottom navigation helper object for disabling the default shift behavior.

import android.support.design.internal.BottomNavigationItemView
import android.support.design.internal.BottomNavigationMenuView
import android.support.design.widget.BottomNavigationView
import android.util.Log

// https://stackoverflow.com/questions/40176244/how-to-disable-bottomnavigationview-shift-mode
object BottomNavigationViewHelper {
    fun disableShiftMode(view: BottomNavigationView) {
        val menuView = view.getChildAt(0) as BottomNavigationMenuView
        try {
            val shiftingMode = menuView.javaClass.getDeclaredField("mShiftingMode")
            shiftingMode.isAccessible = true
            shiftingMode.setBoolean(menuView, false)
            shiftingMode.isAccessible = false
            for (i in 0 until menuView.childCount) {
                val item = menuView.getChildAt(i) as BottomNavigationItemView

                item.setShiftingMode(false)
                // set once again checked value, so view will be updated

                item.setChecked(item.itemData.isChecked)
            }
        } catch (e: NoSuchFieldException) {
            Log.e("BNVHelper", "Unable to get shift mode field", e)
        } catch (e: IllegalAccessException) {
            Log.e("BNVHelper", "Unable to change value of shift mode", e)
        }

    }
}

Search within Codexpedia

Custom Search

Search the entire web

Custom Search