Flutter MVU architecture/state management package

Overview

mvu_flutter

No mutability. No builders. No connectors. No reducers. No StreamControllers and subscription management. A truly declarative state management approach taken from the Elm language.

Motivation

There are a lot of state management packages already, and there are even some MVU packages. Why do we need another one?

I was unable to find one that satisfies my needs. Some are nice but take stupid amounts of code to use, some are good in React world, but here they rely too heavily on code generation. Some give too much freedom and code becomes impossible to maintain, and most of them use mutability. Unfortunately, the list can be extended.

Ok, there is mvu_layer and Dartea. What is wrong with them?

Dartea was a promising project! It really was a close implementation of Elm-like MVU, but it was abandoned three years ago. Shame.

What about mvu_layer?

In my opinion, the final product wandered a little too far from The Elm Architecture. It uses Messengers, builders and it feels more like Redux more than anything else, and it’s “Flutterfied” – that is, it has a lot in common with other, more popular approaches that I don’t find appealing.

The solution

mvu_flutter allows writing entire apps without directly mutating state once, writing purely functional code, and using Programs like loosely coupled building blocks.

It’s a very close implementation of MVU architecture from Elm language and it tries to emulate some of the features. It uses simple data classes to emulate tuples, inheritance to further emulate algebraic data types and it communicates with emulated “Elm runtime” through handy dispatch methods.

It provides full immutability, ease of reasoning about code, protects from bugs that other approaches are very prone to, and is just very pleasant to use. Imagine that you don’t have to fight with state management, but instead, it will help you, lead in the right direction and work as intended in the end. I was unable to find such an approach, so mvu_flutter was created!

If the words above didn’t make a lot of sense for you — that’s okay! MVU is one of those things that “click” and becomes natural after a while, and the section below will help with understanding the core concepts. To further sort out some of the corner cases, a collection of examples can be used.

Getting started

Let’s build a counter!

Model

Firstly, the counter needs a Model. Every user-defined model must extend the base Model class for type safety, and it must be fully immutable; every field must be final.

Model is used to store the current state that the UI reflects, it is a purely data-abstraction that doesn’t do anything other than store data. Our model will hold a single integer field – currentValue.

@freezed
class CounterModel extends Model with _$CounterModel {
  const factory CounterModel(int currentValue) = _CounterModel;
}

@freezed is used here to reduce boilerplate, it’s not obliged to make every model with it, but it comes with some upsides: it implements .== method for us, which needs to be implemented for the library to update the UI when the model changes, and implements the copyWith method which is extremely useful in update function.

Messages

Then, we need to define the Messages that will be sent through the dispatch function and will be used inside the update function. Let’s make our counter able to both increment and decrement the current value. Every message will hold a single field, amount, that will be used to determine by how much currentValue will be modified.

class IncrementMsg extends Msg<CounterModel> {
  final int amount;
  IncrementMsg({required this.amount});
}

class DecrementMsg extends Msg<CounterModel> {
  final int amount;
  DecrementMsg({required this.amount});
}

Think about messages as real-world physical messages, like letters, parcels, and other shipments. They have their form and contents – just as MVU messages do.

Every message must extend the base Msg class and specify as generic the Model that they are used with. So, following the real-world parallels, a shipment that consists of a ceramic mug can follow the type signature class ParselMsg extends Msg<YourName>.

Update function

Now, we need to describe how every message will affect the model. Remember, models are immutable, so the update function will not modify, but produce a new model, copying the existing one with updated fields.

Since MVU is a declarative approach, you do not need to tell how, when, and where our model will be assigned and used – you only need to describe how it will be affected for every message, and the library will take care of the rest.

update function type signature states that it must return not a Model, but a ProducedState. It is a simple tuple wrapper that consists of a new model and commands. Commands are optional and not used here for simplicity.

Update functions must be pure! It’s mandatory that for every combination of inputs the same output will be produced. For anything that involves side effects, commands must be used. Side effects include but are not limited to: API calls, HTTP requests, local database queries, terminal logging, push notifications, and more.

class UpdateCounter extends Update<CounterModel> {
  @override
  ProducedState<CounterModel> update(
      Msg<CounterModel> message, CounterModel model) {
  if (message is IncrementMsg) {
    return ProducedState(
        model.copyWith(currentValue: model.currentValue + message.amount));
  }
  if (message is DecrementMsg) {
    return ProducedState(
        model.copyWith(currentValue: model.currentValue - message.amount));
  }
  return ProducedState(model);
  }
}

Note – the update function must be implemented synchronously, anything asynchronous must be performed through commands, example of commands usage can be found in the examples below.

View

Lastly, we need a UI to display our Model and send Msgs to the update function. mvu_flutter provides an extension of StatelessWidget called View, and every class that extends View must specify the Model that it displays through generic.

Note – every View of Model A must be a direct descendant of Program (more about Programs later) of the same type A. Trying to use View<A> as a descendant of Program<B> that is a descendant of Program<A> will lead to terrible bugs and must never be used in that way. In those cases, the composition or/and dispatchGlobal function must be used. More about those cases can be learned from examples.

class CounterView extends View<CounterModel> {
  @override
  Widget view(BuildContext context, CounterModel model) => Scaffold(
    body: Center(
        child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Text('Counter value: ${model.currentValue}'),
          ElevatedButton(
            onPressed: dispatchCallback(IncrementMsg(amount: 10)),
            child: Text("Increment"),
          ),
          ElevatedButton(
            onPressed: dispatchCallback(DecrementMsg(amount: 1)),
            child: Text("Decrement"),
          ),
        ],
      ),
    ),
  );
}

The View widget adds two new methods – view and dispatch. View’s Model type that View will reflect must be specified as generic. The difference in view and build methods is that in the View widget first one must be overridden instead of build and it has a new parameter – model. Model is immutable and it is passed for read-only rendering purposes.

To change the model and update the state, the second method, dispatch, must be used. It takes as an argument a message, and the message must extend Msg with the same model type as the View. Note the dispatchCallback method – it's a convenience method that can be used as an argument where the dynamic Function() type is needed, so you don't have to wrap the dispatch method in anonymous closure by yourself.

If there is a need to dispatch a message to another Program with another model type, the dispatchGlobal function is used, but be careful with doing so. There is no way to check if such a Program exists, and it is done this way intentionally – the dispatchGlobal function must be used only in cases where it’s known about the target Program existence.

Program

And to wrap everything up we need to declare a Program. Program takes 5 parameters: an initial model, a view, an update, list of initial commands (optional), and a subscriptions function (also optional).

Program can be used as any other widget, and every Program has its data flow loop. Since every program consists of several independent modules, they can be replaced individually, which is very useful in team development and for testing new ideas.

Lets declare the Program:

final counterProgram = () => Program(
      model: CounterModel(0),
      view: () => CounterView(),
      update: UpdateCounter(),
);

Note the view parameter type – it must be a closure to be initialized lazily.

Commands

--Section in progress--

Subscriptions

--Section in progress--

Composition

--Section in progress--

Examples

-- Section in progress--

Performance, reusability, code style, and best practices notes

DOs

  • Do use StatelessWidgets instead of View when a model is not needed – it will help with reusability
  • Do split application into multiple different Programs, program per screen+ is fine – it will help with performance
  • Do split update function into multiple functions, possibly a function for a message – it will help with maintainability

CONSIDERs

  • Consider using freezed package when defying models – it will help with the boilerplate reduction
  • Consider combining both composition approaches when it's appropriate – it will help with maintainability
  • Consider namespacing command's functionality into stateless static dependencies – it will help with segregation of concerns

DON’Ts

  • Don’t use views as non-direct descendants of model type – it will lead to terrible bugs
  • Don’t implement any logic that will produce side effects or be referential non-transparent inside update functions – commands must be used instead and update functions must be pure
  • Don’t store state anywhere outside of Models – stateful dependencies must not be used

Sidenote and roadmap

This package is pretty small and there is not much room to grow bigger, pretty much all of the desired functionality was implemented in the very first version under the course of three evenings. There will be minor performance improvements in the next versions, the example library will continue to grow, but feature-wise – I don’t see what can be added. If you have any suggestions – feel free to contact me via email or by opening an issue on GitHub.

Variance feature in the stable version of Dart’s SDK will lead to breaking changes – the update will be reduced to a single function instead of a function wrapped in class and the initial model will be passed as an argument in the subscriptions function. For now, if the initial model is needed in subscriptions or initial commands, it must be declared as a top-level private variable.

You might also like...

Example of use bloc + freezed with a state that contains a list

blocfreezedlistexample A new Flutter project. Getting Started This project is a starting point for a Flutter application. A few resources to get you s

Mar 21, 2022

Timer based on provider state manager

timer_provider Timer based on provider state manager Getting Started This project is a starting point for a Flutter application. A few resources to ge

Nov 6, 2021

Trip management mobile Application

Trip management mobile Application

HereYouGO Trip management mobile Application This app will help you Track your expense during your trips. Track your trip destinations and the sub tri

Jul 7, 2022

Functional programming in Dart and Flutter. All the main functional programming types and patterns fully documented, tested, and with examples.

Fpdart Functional programming in Dart and Flutter. All the main functional programming types and patterns fully documented, tested, and with examples.

Dec 26, 2022

Demo project to implement MVU architecture in a Flutter application

flutter_mvu A playground project for implementing MVU in Flutter CICD Automated

Dec 16, 2021

State Persistence - Persist state across app launches. By default this library store state as a local JSON file called `data.json` in the applications data directory. Maintainer: @slightfoot

State Persistence Persist state across app launches. By default this library store state as a local JSON file called data.json in the applications dat

Sep 28, 2022

A recipe book app, made with Flutter and architected with clean architecture, using the MobX as state management.

recipe_book_app A new recipe book Flutter project, architected with clean architecture and state managed with MobX. Bases: Idea inpsired by Recipe-App

Jan 5, 2022

This project follows the Reso Coder course for flutter test-driven-development with clean architecture and BloC state management for a random trivia simple app.

This project follows the Reso Coder course for flutter test-driven-development with clean architecture and BloC state management for a random trivia simple app.

Jan 5, 2022

⚖️ A Flutter Architecture for small/medium/large/big large scale using Provider as State Management with Get It!

⚖️ A Flutter Architecture for small/medium/large/big large scale using  Provider as State Management with Get It!

Flutter Provider Architecture Mobile Application Developed in Flutter. Running on both mobile platforms, Android 🤖 & iOS 🍎 . About this app This app

Jan 4, 2023

FlutterQRcode - Flutter QR Code Scanner app for a specific type of QR using GetX State Management Architecture

FlutterQRcode - Flutter QR Code Scanner app for a specific type of QR using GetX State Management Architecture

qrcode A new Flutter QR Scanner Project for Data Entry using GetX state manageme

Dec 11, 2022

This is a practical guide to MVVM architecture in flutter with riverpod for state management.

This is a practical guide to MVVM architecture in flutter with riverpod for state management.

flutter_mvvm This project is a practical guide to MVVM pattern in flutter with Riverpod. Install flutter Get your APIKEY from https://api.openweatherm

Jan 1, 2023

💻 Flutter clean architecture using the bloc & cubit library for state management

💻 Flutter clean architecture using the bloc & cubit library for state management

Egymation 🎥 · This application was developed using a well-defined and decoupled architecture, following TDD (test-driven programming) as a working me

Nov 21, 2022

Music player application for android. It's uses MVVM architecture and Provider & ValueNotifier state management.

Music player application for android. It's uses MVVM architecture and Provider & ValueNotifier state management.

music-player-flutter Flutter music player application which is my personal project published to play store. Project structures are as following,

Jul 10, 2022

This repo is an example of clean architecture using the GetX state-management solution.

This repo is an example of clean architecture using the GetX state-management solution.

GetX Clean Architecture A Flutter Clean Architecture Using GetX. This repo is forked from: https://github.com/phamdinhduc795397/flutter-getx-clean-arc

Jan 3, 2023

Practice building basic animations in apps along with managing app state by BLoC State Management, Flutter Slider.

Practice building basic animations in apps along with managing app state by BLoC State Management including: Cubit & Animation Widget, Flutter Slider.

Jun 8, 2022

An extension to the bloc state management library which lets you create State Machine using a declarative API

An extension to the bloc state management library which lets you create State Machine using a declarative API

Nov 28, 2022

A powerful state machine for MobX management, that can be used in almost any application state.

A powerful state machine for MobX management, which can be used in almost any application state. It has 3 states - loading, success, error - and is pe

Oct 31, 2022

Your best flutter coding friend. All in one; state management, navigation management(with dynamic routing), local storage, localization, dependency injection, cool extensions with best usages and with the support of best utilities!

okito Your best flutter coding friend. All in one; state management, navigation management(with dynamic routing), local storage, dependency injection,

Jul 10, 2022

Shopify Tag and Product Management App using Flutter and Riverpod State Management

Shopify Tag and Product Management App using Flutter and Riverpod State Management

Myshopify App A Simple Flutter Application project to get List of Tags, Products and Product Details from shopify https://shopicruit.myshopify.com/adm

Nov 12, 2022
A flutter boilerplate project with GetX state management.

flutter_getx_boilerplate Languages: English (this file), Chinese. Introduction During my study of Flutter, I have been using the flutter_bloc state ma

Kevin Zhang 234 Jan 5, 2023
Simple global state management for Flutter

Slices Slices is a minimalist state manegement, focused specifically for applications that needs a global state where different "pieces" of the applic

Erick 5 Jun 15, 2021
The modular state management solution for flutter.

The modular state management solution for flutter. Easy debugging : each event is predictable and goes into a single pipeline Centralized state : soli

Aloïs Deniel 44 Jul 6, 2022
Flutter State Management with provider :rocket:

Flutter - Gerenciamento de Estados com Provider Objetivos ao completar os estudos Aprenda a gerenciar o estado da sua aplicação com Single Source of T

Tiago Barbosa 0 Dec 6, 2021
A predictable state management library that helps implement the BLoC design pattern

A predictable state management library that helps implement the BLoC design pattern. Package Pub bloc bloc_test flutter_bloc angular_bloc hydrated_blo

Felix Angelov 9.9k Dec 31, 2022
Another state management solution

VxState VxState is a state management library built for Flutter apps with focus on simplicity. It is inspired by StoreKeeper & libraries like Redux, V

Pawan Kumar 42 Dec 24, 2022
London App Brewery State Management Project

todey_flutter A new Flutter application. Getting Started This project is a starting point for a Flutter application. A few resources to get you starte

null 0 Nov 1, 2021
A simple way to access state while robust and testable.

A state-management library that: catches programming errors at compile time rather than at runtime removes nesting for listening/combining objects ens

Remi Rousselet 3.9k Jan 3, 2023
A lightweight, yet powerful way to bind your application state with your business logic.

binder A lightweight, yet powerful way to bind your application state with your business logic. The vision As other state management pattern, binder a

Romain Rastel 172 Nov 16, 2022
Manage the state of your widgets using imperative programming concepts.

Imperative Flutter Manage the state of your widgets using imperative programming concepts. Setup Intall imperative_flutter package in pubspec.yaml dep

Jeovane Santos 5 Aug 20, 2022