This is the fist post of a series of posts that will explain my take on App architecture for Flutter. It will be highly opinionated so be warned 😇
I’m in software now for about 20 years. I started with mobile 4 years ago with Xamarin Forms because cross platform was the only thing that made sense to me for an Indie App. Xamarin Forms almost forces you to use MVVM because you define the UI in XAML so you need some glue layer to connect the UI to your Model seems to make a lot of sense on the first view. While working with Xamarin I was introduced to ReactiveUI and fell in love with streams and Reactive Extensions (Rx) which made my Apps much more robust.
While using MVVM with Xamarin Forms was just natural I was surprised when coming to Flutter that there was no recommend architectural pattern. So I started to explore different options but didn’t really like any of them:
- InheritedWidget never managed to make it only update the part of the Widget tree that’s data changed, so I just used it to access a model class publishing Dart Streams but soon dropped it in favour of a generic Service Locator
- Scoped Model better than InheritedWidget but didn’t give me the flexibility I was used from ReactiveUI
- Redux which was the one pattern that was recommend from a lot developers coming from React Native. I have written a whole post on why I don’t like it
- BLoC If I hadn’t already started to work on my own pattern when BLoC was promoted I probably would have be stuck with it because it offers a really flexible and reactive solution. What I don’t like is that it publishes
Stream Sinksso I can not just pass a function/command to an event handler of a Widget. Also BLoC does not tell you how you should structure your App as a whole. There is no clear definition of how big a BLoC should be or what’s the scope of a single BLoC.
- MVVM As I was used to it I first checked out if it would fit with Flutter too. It doesn’t! The whole point of an ViewModel is to provide representation of your model to that it can be easily consumed by your Views by using Bindings. But Flutter doesn’t update its Widgets with new data, it always rebuilds them as I discussed here. Further ViewModels need to keep in sync with the underlying Model which can lead to nasty bugs and reality shows that the promised advantage of reuse ViewModels across Apps almost never happen. Adam Pedley has a great ranting post on the shortcomings
The ugly truth of too much Layering
It’s almost a dogma in software development that you always should build your Apps in several layers where each layer should only access the next layer below because it will allow you to:
- Reuse layers in other project
- Let’s you easily replace on layer with another one
- Makes them easier testable
I have almost no project I have seen that whole layers got reused. If you have generic code that can be used somewhere else it makes much more sense to factor that out into a generic library. Also replacing whole layers is not a really common use case. Most people will never replace a data base after an App is beyond a certain development stage, so why add an abstraction layer for it. And in case you really have to our current development tools make refactoring quite easy. What it makes indeed easier is testing.
I don’t say you shouldn’t use layering but I question if we have to do it as strictly as we did in the past. Extensive layering lets you use a lot of code and can make problems in keeping a single source of truth of your App’s state. Introduce layers only where it helps you not just because it’s a best practice.
An ideal architecture for Flutter
So what do I expect from an ideal architecture?
- Make it easy to understand how your App works. This one extremely important goal for me. New developers starting with an existing App’s code should be able to follow execution paths easily
- Make it easy to work as a team on one App
- The architecture itself should be easy to understand and to follow
- No boiler plate code just to make the architecture work
- Support the reactive UI approach of Flutter
- Make debugging easy
- Don’t hurt Flutters great performance
- Easy to extend
- Make testing easy
- Let’s you focus on your Application instead of internals
The self responsible Widget
Given the aimed stateless nature of Flutter UIs one Page/Widget shouldn’t depend on another Page/Widget or change another Page/Widget. This lead to the idea that every Page/Widget should be self responsible for displaying itself and all it’s user interactions. This will make it easy to understand how your App works as you typically explore an App beginning from its UI. It also would make it easy to split work between developers because one developer can work on one Page without the need to know of the work of others.
RxVMS is an evolution of RxVAMS that I described in a previous post While applying RxVAMS in my current real world Flutter project I realized some weaknesses in it and improved it.
The current result of all this thoughts is what I call RxVMS which stands for Rx-View-Managers-Services. It fulfills all the above goals with the only requirement that you have to grasp Streams and parts of Rx. To help you with that I will devote the next post.
This is a partial schema of my current App
These are abstractions of interfaces to the outside of your App this can be a Database which requires you to serialize your objects, a REST API or some hardware of your phone. They don’t change any state of your app.
Managers group together semantically related functionality. Like everything needed for user management / authentication, everything related to an order or to your product catalogue. They provide CRUD operations on your objects.
Every state change (change of data of your App) has to be done through a Manager. They typically don’t store state themselves unless it’s critical for performance or it’s data that doesn’t change throughout the live time of the App.
They also can provide data sources for views if the data needs some additional transformation after retrieving it from a service. An example could be if you need to access two different data sources and combine them to one business object that a View can display. Managers can interact with each other.
Typically a StatefullWidget or a Widget that contains a StreamBuilder so that it can consume returned data from Managers or Services. This will often be a whole Page but could also be a complex custom Widget. They do not store any state that has to persist. Views are allowed to directly access Services so far they don’t change any state.
Although not in the diagram this are the objects that represent your business objects. In other models they belong to their own Model layer which also contains all business logic. In RxVMS they don’t contain any business logic that could change state. They are almost always plain data objects. (If I had included them it would have got RxVMMS which is long and VM could be misunderstood as ViewModel). Logically they belong to the manager layer.
In the upcoming posts we will explore this parts and how they work together in detail.
Originally published at www.burkharts.net on July 27, 2018.