Bird
Raised Fist0
Fluttermobile~20 mins

MVVM pattern in Flutter - Mini App: Build & Ship

Choose your learning style10 modes available
Build: Counter MVVM Screen
A simple counter app using MVVM pattern in Flutter. The screen shows a number and two buttons to increase or decrease the count. The ViewModel manages the count state and notifies the UI to update.
Target UI
---------------------
|   Counter MVVM    |
|-------------------|
|                   |
|       0           |
|                   |
|  [-]       [+]    |
---------------------
Use MVVM pattern: separate View, ViewModel, and Model logic
ViewModel holds the counter value and exposes increment and decrement methods
View listens to ViewModel changes and updates UI accordingly
Two buttons: '+' to increment, '-' to decrement the counter
Display the current count in the center
Use ChangeNotifier and ChangeNotifierProvider from provider package for state management
Starter Code
Flutter
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

// TODO: Create CounterViewModel class here

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

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

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

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

  @override
  Widget build(BuildContext context) {
    // TODO: Build UI that listens to CounterViewModel
    return Scaffold(
      appBar: AppBar(title: const Text('Counter MVVM')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            // TODO: Display counter value
            // TODO: Add buttons to increment and decrement
          ],
        ),
      ),
    );
  }
}
Task 1
Task 2
Task 3
Task 4
Task 5
Solution
Flutter
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

class CounterViewModel extends ChangeNotifier {
  int _count = 0;

  int get count => _count;

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

  void decrement() {
    _count--;
    notifyListeners();
  }
}

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

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

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

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Counter MVVM')),
      body: Center(
        child: Consumer<CounterViewModel>(
          builder: (context, viewModel, child) {
            return Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text(
                  '${viewModel.count}',
                  style: const TextStyle(fontSize: 48),
                ),
                const SizedBox(height: 20),
                Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    ElevatedButton(
                      onPressed: viewModel.decrement,
                      child: const Text('-'),
                    ),
                    const SizedBox(width: 20),
                    ElevatedButton(
                      onPressed: viewModel.increment,
                      child: const Text('+'),
                    ),
                  ],
                ),
              ],
            );
          },
        ),
      ),
    );
  }
}

This app uses the MVVM pattern by separating the UI (View) from the business logic (ViewModel). The CounterViewModel class holds the counter state and methods to change it. It extends ChangeNotifier so it can notify the UI when the data changes.

The MyApp widget provides the ViewModel to the widget tree using ChangeNotifierProvider from the provider package.

The CounterScreen widget listens to the ViewModel using Consumer. It rebuilds the UI when the count changes, showing the current count and two buttons to increment or decrement the value.

This clear separation makes the app easier to maintain and test.

Final Result
Completed Screen
---------------------
|   Counter MVVM    |
|-------------------|
|                   |
|       0           |
|                   |
|  [-]       [+]    |
---------------------
Tapping '+' increases the number displayed by 1
Tapping '-' decreases the number displayed by 1
The number updates immediately on screen
Stretch Goal
Add a reset button that sets the counter back to zero
💡 Hint
Add a reset() method in CounterViewModel that sets _count to 0 and calls notifyListeners(). Add a new button in the UI that calls reset() when pressed.

Practice

(1/5)
1. What is the main role of the ViewModel in the MVVM pattern in Flutter?
easy
A. To handle user input events directly from the UI
B. To hold data and business logic, notifying the View of changes
C. To display the user interface elements on the screen
D. To manage navigation between different screens

Solution

  1. Step 1: Understand MVVM roles

    The ViewModel acts as a middleman holding data and logic separate from UI.
  2. Step 2: Identify ViewModel responsibility

    It notifies the View when data changes so UI updates automatically.
  3. Final Answer:

    To hold data and business logic, notifying the View of changes -> Option B
  4. Quick Check:

    ViewModel = Data + Logic holder [OK]
Hint: ViewModel holds data and logic, not UI display [OK]
Common Mistakes:
  • Confusing ViewModel with View
  • Thinking ViewModel handles UI rendering
  • Assuming ViewModel manages navigation
2. Which of the following is the correct way to notify the View about data changes in a ViewModel using Flutter's ChangeNotifier?
easy
A. call notifyListeners() inside the ViewModel after data update
B. call setState() inside the ViewModel after data update
C. call build() method directly from the ViewModel
D. call dispose() method after data update

Solution

  1. Step 1: Recall ChangeNotifier usage

    ChangeNotifier requires calling notifyListeners() to update listeners.
  2. Step 2: Identify correct notification method

    notifyListeners() triggers UI rebuilds in listening Views.
  3. Final Answer:

    call notifyListeners() inside the ViewModel after data update -> Option A
  4. Quick Check:

    notifyListeners() updates View [OK]
Hint: Use notifyListeners() in ViewModel to update UI [OK]
Common Mistakes:
  • Using setState() inside ViewModel (wrong place)
  • Trying to call build() manually
  • Calling dispose() instead of notifyListeners()
3. Given this ViewModel code snippet in Flutter:
class CounterViewModel extends ChangeNotifier {
  int _count = 0;
  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();
  }
}
What will be the value of count after calling increment() twice starting from 0?
medium
A. 0
B. 1
C. 2
D. Error: notifyListeners() not called

Solution

  1. Step 1: Analyze initial value and increment

    _count starts at 0, increment() adds 1 each call.
  2. Step 2: Calculate after two increments

    After two calls, _count = 0 + 1 + 1 = 2.
  3. Final Answer:

    2 -> Option C
  4. Quick Check:

    Two increments = count 2 [OK]
Hint: Each increment adds 1; two calls add 2 total [OK]
Common Mistakes:
  • Forgetting notifyListeners() updates UI but doesn't change count
  • Assuming count resets after notifyListeners()
  • Confusing initial value with updated value
4. Identify the error in this ViewModel code snippet:
class UserViewModel extends ChangeNotifier {
  String _name = '';
  String get name => _name;

  void updateName(String newName) {
    _name = newName;
  }
}
Why won't the UI update when updateName is called?
medium
A. notifyListeners() is missing after updating _name
B. Getter method name is incorrect
C. ChangeNotifier should not be extended
D. updateName should be async

Solution

  1. Step 1: Check data update method

    updateName changes _name but does not notify listeners.
  2. Step 2: Understand UI update trigger

    Without notifyListeners(), UI won't rebuild on data change.
  3. Final Answer:

    notifyListeners() is missing after updating _name -> Option A
  4. Quick Check:

    Missing notifyListeners() blocks UI update [OK]
Hint: Always call notifyListeners() after data changes [OK]
Common Mistakes:
  • Forgetting notifyListeners() after state change
  • Thinking getter name causes UI update
  • Assuming async needed without async code
5. You want to build a Flutter app using MVVM where the View updates automatically when the ViewModel's list of items changes. Which approach correctly implements this behavior?
hard
A. Store the list in View and pass it to ViewModel without notifications
B. Directly modify the list in the View and call setState() in ViewModel
C. Use a StatefulWidget for ViewModel and call build() manually after list changes
D. Use a ChangeNotifier in ViewModel, call notifyListeners() after modifying the list, and use a Consumer widget in View

Solution

  1. Step 1: Understand MVVM data flow

    ViewModel holds data and notifies View on changes using ChangeNotifier.
  2. Step 2: Identify correct Flutter pattern

    Calling notifyListeners() after list changes triggers Consumer widget rebuild in View.
  3. Step 3: Eliminate incorrect options

    setState() belongs in View, not ViewModel; manual build() calls are incorrect; storing list only in View breaks MVVM.
  4. Final Answer:

    Use a ChangeNotifier in ViewModel, call notifyListeners() after modifying the list, and use a Consumer widget in View -> Option D
  5. Quick Check:

    ChangeNotifier + notifyListeners() + Consumer = MVVM update [OK]
Hint: Use ChangeNotifier + notifyListeners() + Consumer for auto UI update [OK]
Common Mistakes:
  • Calling setState() in ViewModel instead of View
  • Modifying data only in View without notifications
  • Manually calling build() method
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