Android Tutorial: Master-Detail UI with RecyclerView and Fragments

In this tutorial, we will build a modern, list-based Android application. We will use a Single Activity Architecture, meaning our main screen will be hosted inside a Fragment. We will implement both a horizontal and a vertical RecyclerView on the home screen, and make them clickable to open a separate Detail Screen.

App Preview: What We Are Building

Interact with the phone below! You can scroll the top section horizontally, and the bottom section vertically, demonstrating the standard Android RecyclerView behavior.

My App
Horizontal List
Category 1
Category 2
Category 3
Category 4
Vertical List
List Item 1

Brief description for item 1…

List Item 2

Brief description for item 2…

List Item 3

Brief description for item 3…

List Item 4

Brief description for item 4…

Step 1: Update the Main Activity Setup

Replace the default TextView with a FragmentContainerView to host our Fragments. Then update your MainActivity.java to load the MainFragment initially.

res/layout/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/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/fragment_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity.java
package com.example.recyclerview;

import android.os.Bundle;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_main);

        // Load MainFragment initially
        if (savedInstanceState == null) {
            getSupportFragmentManager().beginTransaction()
                    .replace(R.id.fragment_container, new MainFragment())
                    .commit();
        }
    }
}

Step 2: Create a Serializable Data Model

Create a simple MyItem.java class to represent a single list item. We add implements Serializable so this object can be passed between Fragments later.

MyItem.java
package com.example.recyclerview;

import java.io.Serializable;

public class MyItem implements Serializable {
    private String title;
    private String description;

    public MyItem(String title, String description) {
        this.title = title;
        this.description = description;
    }

    public String getTitle() { return title; }
    public String getDescription() { return description; }
}

Step 3: Create the Item Card Layout

Create a layout file that defines how a single list item will look visually in our lists.

res/layout/item_card.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="200dp"
    android:layout_height="wrap_content"
    android:layout_margin="8dp"
    app:cardCornerRadius="8dp"
    app:cardElevation="4dp">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:padding="16dp">

        <ImageView
            android:layout_width="match_parent"
            android:layout_height="100dp"
            android:background="#e0e0e0"
            android:scaleType="centerCrop" />

        <TextView
            android:id="@+id/text_item_title"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp"
            android:text="Item Title"
            android:textSize="16sp"
            android:textStyle="bold" />

        <TextView
            android:id="@+id/text_item_desc"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="4dp"
            android:text="Item description..."
            android:textSize="14sp" />
    </LinearLayout>

</androidx.cardview.widget.CardView>

Step 4: Build the Adapter with Click Listeners

Next, build the adapter to bind your data to your layout. We create an OnItemClickListener interface so the Fragment knows exactly when an item is tapped.

MyAdapter.java
package com.example.recyclerview;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {

    private List<MyItem> itemList;
    private OnItemClickListener listener;

    // 1. Create the click listener interface
    public interface OnItemClickListener {
        void onItemClick(MyItem item);
    }

    // 2. Require the listener in the constructor
    public MyAdapter(List<MyItem> itemList, OnItemClickListener listener) {
        this.itemList = itemList;
        this.listener = listener;
    }

    @NonNull
    @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.item_card, parent, false);
        return new MyViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
        MyItem item = itemList.get(position);
        holder.title.setText(item.getTitle());
        holder.description.setText(item.getDescription());

        // 3. Attach the click listener to the whole item view
        holder.itemView.setOnClickListener(v -> {
            if (listener != null) {
                listener.onItemClick(item);
            }
        });
    }

    @Override
    public int getItemCount() {
        return itemList.size();
    }

    public static class MyViewHolder extends RecyclerView.ViewHolder {
        TextView title, description;

        public MyViewHolder(@NonNull View itemView) {
            super(itemView);
            title = itemView.findViewById(R.id.text_item_title);
            description = itemView.findViewById(R.id.text_item_desc);
        }
    }
}

Step 5: Detail Screen Layout & Class

Create the layout for the full-screen view, and the Fragment class to pull the data from the Bundle and display it.

res/layout/fragment_detail.xml
<?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="match_parent"
    android:orientation="vertical"
    android:padding="24dp"
    android:background="#FFFFFF">

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="250dp"
        android:background="#e0e0e0"
        android:scaleType="centerCrop" />

    <TextView
        android:id="@+id/detail_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="24dp"
        android:text="Detail Title"
        android:textSize="28sp"
        android:textStyle="bold" />

    <TextView
        android:id="@+id/detail_desc"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:text="Full description goes here..."
        android:textSize="18sp" />

</LinearLayout>
DetailFragment.java
package com.example.recyclerview;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;

public class DetailFragment extends Fragment {

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_detail, container, false);

        TextView titleView = view.findViewById(R.id.detail_title);
        TextView descView = view.findViewById(R.id.detail_desc);

        // Retrieve the data passed from the MainFragment
        if (getArguments() != null) {
            MyItem selectedItem = (MyItem) getArguments().getSerializable("item_data");
            
            if (selectedItem != null) {
                titleView.setText(selectedItem.getTitle());
                descView.setText(selectedItem.getDescription());
            }
        }

        return view;
    }
}

Step 6: Main Screen Layout & Class

Finally, we tie it all together in the MainFragment. Here we setup our horizontal and vertical lists, generate dummy data, and launch a FragmentTransaction when an item is clicked.

res/layout/fragment_main.xml
<?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="match_parent"
    android:orientation="vertical"
    android:padding="16dp">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Horizontal Category List"
        android:textSize="20sp"
        android:textStyle="bold" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler_horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="16dp" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Vertical Details List"
        android:textSize="20sp"
        android:textStyle="bold" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler_vertical"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

</LinearLayout>
MainFragment.java
package com.example.recyclerview;

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.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;

public class MainFragment extends Fragment {

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_main, container, false);

        RecyclerView recyclerHorizontal = view.findViewById(R.id.recycler_horizontal);
        RecyclerView recyclerVertical = view.findViewById(R.id.recycler_vertical);

        // Generate Dummy Data
        List<MyItem> dummyData = new ArrayList<>();
        dummyData.add(new MyItem("Item 1", "This is the first description..."));
        dummyData.add(new MyItem("Item 2", "This is the second description..."));
        dummyData.add(new MyItem("Item 3", "This is the third description..."));
        dummyData.add(new MyItem("Item 4", "This is the fourth description..."));

        // Initialize Adapter AND listen for clicks
        MyAdapter adapter = new MyAdapter(dummyData, new MyAdapter.OnItemClickListener() {
            @Override
            public void onItemClick(MyItem item) {
                
                // Prepare the Detail Fragment
                DetailFragment detailFragment = new DetailFragment();
                
                // Put the clicked item into a Bundle
                Bundle bundle = new Bundle();
                bundle.putSerializable("item_data", item);
                detailFragment.setArguments(bundle);

                // Swap the fragments
                if (getActivity() != null) {
                    getActivity().getSupportFragmentManager().beginTransaction()
                            .replace(R.id.fragment_container, detailFragment)
                            .addToBackStack(null) // Allows the user to press the back button
                            .commit();
                }
            }
        });

        // Setup horizontal layout
        recyclerHorizontal.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false));
        recyclerHorizontal.setAdapter(adapter);

        // Setup vertical layout
        recyclerVertical.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false));
        recyclerVertical.setAdapter(adapter);

        return view;
    }
}