Navigation Drawer with Dynamic Modules in Android: Full Working Guide
Every serious Android app needs a way to let users move between features. A navigation drawer is still one of the cleanest patterns for that — a side panel with a header and a list of modules. But instead of a fixed menu, we will build a dynamic module grid where the items come from code. In this guide, I will show you how to build Navigation Drawer with Dynamic Modules in Android using DrawerLayout, NavigationView, RecyclerView, and a custom adapter — with full working Java code.
What is a Navigation Drawer with Dynamic Modules?
A navigation drawer is a panel that slides in from the side of the screen. It usually holds a user profile header and a menu of destinations. Users tap the hamburger icon or swipe from the edge to open it.
Dynamic Modules means the menu items are not hard-coded in XML. Instead, they come from a list built in code. That makes it easy to add, remove, or reorder modules without touching the layout files.
In this guide, the drawer shows a header card with the user's photo, name, and email, and below that a two-column grid of modules: Profile, Gallery, Slider, Logout, Share. Each module is an icon with a label. Tapping one would normally navigate to that feature.
The structure is:
- DrawerLayout — the outer container that holds the main content and the drawer
- NavigationView — the drawer panel itself, with a header layout and a menu
- RecyclerView — inside the header layout, shows the module grid
- CustomAdapter — binds the Module list to the RecyclerView
- Module — a small class with a name and an image id
Let's build it.
What the output looks like
Here is what you get at the end:
- A toolbar at the top with the hamburger icon
- Tapping the icon or swiping from the left opens the drawer
- The drawer header shows a circular profile photo, the user's name, and email on a dark card
- Below the header, a grid of module cards — two per row
- Each module has an icon and a label below it
You can change the modules, add more, or hook them up to real navigation targets later. The dynamic part is what makes this pattern flexible.
Project structure overview
The project is organized into packages. Here is what matters for this guide:
com.example.mynavigation
├── CustomAdapter.java
├── Module.java
├── ui
│ └── home
│ ├── HomeFragment.java
│ └── HomeViewModel.java
└── res
└── layout
├── activity_main.xml
├── app_bar_main.xml
├── content_main.xml
├── fragment_home.xml
└── item.xml
The HomeFragment lives in the ui.home package. The CustomAdapter and Module live in the root package because they are shared across the app. Layouts are split by screen so each one stays focused.
HomeFragment.java — the drawer content
HomeFragment is where the drawer content actually comes alive. It finds the RecyclerView, builds a list of modules, sets a two-column grid layout manager, and attaches the custom adapter.
package com.example.mynavigation.ui.home;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.example.mynavigation.CustomAdapter;
import com.example.mynavigation.Module;
import com.example.mynavigation.R;
import java.util.ArrayList;
import java.util.List;
public class HomeFragment extends Fragment {
private HomeViewModel homeViewModel;
public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_home, container, false);
return root;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
RecyclerView rec = view.findViewById(R.id.myrec);
dummyModule();
rec.setLayoutManager(new GridLayoutManager(getActivity(), 2));
rec.setAdapter(new CustomAdapter(list));
}
List<Module> list = new ArrayList<>();
private void dummyModule() {
Module m1 = new Module();
m1.setName("Profile");
m1.setImage(R.drawable.ic_menu_share);
Module m2 = new Module();
m2.setName("Gallery");
m2.setImage(R.drawable.ic_menu_gallery);
Module m3 = new Module();
m3.setName("Slider");
m3.setImage(R.drawable.ic_menu_camera);
Module m4 = new Module();
m4.setName("Logout");
m4.setImage(R.drawable.ic_menu_manage);
Module m5 = new Module();
m5.setName("Share");
m5.setImage(R.drawable.ic_menu_send);
list.add(m1);
list.add(m2);
list.add(m3);
list.add(m4);
list.add(m5);
}
}
Let's walk through it:
onCreateView
Inflates fragment_home.xml and returns the root view.
onViewCreated
Finds the RecyclerView by id myrec, calls dummyModule() to fill the list, sets a GridLayoutManager with 2 columns, and attaches a new CustomAdapter with the list.
dummyModule
Creates five Module objects with names and image resource ids, then adds them to the list. In a real app this data might come from an API, a database, or a config file. Here we just hardcode it to keep the example focused.
HomeViewModel.java
The ViewModel is part of the standard Android architecture. It holds data for the fragment and survives configuration changes like screen rotation.
package com.example.mynavigation.ui.home;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
public class HomeViewModel extends ViewModel {
private MutableLiveData<String> mText;
public HomeViewModel() {
mText = new MutableLiveData<>();
mText.setValue("This is home fragment");
}
public LiveData<String> getText() {
return mText;
}
}
The ViewModel is included here to show the standard pattern. It is not strictly necessary for the drawer to work, but it is how most modern Android projects are structured. You could remove it and the drawer would still function.
CustomAdapter.java — the RecyclerView adapter
The adapter is the bridge between the Module list and the RecyclerView. It creates a view for each item and binds the data to it.
package com.example.mynavigation;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
public class CustomAdapter extends RecyclerView.Adapter<CustomAdapter.MyView> {
List<Module> list;
public CustomAdapter(List<Module> list) {
this.list = list;
}
@NonNull
@Override
public MyView onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item,
parent, false);
return new MyView(view);
}
@Override
public void onBindViewHolder(@NonNull MyView holder, int position) {
Module m = list.get(position);
holder.im.setImageResource(m.getImage());
holder.tname.setText(m.getName());
}
@Override
public int getItemCount() {
return list.size();
}
class MyView extends RecyclerView.ViewHolder {
ImageView im;
TextView tname;
public MyView(@NonNull View itemView) {
super(itemView);
tname = itemView.findViewById(R.id.mytext);
im = itemView.findViewById(R.id.myimage);
}
}
}
Let's break it down:
Constructor
Takes the Module list and stores it. This is the data the adapter will show.
onCreateViewHolder
Inflates item.xml for each row in the grid. Each row has an ImageView and a TextView.
onBindViewHolder
Gets the Module at the current position, sets the image resource on the ImageView, and sets the name on the TextView. This is where the data actually connects to the view.
getItemCount
Returns the size of the list. RecyclerView uses this to know how many items to show.
MyView
The ViewHolder class. It caches the ImageView and TextView so we do not have to call findViewById on every bind. This is what makes RecyclerView fast.
Module.java — the data holder
The Module class is a simple data holder. It has a name and an image resource id, plus getters and setters.
package com.example.mynavigation;
public class Module {
private String name;
private Integer image;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getImage() {
return image;
}
public void setImage(Integer image) {
this.image = image;
}
}
That is the whole class. There is no logic here — it just holds the two values. Keeping data holders simple like this is a good practice. The adapter knows how to display a Module; the Module itself just carries the data.
Layouts: fragment_home, item, activity_main
fragment_home.xml
This is the main drawer content layout. It has a CardView header with a circular profile image, name, and email, and a RecyclerView below for the module grid.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.cardview.widget.CardView
android:id="@+id/myhead"
android:layout_width="match_parent"
android:layout_height="130dp"
app:cardBackgroundColor="@color/colorPrimaryDark"
app:cardCornerRadius="5dp"
app:cardMaxElevation="10dp"
app:cardUseCompatPadding="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<de.hdodenhof.circleimageview.CircleImageView
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginRight="20dp"
android:src="@drawable/teacher" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_weight="1"
android:gravity="center"
android:text="Deep Singh"
android:textColor="#fff"
android:textSize="20sp"
android:textStyle="bold"
android:typeface="serif" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:layout_weight="1"
android:gravity="center"
android:text="deep@gmail.com"
android:textColor="#fff"
android:typeface="serif" />
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/myrec"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/myhead" />
</RelativeLayout>
The CardView uses cardBackgroundColor="@color/colorPrimaryDark" to give it a dark background, and cardCornerRadius="5dp" for slight rounded corners. The CircleImageView is from the popular de.hdodenhof library — add that dependency if you do not have it yet. The RecyclerView sits below the header using layout_below.
item.xml
The item layout for each module. An ImageView on top, a TextView below, both centered.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="20dp">
<ImageView
android:id="@+id/myimage"
android:layout_width="50dp"
android:src="@drawable/std"
android:layout_gravity="center"
android:layout_height="50dp" />
<TextView
android:id="@+id/mytext"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Deep"
android:textStyle="bold"
android:layout_gravity="center"/>
</LinearLayout>
Each item is a small vertical stack. The layout_gravity="center" centers both the icon and the label. The padding gives each card breathing room in the grid.
app_bar_main, content_main, and strings
activity_main.xml
The outer DrawerLayout. It holds the main content and the NavigationView.
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout
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/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:openDrawer="start">
<include
layout="@layout/app_bar_main"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.google.android.material.navigation.NavigationView
android:id="@+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
app:headerLayout="@layout/nav_header_main"
app:menu="@menu/activity_main_drawer" />
</androidx.drawerlayout.widget.DrawerLayout>
Note the tools:openDrawer="start" attribute. That is only for Android Studio's preview — it does not affect runtime behavior.
app_bar_main.xml
Holds the toolbar, the main content, and the floating action button.
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
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">
<include layout="@layout/content_main" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"
app:srcCompat="@android:drawable/ic_dialog_email" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
The FAB is optional. You can remove it if you do not need a floating action button on the home screen.
content_main.xml
The content area that hosts the Navigation component's NavHostFragment.
<?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"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:showIn="@layout/app_bar_main">
<fragment
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/mobile_navigation" />
</androidx.constraintlayout.widget.ConstraintLayout>
This is the standard Navigation component setup. The navGraph attribute points to your navigation graph, which is what ties the drawer menu items to actual fragments.
strings.xml
<resources>
<string name="app_name">MyNavigation</string>
<string name="navigation_drawer_open">Open navigation drawer</string>
<string name="navigation_drawer_close">Close navigation drawer</string>
<string name="nav_header_title">Android Studio</string>
<string name="nav_header_subtitle">android.studio@android.com</string>
<string name="nav_header_desc">Navigation header</string>
<string name="action_settings">Settings</string>
<string name="menu_home">Home</string>
<string name="menu_gallery">Gallery</string>
<string name="menu_slideshow">Slideshow</string>
<string name="menu_profile">Profile</string>
</resources>
Keeping text in strings.xml is standard Android practice. It makes localization easy and keeps layouts clean.
Common questions
What is Navigation Drawer with Dynamic Modules in Android?
Navigation Drawer with Dynamic Modules is a UI pattern where a side drawer shows a header card and a grid of module icons. Each module is a clickable item that loads a different feature, like Profile, Gallery, or Slider. Dynamic means the module list comes from code or data, not a fixed XML menu.
What is the difference between NavigationView and RecyclerView in a drawer?
NavigationView shows a fixed menu defined in an XML menu resource. RecyclerView with a custom adapter shows a dynamic list of items built in code. Use NavigationView for simple, static menus and RecyclerView for flexible, data-driven modules.
Why use GridLayoutManager with 2 columns?
GridLayoutManager with 2 columns shows two module cards per row, which looks like a dashboard. It uses screen space well on phones and scales naturally to more modules without changing the layout code.
What does the Module class represent?
The Module class is a simple data holder with a name and an image resource id. Each instance represents one module in the drawer. The CustomAdapter reads these values and binds them to the item views.
How do I add a new module to the drawer?
Create a new Module object, call setName and setImage, and add it to the list in dummyModule(). The RecyclerView will show it automatically because the adapter reads from the list.
Why use HomeViewModel in this example?
HomeViewModel is a ViewModel that holds data for the HomeFragment. It is lifecycle-aware, so it survives configuration changes like screen rotation. It is included to show the standard Android architecture pattern.
Where to go next
You now have a complete, working Navigation Drawer with Dynamic Modules in Android. You know the DrawerLayout structure, how to build a header card, how to use RecyclerView with a two-column grid, and how to bind dynamic data with a custom adapter.
If you remember only one thing, remember this: DrawerLayout holds the content and the drawer. NavigationView shows the drawer. RecyclerView inside it makes the modules dynamic. The adapter connects the Module list to the item views.
The real learning happens when you run this in Android Studio. Add a sixth module. Change the header image. Hook up a click listener on each module to navigate to a fragment. That is how the pattern becomes second nature.
If this helped, bookmark it. Next, connect the modules to the Navigation component so tapping "Gallery" actually opens the Gallery fragment. That is the natural next step.
No comments:
Post a Comment