Software architectural pattern for Flutter apps.
MWWM is based on principles of Clean Architecture and is a variation of MVVM.
It consists of three parts: Widget, WidgetModel and Model.
Widget — a representation layer that contains only UI related code.
WidgetModel - handles and accumulates all data needed for Widget: objects of the domain layer, scroll position, text fields values, animation state, etc. WidgetModel uses Model for interaction with various data sources.
Model - a link between WidgetModel and "the external world": data sources, services or other abstraction layers. It allows to develop both separately and have a possibility to modify one layer with no need for changing the other. Model is represented by two components: Change (a signal to model which means what we want to achieve) and Performer (that knows how to achieve it).
This architecture completely separates design and logic. Adds the ability to work on independent layers by different developers. Adds autonomy to work, like HTML and CSS.
Create a WidgetModel class by extending [WidgetModel].
class RepositorySearchWm extends WidgetModel {
RepositorySearchWm(
WidgetModelDependencies baseDependencies, //1
Model model, //2
) : super(baseDependencies, model: model); //3
}
1 - WidgetModelDependencies is a bundle of required dependencies. Default there is ErrorHandler, which give possibility to place error handling logic in one place. You must provide an implementation of handler.
2 - Model is contract with service layer. For now, it is optional feature. It is possible to use services directly but not recommended.
3 - don't forgive about provide model to superclass if you wont to use Model.
Add Widget simply by creating StatefulWidget and replace parent class with CoreMwwmWidget
class RepositorySearchScreen extends CoreMwwmWidget {
//...
@override
State<StatefulWidget> createState() {
return _RepositorySearchScreenState();
}
}
By convention create a same constructor:
RepositorySearchScreen({
WidgetModelBuilder wmBuilder, // need to testing
}) : super(
widgetModelBuilder: wmBuilder ??
(ctx) => RepositorySearchWm(
// provide args,
),
);
or by route:
class RepositorySearchRoute extends MaterialPageRoute {
RepositorySearchRoute()
: super(
builder: (context) => RepositorySearchScreen(
widgetModelBuilder: _buildWm,
),
);
}
WidgetModel _buildWm(BuildContext context) => RepositorySearchWm(
context.read<WidgetModelDependencies>(),
Model([
// performets
]),
);
Change parent of State of StatefulWidget to WidgetState:
class _RepositorySearchScreenState extends WidgetState<RepositorySearchWm>
All done! You create your presentation.
Simply in build method in WidgetState. No difference with Flutter framework.
WidgetState has WidgetModel after initState() called. There is a getter - wm - to get your WidgetModel in your Widget.
Only in WidgetModel. But we don't hardcodea way to do this, yet.
It is optional paragraph. You can write connection with services your favorite way.
To work with business logic need to decribe a contract which consists of two parts: Change and Performer.
Change - is an intention to do something on service layer. Change can has data. Formally, it is an arguments of some function.
Performer<R, Change> - is a functional part of this contract. It is so close to UseCase. Performer, in ideal world, do only one thing. It is small part og logic which needed to perform Change.
We recomend following structure:
- ./
- data/
- model/
- services(repository)/
- changes.dart // can split
- performer.dart // can split
- ui/
- screen(widget)/
- wm.dart
- route.dart
- screen(widget).dart
- screen(widget)/
- Coordinator - abstraction to place navigation logic
- Code generation (may be)
- Somthing else ? Create an issue with request to feature.