Mobile Application DevelopmentUnit 523 min read
Lists, Adapters, Fragments: Dynamic UI & Data Management in Android
Unit 5 of Mobile Application Development covers how to create dynamic user interfaces using Lists, Adapters, and Fragments in Android, including their lifecycle, data binding, and performance optimization techniques.
TAKEAWAYS:
- Lists and Adapters enable efficient display of large datasets in Android with minimal memory usage.
- Fragments allow modular UI design, enabling apps to run on different screen sizes and orientations.
- The Adapter pattern decouples data sources from UI components, improving code reusability.
- Recyclerview is preferred over ListView for better performance and customization in modern Android apps.
- Understanding Fragment lifecycle and communication between fragments is crucial for complex apps.
- Mastering ViewHolder pattern in Adapters optimizes scrolling performance in lists.
Lists in Android: The Foundation of Dynamic UIs
What is a List in Android?
A List in Android is a UI component that displays a vertically scrollable collection of items. The two primary list-based widgets are:
- ListView: Older, simpler widget for displaying lists.
- RecyclerView: Modern, flexible, and high-performance widget for displaying large datasets efficiently.
Key Differences: ListView vs. RecyclerView
| Feature | ListView | RecyclerView |
|---|---|---|
| Performance | Slower for large datasets | Optimized for large datasets |
| Flexibility | Limited layout options | Supports complex layouts (Grid, StaggeredGrid) |
| Memory Usage | Higher (views are created/destroyed frequently) | Lower (recycles views) |
| Use Case | Small, static lists | Large, dynamic, or custom lists |
| Adapter | ArrayAdapter, BaseAdapter | RecyclerView.Adapter |
Why Use RecyclerView?
RecyclerView is the preferred choice for modern Android apps because:
- View Recycling: Reuses views instead of creating new ones, reducing memory overhead.
- Layout Flexibility: Supports multiple layouts (Linear, Grid, StaggeredGrid).
- Better Performance: Uses diffing algorithms to minimize updates when data changes.
- Customization: Supports item animations, nested scrolling, and more.
How RecyclerView Works
- Adapter: Binds data to views.
- LayoutManager: Determines the layout (e.g., LinearLayoutManager for a vertical list).
- ViewHolder: Caches views to improve scrolling performance.
Worked Example: RecyclerView in Action
Scenario: Display a list of Daraz product orders in a user's history. The list must be scrollable, and each item should show the product image, name, price, and order status.
Step-by-Step Implementation
Define the Data Model:
public class Order { private String productName; private String productImageUrl; private double price; private String status; // Constructor, getters, setters }Create the Adapter: The
OrderAdapterextendsRecyclerView.Adapter<OrderAdapter.OrderViewHolder>.public class OrderAdapter extends RecyclerView.Adapter<OrderAdapter.OrderViewHolder> { private List<Order> orders; public OrderAdapter(List<Order> orders) { this.orders = orders; } @Override public OrderViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_order, parent, false); return new OrderViewHolder(view); } @Override public void onBindViewHolder(OrderViewHolder holder, int position) { Order order = orders.get(position); holder.bind(order); } @Override public int getItemCount() { return orders.size(); } class OrderViewHolder extends RecyclerView.ViewHolder { private ImageView productImage; private TextView productName, price, status; public OrderViewHolder(View itemView) { super(itemView); productImage = itemView.findViewById(R.id.product_image); productName = itemView.findViewById(R.id.product_name); price = itemView.findViewById(R.id.price); status = itemView.findViewById(R.id.status); } public void bind(Order order) { // Load image using Glide or Picasso Glide.with(itemView.getContext()) .load(order.getProductImageUrl()) .into(productImage); productName.setText(order.getProductName()); price.setText("Rs. " + order.getPrice()); status.setText(order.getStatus()); } } }Set Up RecyclerView in Activity/Fragment:
RecyclerView recyclerView = findViewById(R.id.recycler_view); recyclerView.setLayoutManager(new LinearLayoutManager(this)); OrderAdapter adapter = new OrderAdapter(ordersList); recyclerView.setAdapter(adapter);State After Each Step:
- Initial State: Empty RecyclerView with no data.
- After Binding Data: RecyclerView populates with
Orderitems. - After Scrolling: Views are recycled (e.g., a previously scrolled item's view is reused for a new item).
Caption: Lifecycle of RecyclerView during scrolling.
Adapters: The Bridge Between Data and UI
What is an Adapter?
An Adapter in Android acts as a mediator between a data source (e.g., List<Order>) and a UI component (e.g., RecyclerView). It provides the following key functionalities:
- Maps data to views.
- Handles view recycling (for RecyclerView).
- Updates the UI when data changes.
Types of Adapters
| Adapter Type | Use Case | Example |
|---|---|---|
| ArrayAdapter | Simple lists with basic data types | ListView with Strings |
| BaseAdapter | Custom lists with complex data | ListView with custom objects |
| RecyclerView.Adapter | Modern lists with performance needs | RecyclerView with large datasets |
| CursorAdapter | Lists backed by a database cursor | SQLite queries in ListView |
Worked Example: Adapter in a Bank Loan App
Scenario: Display a list of Nepal Bank Limited's loan schemes with interest rates, tenure, and eligibility.
Step-by-Step Trace
Data Source: A
List<LoanScheme>fetched from an API or database.public class LoanScheme { private String schemeName; private double interestRate; private int tenureMonths; private String eligibility; // Constructor, getters, setters }Adapter Implementation:
public class LoanSchemeAdapter extends RecyclerView.Adapter<LoanSchemeAdapter.LoanViewHolder> { private List<LoanScheme> loanSchemes; public LoanSchemeAdapter(List<LoanScheme> loanSchemes) { this.loanSchemes = loanSchemes; } @Override public LoanViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_loan_scheme, parent, false); return new LoanViewHolder(view); } @Override public void onBindViewHolder(LoanViewHolder holder, int position) { LoanScheme scheme = loanSchemes.get(position); holder.bind(scheme); } @Override public int getItemCount() { return loanSchemes.size(); } class LoanViewHolder extends RecyclerView.ViewHolder { private TextView schemeName, interestRate, tenure, eligibility; public LoanViewHolder(View itemView) { super(itemView); schemeName = itemView.findViewById(R.id.scheme_name); interestRate = itemView.findViewById(R.id.interest_rate); tenure = itemView.findViewById(R.id.tenure); eligibility = itemView.findViewById(R.id.eligibility); } public void bind(LoanScheme scheme) { schemeName.setText(scheme.getSchemeName()); interestRate.setText(scheme.getInterestRate() + "% p.a."); tenure.setText(scheme.getTenureMonths() + " months"); eligibility.setText(scheme.getEligibility()); } } }State After Data Binding:
- Before Binding:
RecyclerView: Empty Data: [LoanScheme1, LoanScheme2, LoanScheme3] - After Binding:
RecyclerView: ---------------------------- | Scheme1 | 8.5% | 60 months | Salaried only | ---------------------------- | Scheme2 | 9.2% | 36 months | Business only | ---------------------------- | Scheme3 | 7.8% | 120 months| General public | ----------------------------
- Before Binding:
Caption: Data flow from LoanScheme to RecyclerView.
Fragments: Modular UI Components
What is a Fragment?
A Fragment is a modular section of an activity that has its own lifecycle, input event handlers, and UI. Fragments are useful for:
- Creating responsive layouts (e.g., tablets vs. phones).
- Reusing UI components across activities.
- Managing complex UIs with multiple sections.
Fragment Lifecycle
Fragments have their own lifecycle, which is tied to the activity's lifecycle but can also be independent in some cases.
stateDiagram-v2
[*] --> Created
Created --> Started
Started --> Resumed
Resumed --> Paused
Paused --> Stopped
Stopped --> Destroyed
Destroyed --> [*]
Resumed --> Paused: Activity loses focus
Paused --> Stopped: Activity stopped
Stopped --> Destroyed: Fragment removedWorked Example: Fragments in a Pathao Rider App
Scenario: A Pathao rider app has two main sections:
- Current Ride: Shows real-time ride details (customer location, destination, earnings).
- Ride History: Displays past rides with earnings summary.
Implementation Steps
Define Fragments:
CurrentRideFragment: Shows live ride details.RideHistoryFragment: Shows past rides.
Fragment Layouts:
fragment_current_ride.xml:<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/customer_name" /> <TextView android:id="@+id/destination" /> <TextView android:id="@+id/earnings" /> <Button android:id="@+id/end_ride" /> </LinearLayout>fragment_ride_history.xml:<RecyclerView android:id="@+id/ride_history_recycler" android:layout_width="match_parent" android:layout_height="match_parent" />
Fragment Classes:
public class CurrentRideFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_current_ride, container, false); // Initialize UI and set data return view; } } public class RideHistoryFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_ride_history, container, false); RecyclerView recyclerView = view.findViewById(R.id.ride_history_recycler); recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); recyclerView.setAdapter(new RideHistoryAdapter(getRideHistory())); return view; } private List<Ride> getRideHistory() { // Fetch ride history from database or API return rideHistoryList; } }Activity with Fragments:
public class RiderActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_rider); // Replace fragment container with CurrentRideFragment getSupportFragmentManager().beginTransaction() .replace(R.id.fragment_container, new CurrentRideFragment()) .commit(); } public void switchToHistory() { getSupportFragmentManager().beginTransaction() .replace(R.id.fragment_container, new RideHistoryFragment()) .addToBackStack(null) .commit(); } }State After Fragment Transaction:
- Initial State:
Activity: RiderActivity Fragment Container: Empty - After Adding CurrentRideFragment:
Activity: RiderActivity Fragment Container: CurrentRideFragment (showing live ride details) - After Switching to RideHistoryFragment:
Activity: RiderActivity Fragment Container: RideHistoryFragment (showing RecyclerView of past rides)
- Initial State:
Caption: Fragment transaction flow in Pathao Rider App.
In the Real World
eSewa App:
- Idea Used: RecyclerView with Adapters
- How: eSewa displays a list of recent transactions in the user's wallet. The list is implemented using
RecyclerViewwith a customAdapterto bind each transaction (amount, date, type) to a row in the list. TheViewHolderpattern ensures smooth scrolling even with hundreds of transactions.
Khalti Merchant Dashboard:
- Idea Used: Fragments for Modular UI
- How: Khalti's merchant dashboard uses fragments to separate different sections (e.g., "Today's Sales," "Pending Payments," "Analytics"). This allows merchants to switch between views without reloading the entire screen, improving performance and user experience.
Daraz Mobile App:
- Idea Used: Nested RecyclerView and Adapters
- How: Daraz uses
RecyclerViewto display product categories, search results, and user order history. For example, the "My Orders" section uses aRecyclerViewwith a customAdapterto show each order's details (product image, name, price, status). NestedRecyclerViewis used in category pages to show subcategories and products hierarchically.
Ncell Recharge App:
- Idea Used: ListView/RecyclerView with Filters
- How: The recharge app displays a list of available plans (prepaid/postpaid, data, voice). Users can filter plans by type (e.g., "Data Plans Only"). The app uses
RecyclerViewwith a filterableAdapterto dynamically update the list based on user selections.
Combining Lists, Adapters, and Fragments: A Complete Example
Scenario: Build a Nepal Stock Exchange (NEPSE) App that shows:
- A list of top-performing stocks (using
RecyclerView). - A fragment for stock details when a user clicks a stock.
Step-by-Step Implementation
Data Model:
public class Stock { private String symbol; private String companyName; private double price; private double change; private String time; // Constructor, getters, setters }StockAdapter:
public class StockAdapter extends RecyclerView.Adapter<StockAdapter.StockViewHolder> { private List<Stock> stocks; public StockAdapter(List<Stock> stocks) { this.stocks = stocks; } @Override public StockViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_stock, parent, false); return new StockViewHolder(view); } @Override public void onBindViewHolder(StockViewHolder holder, int position) { Stock stock = stocks.get(position); holder.bind(stock); } @Override public int getItemCount() { return stocks.size(); } class StockViewHolder extends RecyclerView.ViewHolder { private TextView symbol, companyName, price, change, time; public StockViewHolder(View itemView) { super(itemView); symbol = itemView.findViewById(R.id.symbol); companyName = itemView.findViewById(R.id.company_name); price = itemView.findViewById(R.id.price); change = itemView.findViewById(R.id.change); time = itemView.findViewById(R.id.time); itemView.setOnClickListener(v -> { // Navigate to StockDetailFragment ((MainActivity) itemView.getContext()).showStockDetail(stocks.get(getAdapterPosition())); }); } public void bind(Stock stock) { symbol.setText(stock.getSymbol()); companyName.setText(stock.getCompanyName()); price.setText("Rs. " + stock.getPrice()); change.setText(stock.getChange() >= 0 ? "+" + stock.getChange() : String.valueOf(stock.getChange())); time.setText(stock.getTime()); } } }StockDetailFragment:
public class StockDetailFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_stock_detail, container, false); TextView detailText = view.findViewById(R.id.stock_detail); // Get data passed from activity Stock stock = (Stock) getArguments().getSerializable("stock"); detailText.setText("Symbol: " + stock.getSymbol() + "\n" + "Company: " + stock.getCompanyName() + "\n" + "Price: Rs. " + stock.getPrice() + "\n" + "Change: " + stock.getChange() + "\n" + "Time: " + stock.getTime()); return view; } }MainActivity:
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); RecyclerView recyclerView = findViewById(R.id.stock_recycler); recyclerView.setLayoutManager(new LinearLayoutManager(this)); recyclerView.setAdapter(new StockAdapter(getStocks())); } private List<Stock> getStocks() { // Fetch from API or database return stocksList; } public void showStockDetail(Stock stock) { StockDetailFragment fragment = new StockDetailFragment(); Bundle args = new Bundle(); args.putSerializable("stock", stock); fragment.setArguments(args); getSupportFragmentManager().beginTransaction() .replace(R.id.fragment_container, fragment) .addToBackStack(null) .commit(); } }State After User Interaction:
- Initial State:
Activity: MainActivity Fragment Container: Empty RecyclerView: Shows list of stocks - After Clicking a Stock:
Activity: MainActivity Fragment Container: StockDetailFragment (showing details of selected stock) RecyclerView: Still visible in background (if layout allows)
- Initial State:
Caption: Flow from stock list click to detail fragment.
Common Pitfalls and Best Practices
Pitfalls to Avoid
Ignoring ViewHolder in RecyclerView:
- Problem: Not using
ViewHolderleads to performance issues during scrolling. - Fix: Always override
onCreateViewHolderandonBindViewHolder.
- Problem: Not using
Not Handling Configuration Changes:
- Problem: Fragments/Adapters lose state during screen rotations.
- Fix: Use
savedInstanceStateorViewModelto retain data.
Memory Leaks in Adapters:
- Problem: Holding references to context or activities in adapters.
- Fix: Use
WeakReferenceor passContextcarefully.
Overusing Fragments:
- Problem: Creating too many fragments can lead to complex navigation.
- Fix: Use fragments for distinct UI sections, not for minor components.
Best Practices
Use DiffUtil for RecyclerView Updates:
- Reduces unnecessary animations and improves performance when updating lists.
public class StockDiffCallback extends DiffUtil.ItemCallback<Stock> { @Override public boolean areItemsTheSame(Stock oldItem, Stock newItem) { return oldItem.getSymbol().equals(newItem.getSymbol()); } @Override public boolean areContentsTheSame(Stock oldItem, Stock newItem) { return oldItem.equals(newItem); } }Lazy-Load Fragments:
- Load fragments only when needed to improve startup performance.
if (savedInstanceState == null) { getSupportFragmentManager().beginTransaction() .add(R.id.fragment_container, new RideHistoryFragment()) .commit(); }Optimize Adapter Performance:
- Use
notifyItemInserted,notifyItemRemoved, etc., instead ofnotifyDataSetChanged. - Avoid heavy operations in
onBindViewHolder.
- Use
Test on Different Screen Sizes:
- Fragments and lists should adapt to tablets, phablets, and phones.
Exam Tip
What to Expect in the Exam
Theoretical Questions:
- Define Adapter pattern in Android and explain its role.
- Compare
ListViewandRecyclerViewwith examples. - Explain the lifecycle of a Fragment and how it differs from an Activity.
- Describe the ViewHolder pattern and its benefits.
Practical Questions:
- Code Implementation: Write an
Adapterfor a custom list (e.g., a list of movies with posters). - Debugging: Given a broken
RecyclerView, identify and fix issues (e.g., missingViewHolder, incorrectLayoutManager). - Fragment Communication: Explain how to pass data between fragments (e.g., using interfaces or
ViewModel). - Performance Optimization: Suggest improvements for a slow-scrolling
RecyclerView.
- Code Implementation: Write an
Scenario-Based Questions:
- Design a UI for a Khalti transaction history using
RecyclerViewandAdapter. - Explain how you would implement a two-pane layout (e.g., list on left, details on right) using fragments.
- Describe how to handle orientation changes in an app using fragments.
- Design a UI for a Khalti transaction history using
Key Formulas and Concepts to Remember
| Concept | Formula/Key Point |
|---|---|
| RecyclerView Performance | ViewHolder recycling reduces O(n) view creation to O(1) per scroll. |
| Fragment Lifecycle | onCreate → onCreateView → onActivityCreated → onStart → onResume. |
| Adapter Efficiency | Use DiffUtil to minimize UI updates: areItemsTheSame() vs. areContentsTheSame(). |
| ListView vs. RecyclerView | RecyclerView supports LayoutManager for grids/staggered layouts. |
Common Exam Mistakes to Avoid
- Forgetting to call
notifyDataSetChanged()after updating the adapter's data. - Not handling
nullchecks inonBindViewHolder. - Using
ArrayAdapterfor complex data models (always prefer customAdapter). - Ignoring the
savedInstanceStatebundle in fragments. - Not setting a
LayoutManagerforRecyclerView.
Final Checklist Before the Exam
- Can you explain the Adapter pattern and its role in Android?
- How do you optimize RecyclerView for large datasets?
- What are the key lifecycle methods of a Fragment?
- How would you pass data between fragments?
- What is the difference between
ListViewandRecyclerView? - Can you write a custom Adapter from scratch?
- How do you handle configuration changes in fragments?
- What is the ViewHolder pattern, and why is it important?
Based on the TU BITM syllabus for Mobile Application Development (IT272), unit 5.
Discussion
Loading…