Bird
Raised Fist0
Fluttermobile~20 mins

ChangeNotifier and Consumer in Flutter - Mini App: Build & Ship

Choose your learning style10 modes available
Build: Counter with ChangeNotifier
A simple counter app that uses ChangeNotifier to manage state and Consumer to update the UI when the count changes.
Target UI
---------------------
| Counter with State |
---------------------
|                   |
|      Count: 0     |
|                   |
|   [Increment +]   |
|                   |
---------------------
Use ChangeNotifier to hold an integer count value.
Create a ChangeNotifierProvider to provide the counter to the widget tree.
Use Consumer widget to listen to changes and update the displayed count.
Add a button labeled 'Increment +' that increases the count by 1 when tapped.
Starter Code
Flutter
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (_) => Counter(),
      child: const MaterialApp(
        home: CounterScreen(),
      ),
    );
  }
}

class Counter extends ChangeNotifier {
  int _count = 0;

  int get count => _count;

  void increment() {
    // TODO: Increase _count by 1 and notify listeners
  }
}

class CounterScreen extends StatelessWidget {
  const CounterScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Counter with State')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text('Count:'),
            // TODO: Use Consumer to show current count
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                // TODO: Call increment on Counter
              },
              child: const Text('Increment +'),
            ),
          ],
        ),
      ),
    );
  }
}
Task 1
Task 2
Task 3
Solution
Flutter
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (_) => Counter(),
      child: const MaterialApp(
        home: CounterScreen(),
      ),
    );
  }
}

class Counter extends ChangeNotifier {
  int _count = 0;

  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();
  }
}

class CounterScreen extends StatelessWidget {
  const CounterScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Counter with State')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text('Count:'),
            Consumer<Counter>(
              builder: (context, counter, child) {
                return Text(
                  '${counter.count}',
                  style: const TextStyle(fontSize: 40, fontWeight: FontWeight.bold),
                );
              },
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                context.read<Counter>().increment();
              },
              child: const Text('Increment +'),
            ),
          ],
        ),
      ),
    );
  }
}

We created a Counter class that extends ChangeNotifier. It holds a private integer _count and a getter count. The increment() method increases _count by 1 and calls notifyListeners() to tell widgets to update.

In MyApp, we use ChangeNotifierProvider to provide the Counter instance to the widget tree.

In CounterScreen, we use a Consumer<Counter> widget to listen for changes and rebuild the Text widget showing the current count.

The button calls context.read<Counter>().increment() to update the count.

This setup keeps state management clean and UI reactive without manual setState calls.

Final Result
Completed Screen
---------------------
| Counter with State |
---------------------
|                   |
|      Count: 0     |
|                   |
|   [Increment +]   |
|                   |
---------------------
Tapping 'Increment +' increases the number shown after 'Count:' by 1.
The count updates immediately on screen without page reload.
Stretch Goal
Add a 'Reset' button that sets the count back to zero.
💡 Hint
Add a reset() method in Counter that sets _count to 0 and calls notifyListeners(). Add another ElevatedButton that calls context.read<Counter>().reset() on press.

Practice

(1/5)
1. What is the main role of ChangeNotifier in Flutter?
easy
A. It holds data and notifies listeners when data changes.
B. It builds the UI widgets directly.
C. It handles user input events.
D. It manages navigation between screens.

Solution

  1. Step 1: Understand ChangeNotifier's purpose

    ChangeNotifier is a class that holds data and can notify listeners when the data changes.
  2. Step 2: Differentiate from UI and navigation roles

    Building UI and navigation are handled by other Flutter components, not ChangeNotifier.
  3. Final Answer:

    It holds data and notifies listeners when data changes. -> Option A
  4. Quick Check:

    ChangeNotifier = Data holder and notifier [OK]
Hint: ChangeNotifier manages data and alerts listeners on change [OK]
Common Mistakes:
  • Thinking ChangeNotifier builds UI
  • Confusing ChangeNotifier with navigation manager
  • Assuming it handles user input directly
2. Which of the following is the correct way to use Consumer widget to listen to a ChangeNotifier called Counter?
easy
A. Consumer<Counter>(builder: (context, counter) => Text('${counter.value}'))
B. Consumer<Counter>(builder: (context, counter, child) => Text('${counter.value}'))
C. Consumer<Counter>(builder: (context, child, counter) => Text('${counter.value}'))
D. Consumer(builder: (context, Counter, child) => Text('${Counter.value}'))

Solution

  1. Step 1: Check Consumer generic and builder parameters

    Consumer takes a generic type for the notifier and a builder with parameters (context, notifier, child).
  2. Step 2: Match parameter order and usage

    Consumer<Counter>(builder: (context, counter, child) => Text('${counter.value}')) correctly uses Consumer<Counter> and builder with (context, counter, child).
  3. Final Answer:

    Consumer<Counter>(builder: (context, counter, child) => Text('${counter.value}')) -> Option B
  4. Quick Check:

    Consumer syntax = Consumer<Counter>(builder: (context, counter, child) => Text('${counter.value}')) [OK]
Hint: Consumer builder params: (context, notifier, child) order matters [OK]
Common Mistakes:
  • Swapping builder parameters order
  • Omitting generic type in Consumer
  • Using wrong parameter names or count
3. Given this code snippet:
class Counter extends ChangeNotifier {
  int value = 0;
  void increment() {
    value++;
    notifyListeners();
  }
}

Consumer<Counter>(
  builder: (context, counter, child) => Text('Count: ${counter.value}')
)
What will the UI show after calling increment() once?
medium
A. Count: 0
B. Count: increment
C. Count: null
D. Count: 1

Solution

  1. Step 1: Understand initial value and increment

    Counter starts at 0, increment() adds 1 and calls notifyListeners().
  2. Step 2: Consumer rebuilds with updated value

    Consumer listens and rebuilds Text with updated counter.value = 1.
  3. Final Answer:

    Count: 1 -> Option D
  4. Quick Check:

    increment() updates value to 1 [OK]
Hint: notifyListeners triggers UI update with new value [OK]
Common Mistakes:
  • Forgetting notifyListeners call
  • Assuming value stays 0 after increment
  • Confusing value with method name
4. What is wrong with this code snippet?
class Counter extends ChangeNotifier {
  int value = 0;
  void increment() {
    value++;
  }
}

Consumer<Counter>(
  builder: (context, counter, child) => Text('Count: ${counter.value}')
)
Why does the UI not update when increment() is called?
medium
A. notifyListeners() is missing after value changes.
B. Consumer is not wrapped in a Provider.
C. The value variable should be final.
D. increment() should return the new value.

Solution

  1. Step 1: Check increment method implementation

    increment() changes value but does not call notifyListeners().
  2. Step 2: Understand why UI doesn't update

    Without notifyListeners(), Consumer does not know to rebuild UI.
  3. Final Answer:

    notifyListeners() is missing after value changes. -> Option A
  4. Quick Check:

    Missing notifyListeners() = no UI update [OK]
Hint: Always call notifyListeners() after changing data [OK]
Common Mistakes:
  • Forgetting notifyListeners() call
  • Assuming Consumer updates automatically without notification
  • Thinking value must be final
5. You want to optimize a Flutter app so only a small part of the UI rebuilds when data changes. Which approach using ChangeNotifier and Consumer is best?
hard
A. Call notifyListeners() multiple times for each widget separately.
B. Wrap the entire app with a single Consumer to listen to all changes.
C. Wrap only the widget that shows changing data with Consumer, not the whole screen.
D. Use ChangeNotifier without Consumer and rebuild UI manually.

Solution

  1. Step 1: Understand Consumer rebuild scope

    Consumer rebuilds only the widgets inside its builder when notified.
  2. Step 2: Optimize by limiting Consumer scope

    Wrapping only the small widget that needs updating avoids unnecessary rebuilds.
  3. Step 3: Evaluate other options

    Wrapping entire app causes large rebuilds; multiple notifyListeners() calls are inefficient; manual rebuild is error-prone.
  4. Final Answer:

    Wrap only the widget that shows changing data with Consumer, not the whole screen. -> Option C
  5. Quick Check:

    Small Consumer scope = better performance [OK]
Hint: Wrap only changing widgets with Consumer for best performance [OK]
Common Mistakes:
  • Wrapping entire app causing slow UI
  • Calling notifyListeners() excessively
  • Avoiding Consumer and rebuilding manually
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard