IT272 Mobile Application Development

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:

  1. View Recycling: Reuses views instead of creating new ones, reducing memory overhead.
  2. Layout Flexibility: Supports multiple layouts (Linear, Grid, StaggeredGrid).
  3. Better Performance: Uses diffing algorithms to minimize updates when data changes.
  4. Customization: Supports item animations, nested scrolling, and more.

How RecyclerView Works

  1. Adapter: Binds data to views.
  2. LayoutManager: Determines the layout (e.g., LinearLayoutManager for a vertical list).
  3. ViewHolder: Caches views to improve scrolling performance.
Item 10Item 21Item 32Item 43Item 54Item 65
RecyclerView view recycling: Only visible items (highlighted) are instantiated; others are reused.

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

  1. Define the Data Model:

    public class Order {
        private String productName;
        private String productImageUrl;
        private double price;
        private String status;
    
        // Constructor, getters, setters
    }
    
  2. Create the Adapter: The OrderAdapter extends RecyclerView.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());
            }
        }
    }
    
  3. 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);
    
  4. State After Each Step:

    • Initial State: Empty RecyclerView with no data.
    • After Binding Data: RecyclerView populates with Order items.
    • 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.

0Loan11—2Loan23Loan3
Adapter’s internal data structure (simplified hash map) mapping loan IDs to loan objects.

Step-by-Step Trace

  1. 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
    }
    
  2. 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());
            }
        }
    }
    
  3. 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 |
      ----------------------------
      

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 removed

Worked Example: Fragments in a Pathao Rider App

Scenario: A Pathao rider app has two main sections:

  1. Current Ride: Shows real-time ride details (customer location, destination, earnings).
  2. Ride History: Displays past rides with earnings summary.

Implementation Steps

  1. Define Fragments:

    • CurrentRideFragment: Shows live ride details.
    • RideHistoryFragment: Shows past rides.
  2. 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" />
      
  3. 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;
        }
    }
    
  4. 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();
        }
    }
    
  5. 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)
      

Caption: Fragment transaction flow in Pathao Rider App.


In the Real World

  1. 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 RecyclerView with a custom Adapter to bind each transaction (amount, date, type) to a row in the list. The ViewHolder pattern ensures smooth scrolling even with hundreds of transactions.
  2. 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.
  3. Daraz Mobile App:

    • Idea Used: Nested RecyclerView and Adapters
    • How: Daraz uses RecyclerView to display product categories, search results, and user order history. For example, the "My Orders" section uses a RecyclerView with a custom Adapter to show each order's details (product image, name, price, status). Nested RecyclerView is used in category pages to show subcategories and products hierarchically.
  4. 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 RecyclerView with a filterable Adapter to 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:

  1. A list of top-performing stocks (using RecyclerView).
  2. A fragment for stock details when a user clicks a stock.

Step-by-Step Implementation

  1. Data Model:

    public class Stock {
        private String symbol;
        private String companyName;
        private double price;
        private double change;
        private String time;
    
        // Constructor, getters, setters
    }
    
  2. 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());
            }
        }
    }
    
  3. 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;
        }
    }
    
  4. 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();
        }
    }
    
  5. 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)
      

Caption: Flow from stock list click to detail fragment.


Common Pitfalls and Best Practices

Pitfalls to Avoid

  1. Ignoring ViewHolder in RecyclerView:

    • Problem: Not using ViewHolder leads to performance issues during scrolling.
    • Fix: Always override onCreateViewHolder and onBindViewHolder.
  2. Not Handling Configuration Changes:

    • Problem: Fragments/Adapters lose state during screen rotations.
    • Fix: Use savedInstanceState or ViewModel to retain data.
  3. Memory Leaks in Adapters:

    • Problem: Holding references to context or activities in adapters.
    • Fix: Use WeakReference or pass Context carefully.
  4. 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

  1. 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);
        }
    }
    
  2. 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();
    }
    
  3. Optimize Adapter Performance:

    • Use notifyItemInserted, notifyItemRemoved, etc., instead of notifyDataSetChanged.
    • Avoid heavy operations in onBindViewHolder.
  4. Test on Different Screen Sizes:

    • Fragments and lists should adapt to tablets, phablets, and phones.

Exam Tip

What to Expect in the Exam

  1. Theoretical Questions:

    • Define Adapter pattern in Android and explain its role.
    • Compare ListView and RecyclerView with examples.
    • Explain the lifecycle of a Fragment and how it differs from an Activity.
    • Describe the ViewHolder pattern and its benefits.
  2. Practical Questions:

    • Code Implementation: Write an Adapter for a custom list (e.g., a list of movies with posters).
    • Debugging: Given a broken RecyclerView, identify and fix issues (e.g., missing ViewHolder, incorrect LayoutManager).
    • Fragment Communication: Explain how to pass data between fragments (e.g., using interfaces or ViewModel).
    • Performance Optimization: Suggest improvements for a slow-scrolling RecyclerView.
  3. Scenario-Based Questions:

    • Design a UI for a Khalti transaction history using RecyclerView and Adapter.
    • 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.

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 null checks in onBindViewHolder.
  • Using ArrayAdapter for complex data models (always prefer custom Adapter).
  • Ignoring the savedInstanceState bundle in fragments.
  • Not setting a LayoutManager for RecyclerView.

Final Checklist Before the Exam

  1. Can you explain the Adapter pattern and its role in Android?
  2. How do you optimize RecyclerView for large datasets?
  3. What are the key lifecycle methods of a Fragment?
  4. How would you pass data between fragments?
  5. What is the difference between ListView and RecyclerView?
  6. Can you write a custom Adapter from scratch?
  7. How do you handle configuration changes in fragments?
  8. 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…