Launch Offer

Flutter Foundations Course

Buy now and get 25% off the regular price!

View Course

Flutter App Architecture with Riverpod: An Introduction

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.

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:

Flutter App Architecture using data, domain, application, and presentation layers. Arrows show the dependencies between layers
Flutter App Architecture using data, domain, application, and presentation layers. Arrows show the dependencies between layers

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:

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:

The Clean Architecture
The Clean Architecture. Source and credit: The Clean Code Blog

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).

Comparison with the Clean Architecture
Comparison with the Clean Architecture

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 Architecture
MVC Architecture

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 MVC Architecture
Comparison with MVC Architecture

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:

MVVM Architecture
MVVM Architecture

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.

Comparison with MVVM Architecture
Comparison with MVVM Architecture

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)
Bloc Architecture
Source and credit: Bloc Architecture

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.

Android App Architecture
Android App Architecture

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:

Comparison with Android App Architecture
Comparison with Android App Architecture

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:

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:

Want More?

Invest in yourself with my high-quality Flutter courses.

The Complete Dart Developer Guide

The Complete Dart Developer Guide

Learn Dart Programming in depth. Includes: basic to advanced topics, exercises, and projects. Fully updated to Dart 2.15.

Flutter Animations Masterclass - Full Course

Flutter Animations Masterclass - Full Course

Master Flutter animations and build a completely custom habit tracking application.