NOTE: Refer Android Setup BottomNavigationView With Jetpack Navigation UI (Kotlin).
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 fragmentif (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>
NOTE: Refer Android Setup BottomNavigationView With Jetpack Navigation UI (Kotlin).