Home State in Flutter Apps Use ChangeNotifier to Update App State

🔔 ChangeNotifier — The Smart Middleman for Your App's Data

⏱️ 35-40 minutes 📊 Intermediate — State management! 📦 2 Projects included 🏷️ ChangeNotifier, ViewModel, State, MVVM, notifyListeners

🧒 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!" 📡

🎯
Today you'll learn:
  • ✅ 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:

🗄️ArticleModelFetches data from Wikipedia API
↓ gives data to
🔔ArticleViewModel (ChangeNotifier)Stores data + calls notifyListeners()
↓ notifies
🖼️ArticleView (Widget)Rebuilds UI when notified

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

lib/main.dart — Add below ArticleModel
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 as null (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:

Add loadRandomArticle method
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:

  1. Set loading to true — The UI can now show a spinner.
  2. Clear errors — If there was a previous error, forget it. Fresh start!
  3. First notifyListeners() — Tells the UI: "I'm about to fetch data. Show loading state!"
  4. Await the Model — Calls our ArticleModel to fetch from Wikipedia. The await pauses here without freezing.
  5. Catch errors — If the network fails, we catch the error instead of crashing.
  6. Set loading to false — Done loading, whether success or failure.
  7. Second notifyListeners() — Tells the UI: "I'm done! Check _summary for data or _errorMessage for errors."
🔑
Why call notifyListeners() TWICE? The first call tells the UI "show a loading spinner." The second call tells the UI "data is ready — show the article or error message." Without notifyListeners(), the UI would never know anything changed!

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

🔄
  1. User taps button → View calls viewModel.loadRandomArticle()
  2. ViewModel sets loading=true → calls notifyListeners()
  3. View rebuilds → sees isLoading==true → shows spinner
  4. ViewModel calls Model_model.getRandomArticleSummary()
  5. Model makes HTTP request → fetches JSON from Wikipedia
  6. Model returns Summary → ViewModel stores it in _summary
  7. ViewModel sets loading=false → calls notifyListeners()
  8. View rebuilds → sees summary!=null → displays article!
This is professional state management! Every layer does one job. If you need to change how data is fetched, you only touch the Model. If you need to change the UI, you only touch the View. The ViewModel remains the reliable middleman that ties everything together.

📝 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()

Project 1Beginner

Objective: Call loadRandomArticle and verify it works

📋 Requirements:

  1. Create an instance of ArticleViewModel in main()
  2. Add a listener using viewModel.addListener(() { ... })
  3. Inside the listener, print the current state:
    • If isLoading — print "Loading..."
    • If errorMessage is not null — print the error
    • If summary is not null — print the article title
  4. Call viewModel.loadRandomArticle()
  5. 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

Project 2Intermediate

Objective: Handle errors and allow retrying failed requests

📋 Requirements:

  1. Verify that your loadRandomArticle properly catches errors
  2. The ViewModel already has _errorMessage — make sure it's set in the catch block
  3. Add a getter called hasError that returns _errorMessage != null
  4. The user can call loadRandomArticle() again to retry — it automatically clears the old error
  5. Test it by:
    • Temporarily changing the URL to something invalid
    • Calling loadRandomArticle()
    • Checking that errorMessage is 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!

💡
Hint: Try using '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.

🚀 What's Next?

Your ViewModel is ready — it fetches data and notifies listeners! In the final lesson of this topic:

  • ListenableBuilder — The widget that automatically rebuilds when ChangeNotifier updates
  • Connect your ViewModel to the UI without manually calling setState
  • Build the complete Wikipedia Reader app with a working UI!
🎉

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!