Starting new projects is fun! 🎉
Initially, it can be tempting to defer any decisions about app architecture and good code structure.
But if your app becomes more than a failed side project, you’ll want to revisit things and choose an app architecture that will support your codebase as it grows. 🧱
And in my previous article, I’ve introduced an app architecture based on the Riverpod package, which is based on four main layers:
I’ve been using this architecture on many different projects, and I’ve already written an entire series of articles about it:
- Flutter App Architecture with Riverpod: An Introduction
- Flutter App Architecture: The Repository Pattern
- Flutter App Architecture: The Domain Model
- Flutter App Architecture: The Application Layer
- Flutter App Architecture: The Presentation Layer
- Flutter Project Structure: Feature-first or Layer-first?
But before you decide to try it out in your next project, I'd like to show you how it compares to other popular architectures.
And this is exactly what this article is about. 👇
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. But how does my Riverpod architecture compare with other popular architectures?
Comparison with Clean Architecture
Clean Architecture was introduced by Robert C. Martin and is represented in 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, 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. Meanwhile, in the Riverpod architecture, repositories belong to the data layer, and controllers belong to the presentation layer.
Likewise, the outer layer is called frameworks & drivers and 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 MVC 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 similar to 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, the Bloc Architecture is quite close to the Riverpod architecture I'm proposing - though I feel it forces you to write too many classes, over and over (even when using Cubits, which are a lightweight version of Bloc).
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 that 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 1) complex business logic or 2) simple business logic that needs to be reused by multiple view models. Likewise, in the Riverpod architecture, we can 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 App Architecture with Riverpod: An Introduction
- Flutter App Architecture: The Repository Pattern
- Flutter App Architecture: The Domain Model
- Flutter App Architecture: The Application Layer
- Flutter App Architecture: The Presentation Layer
- Flutter Project Structure: Feature-first or Layer-first?
And if you want to go deeper, you can check out my Flutter Foundations course, where I guide you through the implementation of a complex eCommerce app using this architecture, along with many useful techniques you won't find elsewhere. 👇
Flutter Foundations 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: