A Fragment is a reusable, modular section of an Activity’s UI. Think of it as a "mini-Activity" that lives inside an Activity.
If an Activity is a “full page,” then Fragments are “sections” of that page that can be swapped in or out dynamically
- To reuse parts of UI in multiple activities.
- To support different screen sizes (e.g., tablets show multiple fragments side-by-side).
- To manage dynamic UIs where one part changes while others stay the same.
- To enable navigation without switching activities (via FragmentTransaction).
A fragment has its own lifecycle — but it is tied to the activity’s lifecycle.
Here is the updated Fragment Lifecycle table with the full method prototypes (access modifiers, return types, and parameters).
| Method Prototype | When It Runs | Purpose |
|---|---|---|
public void onAttach(@NonNull Context context) |
Fragment is first attached to its parent Activity. | Get a reference to the Activity (context) to use later. |
public void onCreate(@Nullable Bundle savedInstanceState) |
Fragment object is created (View does not exist yet). | Initialize non-UI variables (e.g., retrieve params from setArguments). |
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) |
The UI is being drawn for the first time. | Inflate the XML layout and return the View to the Activity. |
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) |
Immediately after onCreateView returns. |
Best place for logic. Find Views (findViewById), set Listeners, and update text. |
public void onStart() |
Fragment becomes visible. | The UI is visible on screen, but user cannot tap it yet. |
public void onResume() |
Fragment is active & interactive. | The user can now tap buttons and interact. |
public void onPause() |
User is leaving (or multi-window mode). | Save urgent data. Stop animations that shouldn't run if unseen. |
public void onStop() |
Fragment is fully hidden (background). | Stop heavy operations (Network calls, Database). |
public void onDestroyView() |
The View is being removed from the screen. | Clean up UI references (set binding = null) to prevent memory leaks. |
public void onDestroy() |
The Fragment object is doing final cleanup. | The object is about to be garbage collected. |
public void onDetach() |
Fragment is detached from the Activity. | Release the reference to the Activity/Context. |
When displaying lists (ListView / RecyclerView) inside a Fragment, an Adapter acts as a bridge between:
• Data source → (e.g., ArrayList) • UI component → (e.g., ListView)
Example:
PassengerAdapter adapter = new PassengerAdapter(requireContext(), AppData.getPassengers());
listView.setAdapter(adapter);- PassengerAdapter → Custom class extending ArrayAdapter
- AppData.getPassengers() → Provides data list
- listView.setAdapter(adapter) → Binds data to UI
- getView() (inside adapter) → Called for each row to create or modify list item views
its a good fit when:
- You have a single Activity managing multiple fragments, are created at Activity startup, have fixed IDs, are always alive when you press the button.
- You want synchronous, immediate communication between fragments.
- You want to validate or coordinate data in the Activity (like checking both inputs).
This approach starts breaking down when:
-
Fragments are added/replaced dynamically.
- For example, when you replace Fragment A with Fragment B using transactions, you may lose the direct reference (
findFragmentById()returns null). - In that case, you’d need to pass data through
setArguments(Bundle)before adding/replacing.
- For example, when you replace Fragment A with Fragment B using transactions, you may lose the direct reference (
-
You have deeply nested or decoupled fragments.
- The Activity becomes a “middleman” for too many things, leading to tight coupling and messy code.
-
You want reusable fragments (used in multiple activities).
- This method hard-codes the Activity as the central hub, so fragments depend on it.
We’ll have:
- MainActivity.java → hosts 3 fragments.
- FragmentOne.java → top-left fragment (with EditText 1).
- FragmentTwo.java → top-right fragment (with EditText 2).
- FragmentThree.java → bottom fragment (displays combined data).
- activity_main.xml → layout that arranges the fragments (2 on top, 1 below, and a button).
Here we define 3 fragments and a button.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:padding="16dp">
<!-- Top two fragments in a horizontal layout -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="horizontal">
<fragment
android:id="@+id/fragmentOne"
android:name="com.example.myapp.FragmentOne"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent" />
<fragment
android:id="@+id/fragmentTwo"
android:name="com.example.myapp.FragmentTwo"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent" />
</LinearLayout>
<!-- Bottom fragment -->
<fragment
android:id="@+id/fragmentThree"
android:name="com.example.myapp.FragmentThree"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<!-- Button -->
<Button
android:id="@+id/btnSendData"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Send Data to Bottom Fragment" />
</LinearLayout>Here we handle button click, fetch data from both top fragments, bundle it, and pass it to bottom fragment.
package com.example.myapp;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
FragmentOne fragmentOne;
FragmentTwo fragmentTwo;
FragmentThree fragmentThree;
Button btnSendData;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
fragmentOne = (FragmentOne) getSupportFragmentManager().findFragmentById(R.id.fragmentOne);
fragmentTwo = (FragmentTwo) getSupportFragmentManager().findFragmentById(R.id.fragmentTwo);
fragmentThree = (FragmentThree) getSupportFragmentManager().findFragmentById(R.id.fragmentThree);
btnSendData = findViewById(R.id.btnSendData);
btnSendData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String text1 = fragmentOne.getInputText();
String text2 = fragmentTwo.getInputText();
if (text1.isEmpty() || text2.isEmpty()) {
Toast.makeText(MainActivity.this, "Please enter text in both fields!", Toast.LENGTH_SHORT).show();
return;
}
// Create bundle
Bundle bundle = new Bundle();
bundle.putString("data1", text1);
bundle.putString("data2", text2);
// Pass to FragmentThree
fragmentThree.updateData(bundle);
}
});
}
}package com.example.myapp;
import android.os.Bundle;
import androidx.fragment.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
public class FragmentOne extends Fragment {
EditText editText;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_one, container, false);
editText = view.findViewById(R.id.editText1);
return view;
}
public String getInputText() {
return editText.getText().toString().trim();
}
}package com.example.myapp;
import android.os.Bundle;
import androidx.fragment.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
public class FragmentTwo extends Fragment {
EditText editText;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_two, container, false);
editText = view.findViewById(R.id.editText2);
return view;
}
public String getInputText() {
return editText.getText().toString().trim();
}
}package com.example.myapp;
import android.os.Bundle;
import androidx.fragment.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
public class FragmentThree extends Fragment {
TextView textViewResult;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_three, container, false);
textViewResult = view.findViewById(R.id.textViewResult);
return view;
}
public void updateData(Bundle bundle) {
String data1 = bundle.getString("data1");
String data2 = bundle.getString("data2");
textViewResult.setText("Received: " + data1 + " & " + data2);
}
}<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="8dp">
<EditText
android:id="@+id/editText1"
android:hint="Enter first text"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</FrameLayout><FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="8dp">
<EditText
android:id="@+id/editText2"
android:hint="Enter second text"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</FrameLayout><FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="8dp">
<TextView
android:id="@+id/textViewResult"
android:text="Result will appear here"
android:textSize="18sp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</FrameLayout>- User enters text in FragmentOne and FragmentTwo.
- Button click in MainActivity collects data from both.
- Bundle is created and sent to FragmentThree.
- FragmentThree updates its UI to show both inputs.
- You create fragment instances at runtime,
- attach data to them using a
Bundle(throughsetArguments()), - and then show/replace them dynamically using a
FragmentTransaction.
Basic pattern:
FragmentX fragment = new FragmentX();
Bundle bundle = new Bundle();
bundle.putString("key", "value");
fragment.setArguments(bundle);
getSupportFragmentManager().beginTransaction()
.replace(R.id.container, fragment)
.commit();In FragmentX.java:
Bundle args = getArguments();
if (args != null) {
String value = args.getString("key");
}✅ When fragments are created dynamically, e.g.:
- You show a detail screen based on user selection.
- You switch fragments via navigation or tabs.
- You add fragments one on top of another (stack behavior).
✅ When fragments are not known in advance (not in XML).
✅ When you want the data to survive configuration changes (Android automatically saves fragment arguments).
| Situation | Why it fails | Better Options |
|---|---|---|
| You need two-way real-time updates (like chat or live counter) | Bundle is one-time — not live | Shared ViewModel or LiveData |
| You pass non-serializable objects (like large custom classes) | Use repository/ViewModel | Bundle only supports primitives, Strings, Parcelable |
Fragment already exists and you call setArguments() again |
Throws exception (only valid before attach) | Use fragment methods or ViewModel |
Let’s imagine a MainActivity with:
-
An EditText for user input
-
Three buttons to open Fragment 1, 2, or 3
-
Two control buttons:
Replace Fragment(replaces current fragment)Add Fragment(adds new fragment over the current one)
This gives you full control:
- Choose which fragment to show
- Choose whether to replace or add the new one
- Each fragment shows the text entered in the EditText
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:padding="16dp"
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:id="@+id/inputField"
android:hint="Enter text to send"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<!-- Fragment Selection Buttons -->
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:id="@+id/btnFragment1"
android:text="Fragment 1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/btnFragment2"
android:text="Fragment 2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/btnFragment3"
android:text="Fragment 3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
<!-- Add vs Replace Control -->
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:id="@+id/btnReplace"
android:text="Replace"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/btnAdd"
android:text="Add Over"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
<!-- Container for Fragments -->
<FrameLayout
android:id="@+id/fragmentContainer"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="#EEEEEE"/>
</LinearLayout>public class MainActivity extends AppCompatActivity {
EditText inputField;
Button btnFrag1, btnFrag2, btnFrag3, btnReplace, btnAdd;
boolean useReplace = true; // toggle add/replace mode
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
inputField = findViewById(R.id.inputField);
btnFrag1 = findViewById(R.id.btnFragment1);
btnFrag2 = findViewById(R.id.btnFragment2);
btnFrag3 = findViewById(R.id.btnFragment3);
btnReplace = findViewById(R.id.btnReplace);
btnAdd = findViewById(R.id.btnAdd);
btnReplace.setOnClickListener(v -> useReplace = true);
btnAdd.setOnClickListener(v -> useReplace = false);
btnFrag1.setOnClickListener(v -> showFragment(new FragmentOne()));
btnFrag2.setOnClickListener(v -> showFragment(new FragmentTwo()));
btnFrag3.setOnClickListener(v -> showFragment(new FragmentThree()));
}
private void showFragment(Fragment fragment) {
String text = inputField.getText().toString().trim();
if (text.isEmpty()) {
Toast.makeText(this, "Enter some text first!", Toast.LENGTH_SHORT).show();
return;
}
// Attach data to fragment
Bundle bundle = new Bundle();
bundle.putString("inputText", text);
fragment.setArguments(bundle);
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
if (useReplace)
ft.replace(R.id.fragmentContainer, fragment);
else
ft.add(R.id.fragmentContainer, fragment);
ft.addToBackStack(null); // allow back navigation
ft.commit();
}
}<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:gravity="center"
android:padding="20dp"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/textViewFragment"
android:textSize="18sp"
android:textStyle="bold"
android:textColor="#000"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>public class FragmentOne extends Fragment {
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_layout, container, false);
TextView textView = view.findViewById(R.id.textViewFragment);
Bundle args = getArguments();
if (args != null) {
String text = args.getString("inputText");
textView.setText("Fragment 1 received: " + text);
}
return view;
}
}Repeat for FragmentTwo and FragmentThree, only changing the label text.
-
Each time user presses a fragment button → Activity creates a new fragment instance.
-
The Activity packages the input text inside a Bundle.
-
The fragment receives it in
getArguments(). -
The
useReplacetoggle controls whether:- The old fragment is replaced (previous destroyed), or
- The new one is added over it (previous remains underneath, and user can pop back).
A ListView is a UI component used to display a scrollable list of items (like messages, contacts, settings, etc.). Each row of the list is typically made from a layout (e.g., a TextView or a custom layout with image + text).
The key idea:
Instead of manually adding multiple TextViews or Buttons, we use a ListView that automatically creates, reuses, and manages views for large data sets efficiently.
A bridge between your data and your ListView. The adapter converts each item in your data source (array, list, cursor, etc.) into a View that ListView can display.
There are different types:
| Adapter | Description | Typical Use |
|---|---|---|
ArrayAdapter |
Simplest one | binds arrays/lists of Strings or simple objects Basic text lists |
SimpleAdapter |
Maps data from a list of maps to views | Custom data |
CursorAdapter |
Works with SQLite database cursors | Database-backed lists |
BaseAdapter |
Fully customizable | Complex custom lists |
You create your adapter → set it on your ListView:
listView.setAdapter(adapter);Then Android will:
- Ask adapter: “How many items?”
- For each position, call
getView(position)to create/recycle a row view. - Display those efficiently with scrolling & recycling.
A ListFragment is a special subclass of Fragment designed specifically for lists. It already includes a built-in ListView, so you don’t have to declare it in your XML. You just set your adapter with:
setListAdapter(adapter);Then you get:
- Automatic click handling with
onListItemClick() - Lifecycle already managing the internal ListView
- Less boilerplate code
- The fragment’s main content is a list (like contact list, email inbox, etc.)
- You want quick setup, minimal XML
- You need other views around your list (e.g., a search bar, buttons, etc.)
- You want custom layout control
When a user clicks an item:
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
String selected = (String) l.getItemAtPosition(position);
}positiongives the index in your dataset.idis usually the database ID if data comes from a DB.getContext()orrequireContext()gives the Activity context.
In fragments with lists, you should:
- Initialize adapter in
onCreateView()oronViewCreated() - Attach listeners (like item clicks) in
onViewCreated() - Access context safely — use
requireContext()instead ofgetContext()if you’re sure fragment is attached.
If you need to communicate back to Activity (like item clicked → show detail), use:
- A callback interface, or
- A shared ViewModel (modern way)
- Left fragment (
PeopleFragment): shows a list of people. - Right fragment (
ChatFragment): shows the chat area. - When you click a person in the list, data is sent to the activity using an interface, and the activity then updates the
ChatFragment.
-
PeopleFragmentuses a built-in ListView + ArrayAdapter.- It defines an interface
OnPersonSelectedListener. - The Activity implements this interface.
- When a name is clicked → fragment calls the interface method.
- It defines an interface
-
MainActivityimplements the interface.- When notified, it finds the
ChatFragmentand tells it which person was selected.
- When notified, it finds the
-
ChatFragmentupdates its TextView to show that person’s name.
We’ll use a simple horizontal layout for both fragments.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="@+id/peopleFragment"
android:name="com.example.chatdemo.PeopleFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<fragment
android:id="@+id/chatFragment"
android:name="com.example.chatdemo.ChatFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2" />
</LinearLayout>Note: Built-in ListView uses id @android:id/list, not a custom one.
<?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">
<ListView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout><?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:padding="20dp"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/chatText"
android:text="Select a person to chat"
android:textSize="18sp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>package com.example.chatdemo;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.ListFragment;
// Using ListFragment simplifies setup — no need to define onCreateView
public class PeopleFragment extends ListFragment {
// Define interface for communication
interface OnPersonSelectedListener {
void onPersonSelected(String name);
}
private OnPersonSelectedListener listener;
private String[] people = {"Ali", "Sara", "Rauf", "John", "Mina"};
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
// Polymorphism: activity must implement interface
if (context instanceof OnPersonSelectedListener) {
listener = (OnPersonSelectedListener) context;
} else {
throw new ClassCastException(context.toString() +
" must implement OnPersonSelectedListener");
}
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// Built-in adapter for simple text list
ArrayAdapter<String> adapter = new ArrayAdapter<>(
requireContext(),
android.R.layout.simple_list_item_1,
people
);
setListAdapter(adapter);
}
@Override
public void onListItemClick(@NonNull ListView l, @NonNull View v, int position, long id) {
super.onListItemClick(l, v, position, id);
String selected = (String) l.getItemAtPosition(position);
Toast.makeText(requireContext(), "Clicked: " + selected, Toast.LENGTH_SHORT).show();
// Inform the activity via interface callback
if (listener != null) listener.onPersonSelected(selected);
}
}package com.example.chatdemo;
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 ChatFragment extends Fragment {
private TextView chatText;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_chat, container, false);
chatText = view.findViewById(R.id.chatText);
return view;
}
// Public method that can be called by activity
public void updateChat(String name) {
chatText.setText("Chatting with " + name);
}
}package com.example.chatdemo;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.FragmentManager;
public class MainActivity extends AppCompatActivity
implements PeopleFragment.OnPersonSelectedListener {
private ChatFragment chatFragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Get ChatFragment using FragmentManager
FragmentManager fm = getSupportFragmentManager();
chatFragment = (ChatFragment) fm.findFragmentById(R.id.chatFragment);
}
@Override
public void onPersonSelected(String name) {
// Interface polymorphism in action
chatFragment.updateChat(name);
}
// another way by getting view of fragment rather than calling public function
public void onNameSelected(String name) {
FragmentManager fm = getSupportFragmentManager();
ChatFragment chatFragment = (ChatFragment) fm.findFragmentById(R.id.fragChat);
if (chatFragment != null && chatFragment.getView() != null) {
// Direct access to TextView inside fragment
TextView tv = chatFragment.getView().findViewById(R.id.tv_chatInfo);
tv.setText("Chatting with: " + name);
}
}
}PeopleFragment(ListFragment) inflates automatically with its built-in ListView (@android:id/list).onAttach()runs → attaches interface listener to Activity.onActivityCreated()sets adapter for the list.- User clicks an item →
onListItemClick()is triggered. listener.onPersonSelected(name)calls the method inMainActivity.MainActivitythen callschatFragment.updateChat(name)to change text.- UI updates.
When a fragment has its own XML, you always access views via the inflated
View, not withfindViewById()directly on the activity.
Example:
View view = inflater.inflate(R.layout.fragment_chat, container, false);
TextView tv = view.findViewById(R.id.chatText);If you use getView() later (after onCreateView), then:
TextView tv = getView().findViewById(R.id.chatText);Never use getActivity().findViewById() unless you want a view from the activity’s layout, not the fragment’s.
Each fragment has its own lifecycle, layout, and data source.
So when you use a ListView in both fragments (say, Students and Teachers), each fragment:
- manages its own
ListView - sets its own adapter
- handles its own
onListItemClick()orOnItemClickListener.
That’s why we must create two adapter instances — even if they’re both simple ArrayAdapter<String> types.
Fragments can’t directly talk to each other — they must go through the Activity that hosts them.
So, when an item in one fragment is clicked:
- That fragment calls a method of an interface, implemented by the Activity.
- The Activity then decides what to do (e.g., show another fragment or update its view).
| Approach | How It Works | When to Use |
|---|---|---|
| A: Directly access fragment’s view | Activity finds the fragment using getSupportFragmentManager().findFragmentById() and then findViewById() on it. |
When the fragment’s layout is simple (e.g., just a TextView) and you only need to set text or color. |
| B: Call a function of the target fragment | Activity finds the fragment, then calls a method (like updateInfo(String text)) inside it. |
When the update is more complex (e.g., involves logic or multiple UI components). |
We’ll use both methods in this example — one for the Students list, and one for the Teachers list.
- FragmentA → Students list
- FragmentB → Teachers list
- FragmentInfo → shows selected person’s info in full screen
- MainActivity → acts as communication bridge
We’ll use:
- Built-in ListFragment
- Built-in ArrayAdapter
- Interface callbacks
- Fragment Transactions to show the info fragment dynamically.
<?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">
<!-- Top part: Two fragments side by side -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="horizontal">
<fragment
android:id="@+id/fragStudents"
android:name="com.example.school.FragmentStudents"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<fragment
android:id="@+id/fragTeachers"
android:name="com.example.school.FragmentTeachers"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
</LinearLayout>
<!-- Bottom hidden fragment for info (appears when clicked) -->
<FrameLayout
android:id="@+id/fragInfoContainer"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>package com.example.school;
import android.content.Context;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import androidx.fragment.app.ListFragment;
public class FragmentStudents extends ListFragment {
// Interface reference
OnPersonSelected listener;
public interface OnPersonSelected {
void onStudentSelected(String name);
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof OnPersonSelected)
listener = (OnPersonSelected) context;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
String[] students = {"Ali", "Sara", "Hina", "Zain"};
ArrayAdapter<String> adapter = new ArrayAdapter<>(
getActivity(),
android.R.layout.simple_list_item_1,
students
);
setListAdapter(adapter);
}
@Override
public void onListItemClick(ListView l, android.view.View v, int position, long id) {
String name = (String) getListView().getItemAtPosition(position);
if (listener != null)
listener.onStudentSelected(name);
}
}package com.example.school;
import android.content.Context;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import androidx.fragment.app.ListFragment;
public class FragmentTeachers extends ListFragment {
OnPersonSelected listener;
public interface OnPersonSelected {
void onTeacherSelected(String name);
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof OnPersonSelected)
listener = (OnPersonSelected) context;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
String[] teachers = {"Mr. Khan", "Ms. Fatima", "Sir Aslam"};
ArrayAdapter<String> adapter = new ArrayAdapter<>(
getActivity(),
android.R.layout.simple_list_item_1,
teachers
);
setListAdapter(adapter);
}
@Override
public void onListItemClick(ListView l, android.view.View v, int position, long id) {
String name = (String) getListView().getItemAtPosition(position);
if (listener != null)
listener.onTeacherSelected(name);
}
}package com.example.school;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.fragment.app.Fragment;
public class FragmentInfo extends Fragment {
TextView tvInfo;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_info, container, false);
tvInfo = v.findViewById(R.id.tv_info);
return v;
}
// Method used when activity calls directly (Approach B)
public void updateInfo(String text) {
if (tvInfo != null)
tvInfo.setText(text);
}
}<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tv_info"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:textSize="22sp"
android:text="Select a person" />
</FrameLayout>package com.example.school;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction;
import android.os.Bundle;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity
implements FragmentStudents.OnPersonSelected,
FragmentTeachers.OnPersonSelected {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
// When a student is clicked → show info via FUNCTION CALL (Approach B)
@Override
public void onStudentSelected(String name) {
FragmentInfo infoFrag = new FragmentInfo();
getSupportFragmentManager().beginTransaction()
.replace(R.id.fragInfoContainer, infoFrag)
.addToBackStack(null)
.commit();
// Delay until fragment attached
getSupportFragmentManager().executePendingTransactions();
infoFrag.updateInfo("Student selected: " + name);
}
// When a teacher is clicked → show info via DIRECT VIEW ACCESS (Approach A)
@Override
public void onTeacherSelected(String name) {
FragmentInfo infoFrag = new FragmentInfo();
getSupportFragmentManager().beginTransaction()
.replace(R.id.fragInfoContainer, infoFrag)
.addToBackStack(null)
.commit();
getSupportFragmentManager().executePendingTransactions();
// Access TextView directly from fragment’s view
TextView tv = infoFrag.getView().findViewById(R.id.tv_info);
tv.setText("Teacher selected: " + name);
}
}1️⃣ User clicks a student name → FragmentStudents → calls listener.onStudentSelected().
2️⃣ MainActivity receives that callback.
3️⃣ It loads FragmentInfo and calls updateInfo() to set text.
4️⃣ User clicks a teacher name → FragmentTeachers → calls listener.onTeacherSelected().
5️⃣ MainActivity again loads FragmentInfo but now accesses its TextView directly.
Both end up showing info, but via different communication styles.
| Style | Good for | Avoid when |
|---|---|---|
| Direct view access | Quick UI updates (e.g., one TextView) | Fragment may not be fully created → can cause `NullPointerException |
| Function call | Safer and cleaner for logic/UI both | Requires exposing public methods |
The built-in adapters (ArrayAdapter, SimpleAdapter, SimpleCursorAdapter) are limited.
They work well only when:
- Each list item has a single text line (or maybe text + small image).
- The data is simple, like an array of strings.
But in most real apps (chat lists, contact lists, news feeds, etc.), each row needs:
- Multiple text fields (e.g., name + message + time)
- Images (e.g., profile picture)
- Buttons, CheckBoxes, or progress indicators
- Custom fonts, colors, alignment, etc.
In those cases, we make our own layout for each row and our own Adapter class to populate it.
| Feature | Built-in ArrayAdapter |
Custom Adapter |
|---|---|---|
| Layout per row | Predefined (simple_list_item_1, etc.) |
Your own XML layout (custom_row.xml) |
| Data Source | Usually just List |
Can be list of objects (List) |
| Binding Data | Automatic (adapter handles it) | You manually set values to views |
| Performance | Good for simple lists | You must handle optimization manually (like ViewHolder pattern) |
| Flexibility | Very limited | Fully customizable layout, fonts, colors, and logic |
Here are the new components you must build manually:
| Component | Purpose |
|---|---|
| Custom model class (POJO) | Represents one row’s data (e.g., Student object). |
| Custom layout XML for list row | Defines how one row looks (e.g., image + 2 text lines). |
| Custom Adapter class (extends BaseAdapter or ArrayAdapter) | Binds model data → custom row layout views. |
| ViewHolder pattern | (Optional but important) For performance — reuses old views. |
| ListView in fragment/activity | Same as before — but you attach your custom adapter instead of built-in one. |
ArrayAdapter<T>→ easiest when your data is a list of objects. You can overridegetView()to control layout.BaseAdapter→ most flexible, used when you need full control or data isn’t a simple list.RecyclerView.Adapter→ modern, advanced replacement for ListView (we’ll come to it later).
ArrayAdapter<String> adapter = new ArrayAdapter<>(
this,
android.R.layout.simple_list_item_1,
namesArray
);
listView.setAdapter(adapter);✅ No extra layout, adapter, or model class.
- Create a model class:
class Student {
String name;
String roll;
int imageRes;
// constructor + getters
}- Create custom_row.xml:
<LinearLayout ...>
<ImageView android:id="@+id/img_profile" .../>
<TextView android:id="@+id/tv_name" .../>
<TextView android:id="@+id/tv_roll" .../>
</LinearLayout>- Create a CustomAdapter.java:
class CustomAdapter extends ArrayAdapter<Student> {
...
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// Inflate custom_row.xml
// Bind Student data to each View
}
}- Attach adapter:
listView.setAdapter(new CustomAdapter(this, studentList));Every row in a ListView is drawn using the adapter’s getView() method.
@Override
public View getView(int position, View convertView, ViewGroup parent)position→ index of current itemconvertView→ old (reusable) view; ifnull, you must inflate new oneparent→ the ListView itself
This mechanism prevents creating a new view for every scroll, improving speed.
✅ ViewHolder pattern stores references to views inside a row so they can be reused efficiently.
If ListView is inside a Fragment:
- Inflate layout in
onCreateView() - Setup adapter in
onViewCreated() - Use
getActivity()orrequireContext()for context - Clean up heavy references in
onDestroyView()
- A Passenger class → holds data for one passenger (name, phone, preference, etc.)
- An Application class → global holder for all passenger data (so all fragments can access it)
- A List Fragment (Fragment A) → shows a list of passengers with their name + preference icon
- A Details Fragment (Fragment B) → shows full info when one passenger is clicked
- A Custom Adapter → handles custom layout, binds
Passengerdata to views
AppData (Application)
↓
PassengerListFragment → CustomAdapter → custom_row.xml
↓ (on item click)
PassengerDetailFragment (shows info of selected passenger)
Because the ListView object needs to know which adapter will supply data.
You can’t just call setAdapter() without specifying an object — listView is an instance of a ListView class, and you call its method to set the adapter:
listView.setAdapter(adapter);Here:
listView= your specific ListView in the layout.setAdapter()= ListView’s method.adapter= your custom adapter instance.
If you call just setAdapter() alone, it doesn’t know which ListView to apply to — that’s why you always use listView.setAdapter(adapter). else implment to list fragment rather than just fragment
We’ll build 6 files:
Passenger.java– model classAppData.java– Application class (global data)fragment_passenger_list.xml– layout with ListViewrow_passenger.xml– layout for each row in listPassengerAdapter.java– custom adapter classPassengerListFragment.java– fragment with listPassengerDetailFragment.java– fragment with detailsMainActivity.java– hosts both fragments
public class Passenger {
private String name;
private String phone;
private String preference; // "Bus" or "Plane"
public Passenger(String name, String phone, String preference) {
this.name = name;
this.phone = phone;
this.preference = preference;
}
public String getName() { return name; }
public String getPhone() { return phone; }
public String getPreference() { return preference; }
}import android.app.Application;
import java.util.ArrayList;
public class AppData extends Application {
private static ArrayList<Passenger> passengers = new ArrayList<>();
@Override
public void onCreate() {
super.onCreate();
passengers.add(new Passenger("Alice", "1234567890", "Bus"));
passengers.add(new Passenger("Bob", "9876543210", "Plane"));
passengers.add(new Passenger("Charlie", "1122334455", "Bus"));
}
public static ArrayList<Passenger> getPassengers() {
return passengers;
}
}Remember to declare this Application class in your
AndroidManifest.xml<application android:name=".AppData" ... >
Custom layout for each row.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:padding="8dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tvName"
android:textSize="18sp"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="wrap_content"/>
<ImageView
android:id="@+id/imgPreference"
android:layout_width="40dp"
android:layout_height="40dp"
android:contentDescription="Preference icon" />
</LinearLayout>import android.content.Context;
import android.view.*;
import android.widget.*;
import java.util.List;
public class PassengerAdapter extends ArrayAdapter<Passenger> {
private LayoutInflater inflater;
public PassengerAdapter(Context context, List<Passenger> passengers) {
super(context, 0, passengers);
inflater = LayoutInflater.from(context);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// Reuse view if possible
if (convertView == null) {
convertView = inflater.inflate(R.layout.row_passenger, parent, false);
}
Passenger p = getItem(position);
TextView tvName = convertView.findViewById(R.id.tvName);
ImageView imgPref = convertView.findViewById(R.id.imgPreference);
tvName.setText(p.getName());
if (p.getPreference().equalsIgnoreCase("Bus")) {
imgPref.setImageResource(R.drawable.ic_bus);
} else {
imgPref.setImageResource(R.drawable.ic_plane);
}
return convertView;
}
}<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@+id/listPassengers"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>import android.os.Bundle;
import android.widget.ListView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.ListFragment;
import androidx.fragment.app.FragmentTransaction;
public class PassengerListFragment extends ListFragment {
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
PassengerAdapter adapter = new PassengerAdapter(requireContext(), AppData.getPassengers());
setListAdapter(adapter); // no need to find ListView manually
}
@Override
public void onListItemClick(@NonNull ListView l, @NonNull android.view.View v, int position, long id) {
super.onListItemClick(l, v, position, id);
Passenger selected = AppData.getPassengers().get(position);
Bundle bundle = new Bundle();
bundle.putString("name", selected.getName());
bundle.putString("phone", selected.getPhone());
bundle.putString("preference", selected.getPreference());
PassengerDetailFragment frag = new PassengerDetailFragment();
frag.setArguments(bundle);
FragmentTransaction ft = requireActivity().getSupportFragmentManager().beginTransaction();
ft.replace(R.id.container, frag);
ft.addToBackStack(null);
ft.commit();
}
}import android.os.Bundle;
import android.view.*;
import android.widget.*;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction;
public class PassengerListFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_passenger_list, container, false);
ListView listView = v.findViewById(R.id.listPassengers);
PassengerAdapter adapter = new PassengerAdapter(requireContext(), AppData.getPassengers());
listView.setAdapter(adapter);
listView.setOnItemClickListener((parent, view, position, id) -> {
Passenger selected = AppData.getPassengers().get(position);
Bundle bundle = new Bundle();
bundle.putString("name", selected.getName());
bundle.putString("phone", selected.getPhone());
bundle.putString("preference", selected.getPreference());
PassengerDetailFragment frag = new PassengerDetailFragment();
frag.setArguments(bundle);
FragmentTransaction ft = requireActivity().getSupportFragmentManager().beginTransaction();
ft.replace(R.id.container, frag);
ft.addToBackStack(null);
ft.commit();
});
return v;
}
}public class PassengerDetailFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Only inflate and return
return inflater.inflate(R.layout.fragment_passenger_detail, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
TextView tvDetails = view.findViewById(R.id.tvDetails);
Bundle args = getArguments();
if (args != null) {
String name = args.getString("name");
String phone = args.getString("phone");
String pref = args.getString("preference");
tvDetails.setText("Name: " + name + "\nPhone: " + phone + "\nPreference: " + pref);
}
}
}<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:padding="16dp"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tvDetails"
android:textSize="18sp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</FrameLayout>import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
getSupportFragmentManager()
.beginTransaction()
.add(R.id.container, new PassengerListFragment())
.commit();
}
}
}<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent" />- App starts →
MainActivityloadsPassengerListFragment. PassengerListFragment→ gets data fromAppData→ populates ListView usingPassengerAdapter.- User clicks on an item → passes passenger info via
Bundle→ opensPassengerDetailFragment. PassengerDetailFragment→ reads bundle → displays full info.