When building complex apps, choosing the correct app architecture is crucial, as it allows you to structure your code and support your codebase as it grows.
Good architecture should help you handle complexity without getting on the way. But it's not always easy to get it right:
- The absence of it creates an overall lack of organization in your code
- The excess use of it leads to over-engineering, making it hard to make even simple changes
In practice, things can be quite nuanced and it takes some practice and experience to get the right balance.
So in this article, I will introduce a reference architecture based on the Riverpod package that is very well suited for Flutter app development.
And I'll also offer a comparison with other popular architectures, as I have borrowed many concepts and ideas from them.
sponsor

Build and grow in-app purchases. Glassfy’s Flutter SDK solves all the complexities and edge cases of in-app purchases and subscriptions so you don't have to. Test and build for free today by clicking here.
Overview of Existing Architectures
If you research this topic, you may come across terms such as MVC, MVP, MVVM, and Clean Architecture. These are popular app architectures that were introduced long ago to solve similar problems to the ones we face today with Flutter.
Strictly speaking, MVC, MVP, and MVVM are design patterns, while clean architecture defines a set of rules and principles to help you architect any complex software system.
While the principles they were built upon are still very relevant today, they were not tailored for Flutter app development.
Still, many attempts have been made to bring them to the Flutter world, with varying degrees of success.
Popular Flutter App Architectures: Bloc and Stacked
Most notably, the Bloc Architecture has gained some good adoption, mainly thanks to the popularity of the Bloc Library, which is used by various large companies. It is very opinionated and gives us a strict set of rules for how we're to structure our Flutter apps (and this is a good thing!).
Another promising one is the Stacked Architecture, which is based on the Stacked package and is largely inspired by MVVM.
They both rely on Provider, which is the recommended package for state management in the official Flutter documentation.
And while there's nothing wrong with either Bloc or Stacked, the author of Provider has created the Riverpod package to "make some improvements that would be otherwise impossible" (in his own words).
Over time, I've come to appreciate the power of Riverpod as a complete solution for dependency injection and state management. So I decided to build my own architecture on top of it. 👇
My Take on Flutter App Architecture (using Riverpod)
While building Flutter apps of varying complexity, I've experimented a lot with app architecture and developed a deep understanding about what works well and what doesn't.
The result is a reference architecture that I've used in all my latest projects.
For lack of a better name, I'll call this Riverpod Architecture - though keep in mind that this is just my take on it, and not an "official" architecture endorsed by Remi Rousselet (the author of Riverpod).
This architecture is composed of four layers (data, domain, application, presentation).
Here's a preview:
Each of these layers has its own responsibility and there's a clear contract for how communication happens across boundaries.
In the diagram above, the arrows indicate the dependencies between layers.
And if you're interested in the details, here's a series of articles covering each individual layer separately:
- Flutter App Architecture: The Repository Pattern
- Flutter App Architecture: The Domain Model
- Flutter App Architecture: The Application Layer
- Flutter App Architecture: The Presentation Layer
But for now, I just want to offer a useful comparison with other architectures.
Comparison with Clean Architecture
Clean Architecture was introduced by Robert C. Martin and is represented with this diagram:
While things have different names, we can identify some similar concepts:
- Entities → Models
- Use Cases → Services
Since Clean Architecture was designed to be independent of UI, it does not account for a unidirectional flow of data from the data sources to the UI, which is a core design principle in many modern app architectures (Flutter and web alike).
Instead, it uses an interface adapter layer that contains controllers, gateways, and presenters. While in the Riverpod architecture, repositories belong to the data layer, and controllers belong to the presentation layer.
Likewise, the outer layer called frameworks & drivers contains both UI and DB. But these are at opposite ends in the Riverpod architecture.
That is not to say that Clean Architecture is bad. But I find that it adds some mental overhead, and I'd rather use a conceptual model that is closer to what I use daily for Flutter app development.
Comparison with MVC
Model–view–controller (MVC) is a software design pattern made of 3 types of components:
- Model: directly manages the data, logic, and rules of the application
- View: UI components (widgets)
- Controller: Accepts input and converts it to commands for the model or view
MVC typically overloads the model with too much logic, and an additional service layer is often added: MVC+S.
The model should be reactive (e.g., using ChangeNotifier
) so that the widgets rebuild when it changes.
And user inputs always go through the controller and not the model directly.
When compared to the Riverpod architecture, vanilla MVP is quite different in how the layers communicate with each other:
Comparison with MVVM
The Model-View-ViewModel (MVVM) pattern was introduced to separate the business and presentation logic of an application from its user interface (UI).
It is usually represented like this:
When using MVVM, the view can't access the model directly.
Rather, the view model handles user inputs and converts the data from the model so it can be easily presented.
And the view model is connected to the view via a data binding that uses the observer design pattern.
Having an intermediate view model is particularly useful when the data returned from the backend doesn't map well to the UI you need to show.
In this respect, it's easy to compare MVVM with the Riverpod architecture:
- View → Widget
- View Model → Controller
- Model → Model + repository + data source
MVVM is just a design pattern, and the diagram above doesn't include any concept of data later.
In practice, services and repositories are added to encapsulate data access and caching, making MVVM quite similar to the Riverpod architecture.
Comparison with Bloc Architecture
The Bloc Architecture page contains this diagram that defines three layers:
- Presentation
- Business Logic
- Data (Repository & Data Provider)
This is in line with the Riverpod architecture presented here:
- Presentation (UI) → Widgets
- Business Logic (Bloc) → Controllers & Services
- Data (Repositories) → Repositories
- Data (Data Providers) → Data Sources
The primary difference is that blocs can depend on other blocs in order to react to their state changes, while the Riverpod architecture has a clearer distinction between controllers (which manage the widget state) and services (which manage the application state).
Note: when using Bloc, state changes are always represented as streams of data. But Riverpod makes it possible to watch different kinds of data (Streams, Futures, StateNotifiers, ChangeNotifiers etc.)
Overall, I find that Bloc Architecture is the closest one to the approach presented here.
Comparison with Stacked Architecture
The Stacked Architecture (based on the Stacked package) is similar (but not identical) to MVVM and consists of 3 major pieces which map quite easily to the Riverpod architecture:
- View → Widgets
- ViewModel → Controller
- Service → Service
It also defines some rules about what you can and cannot do (more info here).
Stacked provides a base architecture and an extensive set of useful widgets (such as ViewModelBuilder
) that you can use to "bind" a ViewModel with a View and make your life easier so that you don't have to reinvent the wheel.
ViewModels themselves are Dart classes that extend ChangeNotifier
and they come with reactive and non-reactive variants.
The main difference is that Stacked is based on Provider, whereas the Riverpod architecture uses Riverpod (obviously 😅).
But as we said, Riverpod gives us everything we need to build a robust architecture.
Comparison with Android App Architecture
I have not mentioned this until now, but the Android docs also have a useful guide to app architecture that closely resembles the one I'm using.
While this architecture refers to many common Android components (such as activities, fragments, and so on), these are the same as widgets in the Flutter world, and there's a close match across layers:
In the Android app architecture, the UI and data layers are always required, while the domain layer is optional. This is because not all apps have complex business logic, or simple business logic that needs to be reused by multiple view models. Likewise, in the Riverpod architecture it makes sense to omit the application layer if it's not needed.
Overall, I encourage you to read the entire Android guide, as it describes the role of each layer in detail.
Other Popular Architectures
App architecture is based on abstractions that are implemented using design patterns, not APIs. As a result, you can create your own app architecture on top of other popular state management packages such as redux, MobX, etc.
You can even use what Flutter gives you out of the box (InheritedWidget, ChangeNotifier, ValueListenableBuilder, and friends).
Note: GetX is a popular state management framework that claims to do-it-all. But I won't cover it here, for some very good reasons.
Ultimately, what matters most is that you define clear contracts and boundaries in your app.
In this respect, I really like this quote from the eBay engineering blog:
We believe that choosing the right tool, or applying a single design pattern, is much less important than establishing clear contracts and boundaries between the unique features and components in the application. Without boundaries, it becomes too easy to write unmaintainable code that doesn’t scale as the application grows in complexity.
Conclusion
As we have seen, the Riverpod Architecture has many things in common with other popular architectures (and a few differences).
To this day, I've been very happy to use it in various Flutter apps, and I've written a series of articles covering it in more detail:
- Flutter Project Structure: Feature-first or Layer-first?
- Flutter App Architecture: The Repository Pattern
- Flutter App Architecture: The Domain Model
- Flutter App Architecture: The Application Layer
- Flutter App Architecture: The Presentation Layer
New Flutter Course Now Available
I launched a brand new course that covers Flutter app architecture in great depth, along with other important topics like state management, navigation & routing, testing, and much more: