A Smartphone with chat bubbles containing the Flutter Logo and the text "agnostic state management"
Apps

Flutter: Implementing Agnostic State Management to Build Scalable Apps

Lesezeit
8 ​​min

Whether you are a beginner or an experienced Flutter Developer, while your app evolves there will be the moment when you have to think about Flutter state management. You may manage to delay this decision to the moment when a feature forces you to manage the app state. But what to do now? It is always good to have a quick look into the official Flutter documentation for a recommendation from the Flutter Development Team. The recommendation will confront you with a various number of state management options. 

Common use cases

Most likely you need to consider use cases like persisting the state for a use session (see image: the common app). For example: If a webshop shopping cart is filled you may want to preserve, change and show the content of the shopping cart on multiple widgets. To do so, the Flutter app needs to have an entity that is able to manage the shopping cart state. We will discuss entities at a later stage in this article.

Diagramm of a common app with relationship cross points
The common app

Popular Flutter state management packages

MobX, Redux, BloC, or Riverpod just to name a few (see image: the common app) Flutter state management packages. Additionally, there is the option to use Stateful Widgets to handle your widget state locally. All of the above-named packages have their pros and cons, depending on the Flutter app requirements. For example, if you find yourself in an environment where you have to manage multiple connected use cases across your application. In this case, Redux or BloC might NOT be much of a help, even though the Redux DevTools are quite helpful.

Problems with Flutter state management packages

Modern Flutter State Management offers simple functionality to handle your defined state. Namely, you should be able to create variables to persist- and functions to change the defined state. Common public packages differ mainly in naming conventions or the scope of the state. In Riverpod, you are able to cut down your state in individual state variables and the sum of all states considered as the Flutter app state (see image: sum of states).

formula of the sum of states
sum of states

It becomes a challenge to choose the most suitable Flutter state management mechanism which is the best fit for the requirement of an app. Furthermore, the urge of developers to follow old patterns could create additional issues. The phrase “We have always done it this way!“, is a developer trapp like the law of instrument stated by A. Maslow:

„If the only tool you have is a hammer, it is tempting to treat everything as if it were a nail.“ 

1966 Maslow’s hammer by Abraham Maslow 

Following this approach leads to a loss of freedom and could slow you down significantly while new features are added to your app.

As previously mentioned the question remains, “Which Flutter state management to choose?“. Obviously, there is no straightforward answer, but a recommendation. If the structure of the app remains adjustable without major refactoring, then you are in a good position to change your Flutter state management package. Of course, sooner or later your app becomes more complex and changes in state management may become more difficult.

In the following, you will see a more agnostic approach to Flutter state management. With this approach, you are able to change the state management package with minor effort.

Flutter agnostic state management

In order to be state management package agnostic, two things have to be defined. First is the convention for how and where we manage the state. This simple convention keeps the Flutter app clean and the structure can easily be discussed amongst all developers. Secondly are independent entities to wrap the selected function of a Flutter state management package. You may think: “Oh no, another wrapper class to implement, test and manage“ and you are correct. Doing this will lead to some boilerplate code. But this boilerplate, as any, has one benefit. It is yours, and you are in control of exposing only the things you need. Below are code snippets showing an example based on the integration of hive as a state management solution, therefore Store, AppState, and Box are model classes. The model classes are either required from hive or used as a custom state wrapper.

Convention

a prototypical smartphone screen with two widgets on it
use case convention

If you are familiar with the Domain Driven Design from Eric Evans, where we structure our software close to the stakeholder domain. We try to transform the domain into use cases and connect the Flutter app state to multiple use cases. What that means, create a directory for your use case and subdirectories for sub-use cases. Where every UI element of the use case is allowed to use an entity that is directly connected to its managed state (see image: use case convention). Furthermore, the encapsulated state is never used directly from a Ui-Widget, but only from an entity that wraps the connection between UI and store. A detailed description of the store follows later in this article.

Entities

An agnostic state management approach has to provide a minimum of three entities to wrap the initialization of a state, the possibility to change the state, and a connection to the UI-related widgets (see images: discover entities).

graphic of Flutter state management: initialization, change and connect

Entities are collections of functions wrapped in Dart classes, which are injected or imported into your Flutter app.

code connect: wire state to UI

code of initialization and change
discovery entities

Initializer.dart

The simplest implementation for an entity that cares about the initialization of a state is a wrapper around Flutter’s FutureBuilder. The clue is the minimal exposure of a future that returns the app with an initialized store (see code: Initializer). At this point, the terminology is quite important. A store initializes a Flutter app state and a store provides a function that changes the state.

code: Initializer

The described store is exposed as a global immutable getter, like the MobX or Riverpod package. On the other side, the store needs to be connected to the Ui. Doing this is wrapped within a designated StoreListender.

StoreListener.dart

The connection between Ui and Store could be achieved by implementing a ValueListener. This typed listener uses the core flutter implementation without extra packages (see code: StoreListener).

code: StoreListener

As recommended by the convention from above the StoreListener should be wired to the use case level widget (see code: Use Case Widget). Remaining is an abstraction between widget and store, which corresponds to a use case-driven repository. Since this article concentrates only on the top-level Flutter state management, we will not go deeper into all required wrappers.

code: Use Case Widget

The entities Initializer, Store, and StoreListener, are the wrapper to achieve agnostic state management. The benefit is a well-structured and maintainable code. You are able to switch between Flutter state management packages which use a consolidated state. For simplicity, the new app state is created within the widget, but this should be not the common practice. The following example shows the integration of hive as state management and the basic interface which has been described in this article (see code: Store).

code: Store  

Recap

Flutter has become one of the most convenient frameworks for cross-platform development. Following the described approach, a Flutter app is more independent in regard to state management and therefore more flexible. This article should be a source of inspiration for other developers to question their architecture decisions.

Follow up:

Hat dir der Beitrag gefallen?

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert