Android dynamic bottom navigation

Denis Coman
Agile Freaks
Published in
4 min readAug 3, 2020

Let’s say we need to implement a bottom navigation menu which will be controlled by API. This means that we can control from the serverside the following:

  • item image
  • item label
  • items order
  • items count (this should be between 3 and 5 as BotttomNavigationView requirements)

First of all, we need to add to module level build.gradle file all the dependencies that we will use for this project.

Androidx Navigation

def nav_version = "2.3.0"
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"

Picasso and Rx java. We will use this to load menu items icons.

implementation "com.squareup.picasso:picasso:2.71828"
implementation "io.reactivex.rxjava2:rxandroid:2.1.1"

Next, in our activity layout file we should add the BottomNavigationView, which we will populate later, and a NavHostFragment that will host all our fragments that can be triggerd by menu items

<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=".MainActivity">

<fragment
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="@id/nav_view"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/main_nav_graph" />

<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/nav_view"
android:layout_width="match_parent"
android:layout_height="64dp"
android:background="@color/colorPrimary"
app:itemIconTint="@color/menu_item"
app:itemTextColor="@color/menu_item"
app:labelVisibilityMode="labeled"
app:layout_constraintBottom_toBottomOf="parent"
tools:visibility="visible" />

</androidx.constraintlayout.widget.ConstraintLayout>

As you can see, NavHostFragment has an attribute called navGraph. To create this file, first we should create the Fragments that will represent the items that we can receive from the API. In this example we will use 3 fragments:

  • Home
  • Chat
  • Profile

After creating those fragments, we come back to navGraph. In res/navigation directory, create a navigation resource file called main_nav_graph. It should look like this:

<?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"
android:id="@+id/main_nav_graph"
app:startDestination="@id/home_dest">

<fragment
android:id="@+id/home_dest"
android:name="com.comandenis.example.home.HomeFragment" />
<fragment
android:id="@+id/chat_dest"
android:name="com.comandenis.example.chat.ChatFragment" />
<fragment
android:id="@+id/profile_dest"
android:name="com.comandenis.example.profile.ProfileFragment" />
</navigation>

Here should be declared a destination for all the fragments that is planned to have on bottom navigation view. Don’t forget to add startDestination property!

Let’s assume that we have all the data from the API and will map them into a MenuItem data class which will have the properties that we want to use in the menu. For this example we will use a labeled menu so we will need this 3 properties:

data class MenuItem(
val label: String,
val image: String,
val destinationId: Int
)

For the purpose of this post will create a hard-coded list of menu items

private val menuItems = listOf(
MenuItem(
label = "Home",
image = "https://i.ibb.co/B4nR76t/home-24px.png",
destinationId = R.id.home_dest
),
MenuItem(
label = "Chat",
image = "https://i.ibb.co/6gG1M71/message-24px.png",
destinationId = R.id.chat_dest
),
MenuItem(
label = "Profile",
image = "https://i.ibb.co/FHP56wG/perm-identity-24px.png",
destinationId = R.id.profile_dest
)
)

destinationId is the id of the fragment declared in the nav graph, so after getting the data from the API we should map that list so we can add the destinationId for each item.

Finally, let’s add the items to bottom navigation

menuItems.forEachIndexed { index, menuItem ->
nav_view.menu.add(Menu.NONE, menuItem.destinationId, index, menuItem.label)
}

Iterate over the menuItems list and add each item to the menu.

After doing this the result will be something like this.

It’s obvious that we still need to add icons for this items, so let’s do it!

data class Tuple(val menuItem: MenuItem, val bitmap: Bitmap)

val picasso = Picasso.get()

menuItems.forEachIndexed { index, menuItem ->
nav_view.menu.add(Menu.NONE, menuItem.destinationId, index, menuItem.label)
}

subscriptions.add(
Observable.fromIterable(menuItems)
.switchMap {
Observable.just(
Tuple(it, picasso.load(it.image).get())
)
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
val menuItem = nav_view.menu.findItem(it.menuItem.destinationId)
menuItem.icon = BitmapDrawable(resources, it.bitmap)
},
{
// Handle errors here
},
{
// On complete we should setup nav controller
val navController = findNavController(R.id.nav_host_fragment)

nav_view.setupWithNavController(navController)
}
)
)

Using rx java, I created an observable from the menu items list that emits each item. For each of those items we will use Picasso to get the from URL, then we will emit further a Tuple with the item and the bitmap that we get from Picasso.

When item arrive on the onNext method will search for the item in the menu that is already populated, convert the bitmap into drawable and set the icon of the item with it.

Here is the result

Hope this will help you. Feel free to get an hands-on and ask anything.

Happy Coding!

--

--