Learn to create a ViewModel with ChangeNotifier and manage loading, success, and error states.
Introduction
When developers talk about state-management in Flutter, they're essentially referring to the pattern by which your app updates the data it needs to render correctly and then tells Flutter to re-render the UI with that new data.
In MVVM, this responsibility falls to the ViewModel layer,
which sits between and connects your UI to your Model layer.
In Flutter, ViewModels use Flutter's ChangeNotifier class to
notify the UI when data changes.
To use ChangeNotifier, extend it in your state management class to
gain access to the notifyListeners() method,
which triggers UI rebuilds when called.
Create the basic view model structure
Create the ArticleViewModel class with its
basic structure and state properties:
class ArticleViewModel extends ChangeNotifier {
final ArticleModel model;
Summary? summary;
Exception? error;
bool isLoading = false;
ArticleViewModel(this.model);
}The ArticleViewModel holds three pieces of state:
summary: The current Wikipedia article data.error: Any error that occurred during data fetching.isLoading: A flag to show progress indicators.
Add constructor initialization
Update the constructor to automatically fetch content when the
ArticleViewModel is created:
class ArticleViewModel extends ChangeNotifier {
final ArticleModel model;
Summary? summary;
Exception? error;
bool isLoading = false;
ArticleViewModel(this.model) {
fetchArticle();
}
// Methods will be added next.
Future<void> fetchArticle() async {}
}This constructor initialization provides immediate content when
a ArticleViewModel object is created.
Because constructors can't be asynchronous,
it delegates initial content fetching to a separate method.
Set up the fetchArticle method
Add the fetchArticle method that fetches data and manages state updates:
class ArticleViewModel extends ChangeNotifier {
final ArticleModel model;
Summary? summary;
Exception? error;
bool isLoading = false;
ArticleViewModel(this.model) {
fetchArticle();
}
Future<void> fetchArticle() async {
isLoading = true;
notifyListeners();
// TODO: Add data fetching logic
isLoading = false;
notifyListeners();
}
}The ViewModel updates the isLoading property and
calls notifyListeners() to inform the UI of the update.
When the operation completes, it toggles the property back.
When you build the UI, you'll use this isLoading property to
show a loading indicator while fetching a new article.
Retrieve an article from the ArticleModel
Complete the fetchArticle method to fetch an article summary.
Use a try-catch block to gracefully handle network errors and
store error messages that the UI can display to users.
The method clears previous errors on success and
clears the previous article summary on error to maintain a consistent state.
class ArticleViewModel extends ChangeNotifier {
final ArticleModel model;
Summary? summary;
Exception? error;
bool isLoading = false;
ArticleViewModel(this.model) {
fetchArticle();
}
Future<void> fetchArticle() async {
isLoading = true;
notifyListeners();
try {
summary = await model.getRandomArticleSummary();
error = null; // Clear any previous errors.
} on HttpException catch (e) {
error = e;
summary = null;
}
isLoading = false;
notifyListeners();
}
}Test the ViewModel
Before building the full UI, test that your HTTP requests work by
printing results to the console.
First, update the fetchArticle method to
print the results:
Future<void> fetchArticle() async {
isLoading = true;
notifyListeners();
try {
summary = await model.getRandomArticleSummary();
print('Article loaded: ${summary!.titles.normalized}'); // Temporary
error = null; // Clear any previous errors.
} on HttpException catch (e) {
print('Error loading article: ${e.message}'); // Temporary
error = e;
summary = null;
}
isLoading = false;
notifyListeners();
}Then, update the MainApp widget to create the ArticleViewModel,
which calls the fetchArticle method on creation:
class MainApp extends StatelessWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context) {
// Instantiate your `ArticleViewModel` to test its HTTP requests.
final viewModel = ArticleViewModel(ArticleModel());
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Wikipedia Flutter')),
body: const Center(child: Text('Check console for article data')),
),
);
}
}Hot reload your app and check your console output. You should see either an article title or an error message, which confirms that your Model and ViewModel are wired up correctly.