RxVMS - A Practical Architecture for Flutter Applications
This is the first post in a series of publications explaining my understanding of Flutter's application architecture. I warn you - it will be very self-confident .
So far planned:
- Introduction (this post)
- Dart Streams Basics
- RxDart: magic transformations of flows
- RxVMS Basics: RxCommand and GetIt
- RxVMS: Services and Managers
- RxVMS: self-contained widgets
- User Authentication with RxVMS
I have been in programming for about 20 years. I started mobile development 4 years ago with Xamarin.Forms, because cross-platform was the only motivation for me as an indie developer. Xamarin.Forms is literally pushing you to use the MVVM pattern, since UI is defined in XAML and you need some kind of layer to glue the UI with the Model. In the process of working with Xamarin, I met ReactiveUI and was literally captivated by the streams and reactive extensions ( Rx ) that made my applications more reliable.
While Xamarin.Forms MVVMs were out of the box, when I switched to Flutter, I was surprised that there were no similar design patterns in it. I began to research the various proposed approaches, but none of the available ones completely satisfied me:
- InheritedWidget : I couldn’t get to update only the changed part of the widget tree, so I used it only to access model classes that publish dart streams (Dart Streams), but soon abandoned this idea in favor of the template Service Locator
- The Scoped Model is more interesting than it was
InheritedWidget, but it did not give me as much flexibility as I was used to with ReactiveUI
- Redux was the template recommended by many developers familiar with React Native. I have a whole post on why I don't like it.
- BLoC : if I had not already started developing my own pattern at a time when BLoC began to advance, most likely I would have taken it, as it is really flexible and reactive. What I don't like is that it publishes Stream Sinks and I can't just take and pass functions or commands to the widget's event handler. In addition, BLoC does not tell you how to structure your application as a whole, nor is there a clear definition of how big a certain BLoC should be or what its scope is
- MVVM : since I worked with him, this is the first thing I hoped to implement in Flutter. But no! The point of ViewModel is to gracefully provide the representation of your model in View through bindings. But Flutter does not update its models with new data, it always rebuilds them, as I already described . In addition, ViewModels should always be in sync with the base model, which leads to unpleasant bugs, and reality shows that the promised advantage of reusing ViewModels in applications is almost never achieved. Adam Pedley has a great post about these flaws
The harsh truth about layer redundancy
It is almost a dogma to the idea that in development you should always build your application in several layers, each of which has access only to the underlying, as this will allow you:
- reuse layers in other projects
- transparently replace one layer with another
- simplify testing
- in my practice there was no case where I observed a complete reuse of layers. If you have a universal code that can be reused, it makes more sense to put it in some kind of universal library;
- replacing whole layers is also not common practice. Most people are unlikely to replace the database after the application has reached a certain stage of development, so why add an abstraction layer for it. Well, if some of our current development tools are required, refactoring is quite easy;
- what really works is simplification of testing
I am not against the use of layers, however, should we follow this rule so recklessly as before. Excessive use of them leads to an increase in code, and can potentially create problems while maintaining a single source of application state. Therefore, apply layers when it is really necessary, and not based on "best practices."
Ideal architecture for Flutter
So what do I expect from a perfect architecture?
- Ease of understanding the application. For me, this is the most important goal. New developers involved in working with existing code should easily understand the development structure
- Team Development Ease
- The architecture itself should be easy to understand and maintain.
- No template code for crutches in development
- Flutter reactive style support
- Easy debugging
- Performance should not suffer
- Ease of expansion
- Ease of testing
- Opportunities to focus on the application instead of wandering in the source
Based on the nature of stateless interface elements, no page / widget in Flutter should depend on or influence others. This leads to the idea that each page / widget should independently be responsible for displaying itself and all its interactions with the user.
RxVMS is an evolution of the RxVAMS pattern described in a previous post , during the practical application of which some problems were identified and fixed.
The current result of all these thoughts is the RxVMS, or Rx-View-Managers-Services pattern. It performs all of the above tasks with the only requirement that you must understand Rx threads and elements. To help you with this, I dedicate the following post.
Here is a brief outline of my application
These are abstractions of interfaces to the outside world, which can serve as databases, REST APIs, etc. They do not affect the state of the application.
Managers group semantically similar functionality, such as authentication, ordering procedures, and the like. Managers manipulate the state of an object.
Any change in state (changes in application data) should be made only through managers. As a rule, managers themselves do not store data, except in cases critical for performance or runtime constants.
Managers can serve as proxy data sources, in case their certain transformation is required after a request from services. An example is combining data from two or more sources for display in a View.
Managers can interact with each other.
Typically, this is StatefullWidget or StreamBuilder, which is able to use data from managers and services. It can be a whole page or widget. Views do not store any state and can directly contact services while this rule is respected.
Although not included in the diagram, these are important entities that represent the business model. In other patterns, they can belong to a separate layer ( business model ) in conjunction with business logic. Here, in RxVMS, they do not contain any logic that changes the state of the application. Almost always, these are simple data types - plain data objects (if I included them in the pattern, it would look like RxVMMS, which is a bit long and leads to confusion - the VM might be mistaken as a ViewModel). Logically, domain objects are located in the managers layer.
The following posts will reveal the essence and interaction of these elements.