🔔 ChangeNotifier — The Smart Middleman for Your App's Data
🧒 Who Tells the UI to Update? (The Radio Station Analogy!)
Right Now, Your Model Fetches Data — But Nobody Knows About It!
Your ArticleModel can successfully fetch Wikipedia articles. But there's a problem:
the UI has no idea when the data arrives! The Model is like a radio station
broadcasting, but your UI has no radio to listen with.
Enter ChangeNotifier — it's like a radio transmitter that shouts: "Hey everyone! I have new data! Update yourselves!" 📡
- ✅ What ChangeNotifier is and why it's essential
- ✅ How to build an ArticleViewModel (the MVVM middleman)
- ✅ How to call notifyListeners() to update the UI
- ✅ The complete Model → ViewModel → View data flow
ChangeNotifier — The "I Have News!" Announcement System
ChangeNotifier is a class from Flutter that does two simple things:
1. notifyListeners()
Call this method when your data changes. It's like ringing a bell — everyone who's listening knows to check for updates.
2. addListener()
Other objects can "subscribe" to notifications. When notifyListeners() is called, all subscribers are alerted.
Think of it like a group chat: ChangeNotifier is the chat group. When someone sends a message (notifyListeners()), everyone in the group gets a notification and can read the new message.
The Complete MVVM Data Flow
Here's how data flows through your app with all three MVVM layers connected:
The golden rule of MVVM: The View never talks directly to the Model. Everything goes through the ViewModel. This keeps your code clean and testable!
1 Create the ArticleViewModel — The State Manager
The ViewModel is the bridge between your Model (data) and View (UI). It extends ChangeNotifier to broadcast updates.
Step 1.1 The Basic ViewModel Structure
Add this code to your main.dart file, below the ArticleModel class:
🔍 ArticleViewModel — Line by Line
class ArticleViewModel extends ChangeNotifier { // ← Extends ChangeNotifier!
final ArticleModel _model = ArticleModel(); // ← Has a Model inside
Summary? _summary; // ← Private state (the data)
bool _isLoading = false; // ← Loading state
String? _errorMessage; // ← Error state
// Public getters — read-only access to state
Summary? get summary => _summary;
bool get isLoading => _isLoading;
String? get errorMessage => _errorMessage;
}
What each part means:
extends ChangeNotifier— "I am a ChangeNotifier! I can broadcast updates to listeners."ArticleModel _model— The ViewModel owns the Model. It's the only one who talks to it.Summary? _summary— The actual data we fetched. Starts asnull(no data yet). The?means it can be null.bool _isLoading— Tracks whether we're currently fetching data. The UI can show a spinner!String? _errorMessage— If something goes wrong, we store the error here for the UI to display.- Public getters — Outside code can READ these values but can't change them directly. Only the ViewModel changes them!
2 Add the Load Method — Fetch Data and Notify
Now we add the method that fetches data from the Model, stores it, and notifies listeners.
Step 2.1 The loadRandomArticle Method
Add this method inside your ArticleViewModel class:
Future<void> loadRandomArticle() async {
_isLoading = true; // 1. Turn on loading spinner
_errorMessage = null; // 2. Clear any old errors
notifyListeners(); // 3. Tell UI: "Show loading!"
try {
_summary = await _model.getRandomArticleSummary(); // 4. Fetch data from Model
} catch (e) {
_errorMessage = e.toString(); // 5. If error, store the message
}
_isLoading = false; // 6. Turn off loading spinner
notifyListeners(); // 7. Tell UI: "Data is ready (or error)!"
}
Step-by-step walkthrough:
- Set loading to true — The UI can now show a spinner.
- Clear errors — If there was a previous error, forget it. Fresh start!
- First notifyListeners() — Tells the UI: "I'm about to fetch data. Show loading state!"
- Await the Model — Calls our ArticleModel to fetch from Wikipedia. The
awaitpauses here without freezing. - Catch errors — If the network fails, we catch the error instead of crashing.
- Set loading to false — Done loading, whether success or failure.
- Second notifyListeners() — Tells the UI: "I'm done! Check _summary for data or _errorMessage for errors."
3 The Complete MVVM Picture — How Everything Connects
Let's see all three layers together and trace exactly what happens when a user taps "Load Random Article."
Step 3.1 Your Complete Code So Far
Here's the full architecture you've built. Notice how each layer has a single responsibility:
ArticleModel
Only job: Make HTTP requests and parse JSON. Knows nothing about UI or state.
getRandomArticleSummary()
ArticleViewModel
Only job: Hold state (summary, loading, error) and notify the UI when things change.
loadRandomArticle() + notifyListeners()
ArticleView (Widget)
Only job: Display whatever the ViewModel tells it to. No data-fetching logic!
(Built in next lesson!)
Step 3.2 The Full Data Flow — From Tap to Screen
- User taps button → View calls
viewModel.loadRandomArticle() - ViewModel sets loading=true → calls
notifyListeners() - View rebuilds → sees
isLoading==true→ shows spinner - ViewModel calls Model →
_model.getRandomArticleSummary() - Model makes HTTP request → fetches JSON from Wikipedia
- Model returns Summary → ViewModel stores it in
_summary - ViewModel sets loading=false → calls
notifyListeners() - View rebuilds → sees
summary!=null→ displays article!
📝 What You Learned Today
ChangeNotifier Basics
Extended ChangeNotifier to create a class that can broadcast updates to listeners whenever data changes.
Built ArticleViewModel
Created the MVVM middleman with private state (_summary, _isLoading, _errorMessage) and public getters.
Used notifyListeners()
Called notifyListeners() twice — once to show loading, once when data arrives. This triggers UI rebuilds.
Complete MVVM Flow
Understood the full data path: Model fetches → ViewModel stores + notifies → View rebuilds. Separation of concerns!
🧠 Test Yourself!
Q1 What is the purpose of notifyListeners() in a ChangeNotifier?
Q2 In MVVM, which layer is responsible for making HTTP requests?
📦 Project 1: Test the ViewModel in main()
Objective: Call loadRandomArticle and verify it works
📋 Requirements:
- Create an instance of
ArticleViewModelinmain() - Add a listener using
viewModel.addListener(() { ... }) - Inside the listener, print the current state:
- If
isLoading— print "Loading..." - If
errorMessageis not null — print the error - If
summaryis not null — print the article title
- If
- Call
viewModel.loadRandomArticle() - Run and check your terminal for the output
🎯 Expected Output:
Your terminal should print "Loading..." followed by a real Wikipedia article title!
📦 Project 2: Add a Retry Capability
Objective: Handle errors and allow retrying failed requests
📋 Requirements:
- Verify that your
loadRandomArticleproperly catches errors - The ViewModel already has
_errorMessage— make sure it's set in the catch block - Add a getter called
hasErrorthat returns_errorMessage != null - The user can call
loadRandomArticle()again to retry — it automatically clears the old error - Test it by:
- Temporarily changing the URL to something invalid
- Calling
loadRandomArticle() - Checking that
errorMessageis set - Fixing the URL and calling it again — verify the error clears!
🎯 Expected Output:
The ViewModel gracefully handles errors and allows retry — a real-world pattern!
'https://en.wikipedia.org/api/rest_v1/page/summary/THIS_PAGE_DOES_NOT_EXIST_12345' as a test URL that will return a 404 error.Lesson Complete!
You built a professional ViewModel with ChangeNotifier! You now understand the core pattern used in production Flutter apps for state management.
Click the button above to track your progress!