Posted on Oct 8 • AI-assisted
Series 2 of 2 — Part 1 of 4
Building DevFeed — Part 1: MVVM Architecture Before Writing a Single ScreenSeries: Building DevFeed — A Proper Flutter MVVM App from Day One
When I started DevFeed, I had just finished the painful MVVM refactor of the shopping app. I knew what proper architecture looked like. I knew the difference between a repository and a ViewModel. I had felt the cost of not having that separation.
So this time I set up the entire architecture before I wrote a single screen.
DevFeed is a Hacker News reader app built as a Flutter MVVM practice project during my internship. This series documents how I built it — from a properly structured first commit all the way to shipping v1.0.1 with a GitHub Actions release workflow.
Planning the architecture first
Before opening the first Dart file, I mapped out the layers:
-
Model:
ArticleModelto represent a Hacker News story — id, title, url, score, author, and comment count. -
Repository:
HackerNewsRepositoryresponsible for fetching stories from the API. Returns a list ofArticleModel. -
ViewModel:
FeedViewModelusing RiverpodAsyncNotifier. Calls the repository, exposesAsyncValuestate to the UI. -
View:
FeedScreenthat watches the ViewModel. Renders loading, error, and data states. No API knowledge.
This is the same structure I eventually reached in the shopping app. The difference in development experience because of starting with it is the main story of this series.
The first commit: full scaffold
The first commit was a full scaffold — all 35 files. Models, repositories, ViewModels, screens, widgets, routing, theming, and a basic test file. Nothing was fully implemented but the skeleton of every layer was in place.
This approach felt strange at first. You write a lot of boilerplate before you can see anything on screen. But it paid off immediately when I started filling in implementations — because I always knew exactly which file to open and what it should contain.
Riverpod AsyncNotifier setup
I used AsyncNotifier for the feed ViewModel. AsyncNotifier is the right choice when your state is the result of an async operation — it handles loading, error, and data states out of the box.
class FeedViewModel extends AsyncNotifier<List<ArticleModel>> {
@override
Future<List<ArticleModel>> build() async {
return ref.read(hackerNewsRepositoryProvider).fetchTopStories();
}
Future<void> refresh() async {
state = const AsyncLoading();
state = await AsyncValue.guard(
() => ref.read(hackerNewsRepositoryProvider).fetchTopStories(),
);
}
}
go_router for navigation
I used go_router from day one. In the shopping app I had used Navigator.push directly, which scattered navigation logic across screens. go_router centralises routing in a single config file and supports declarative navigation, deep links, and route guards. Adding a new route later was a two-line change instead of a hunt through multiple screens.
What starting right feels like
The most noticeable difference compared to the shopping app was the absence of confusion. When I needed to add a feature, I knew where it should go. When I needed to fix a bug, I knew which layer was responsible.
The shopping app taught me what MVVM is. DevFeed taught me what it feels like to work in a well-structured codebase. You need both lessons.
Next: Part 2 — Building the Feed: Hacker News API, AsyncNotifier, and Parallel Fetching
Top comments (0)
For further actions, you may consider blocking this person and/or reporting abuse
