flutter_mediator 1.0.0+1 flutter_mediator: ^1.0.0+1 copied to clipboard
Flutter mediator is a MVC state management package base on InheritedModel with automatic aspect management to make them simpler, easier, and intuitive to use.
Flutter Mediator #
Flutter mediator is a MVC state management package base on InheritedModel with automatic aspect management to make them simpler, easier, and intuitive to use.
Features #
- Easy and simple to use - automatically make things work by only a few steps
- Efficiency and Performance - use InheritedModel as the underlying layer to make the app quick response
- Lightweight - small package size
- Flexible - provide both automatic and manual observation
- Intuitive - three main classes:
Publisher
,Subscriber
,Host
Publisher
- publish aspects
Subscriber
- subscribe aspects
Host
- dispatch aspects
Flutter Widget of the Week: InheritedModel explained #
InheritedModel provides an aspect parameter to its descendants to indicate which fields they care about to determine whether that widget needs to rebuild. InheritedModel can help you rebuild its descendants only when necessary.
Setting up #
Add the following dependency to pubspec.yaml of your flutter project:
dependencies:
flutter_mediator: "^1.0.0+1"
Import flutter_mediator in files that will be used:
import 'package:flutter_mediator/mediator.dart';
For help getting started with Flutter, view the online documentation.
Getting Started Quick Steps #
Model
, View
, Controller
, Host
: #
Model
: #
- Extend the model from
Publisher
- Declare rx variables with
.rx
, which will automatically rebuild the aspect-related-widget when updated. - Implement the update methods.
MyModel.dart
class MyModel extends Publisher {
/// `.rx` make the var automatically rebuild the aspect-related-widget when updated
var int1 = 0.rx;
void updateInt1() {
/// since int1 is a rx variable,
/// it will automatically rebuild the aspect-realted-widget when updated
int1 += 1;
}
Subscribe the View
: #
- Subscribe the widget with designated one or more aspect.
4-1. Choice an aspect to represent the widget.
In this example, a string type aspect with value'int1'
is designated.
4-2. Designate the model to be used.
In this example, myModel of class<MyModel>
is used.
_Then any rx variables used inside the create method will automatically rebuild the aspect-related-widget when updated._ (triggered by getter and setter)
class Int1Listener extends StatelessWidget {
@override
Widget build(BuildContext context) {
return 'int1'.subModel<MyModel>(
create: (context, model) {
/// Since model.int1 is a rx variable,
/// it will automatically rebuild the aspect-related-widget when updated.
/// In this example, the aspect is 'int1' of <MyModel>.
return Text('Int1 is ${model.int1}');
},
);
}
}
Controller
: #
- Put the controller somewhere in the widget tree.
For example, get the model of class<MyModel>
and execute it's update method within a RaisedButton.
RaisedButton(
child: const Text('Update Int1'),
onPressed: () => context.getModel<MyModel>().updateInt1(),
);
Host
: #
- Inject host at the top level of the widget tree.
void main() {
runApp(
MultiHost.create2(
MyModel(updateMs: 1000), // model extends from Publisher
ListModel(updateMs: 500),// model extends from Publisher
child: MyApp(),
),
/// MultiHost.create1 to MultiHost.create9 are provided by the package.
);
}
Works automatically!
#
- Then the state works automatically!
These steps can help you with most situations. The following details will explain the package one step further.
Example #
You can find the example in the example folder.
Try it once, you will see it's simple and easy to use.
Detail #
- Single model - host
- Multiple models - host
- Automatically update the widget with rx variable - Publisher
- Access the underlying value directly of rx variable - Publisher
- Update the value by call style of rx variable - Publisher
- Manually publish an aspect of the model - Publisher
- Manually publish multiple aspects of the model - Publisher
- Manually publish all aspects of the model - Publisher
- Future publish of the model - Publisher
- Rebuild only once per frame for the same aspect - Publisher
- Writing model helper - Publisher
- Get the model - Subscriber
- Subscribe an aspect of the model - Subscriber
- Subscribe multiple aspects of the model - Subscriber
- Subscribe all aspects of the model - Subscriber
- Subscribe with enum aspects of the model - Subscriber
- Manage rx aspects - Chain react aspects - advance topic
- Implement custom rx class - advance topic
- Aspect type - terminology
1. Single model #
void main() {
runApp(
Host(
model: AppModel(), // model extends from Publisher
child: MyApp(),
),
);
}
2. Multiple models #
MultiHost.create1
to MultiHost.create9
are provided by the package.
You can add more MultiHost.createXX
method, see multi_host.dart for example.
void main() {
runApp(
MultiHost.create2(
MyModel(updateMs: 1000), // model extends from Publisher
ListModel(updateMs: 500), // model extends from Publisher
child: MyApp(),
),
);
}
3. Automatically update the widget #
.rx
make the var automatically rebuild the aspect-related-widget when updated
int:
class MyModel extends Publisher {
/// `.rx` make the var automatically update the aspect-related-widget
var int1 = 0.rx;
void updateInt1() {
int1 += 1; // automatically update the aspect-related-widget
}
List:
class ListModel extends Publisher {
/// `.rx` make the var automatically update the aspect-related-widget
final data = <ListItem>[].rx;
void updateListItem() {
// get new item data...
final newItem = ListItem(itemName, units, color);
data.add(newItem); // automatically update the aspect-related-widget
}
rx variable of type int
, double
, num
, string
, bool
, list
, map
, set
are provided by the package.
See also RxInt class,
RxList class,
RxList.add
4. Access the underlying value directly #
Access the underlying value directly by .value
.
MyModel.dart
var int1 = 0.rx;
void updateInt1() {
// int1 += 1;
/// is the same as
int1.value += 1; // automatically update the aspect-related-widget
}
5. Update the value by call style #
MyModel.dart
var _foo = 1.rx;
set foo(int value) {
_foo(value); // update rx variable by call() style
/// is the same as
// _foo = value;
/// is the same as
// _foo.value = value;
}
6. Manually publish an aspect #
MyModel.dart
int manuallyInt = 0;
void manuallyPublishDemo(int value) {
manuallyInt = value;
publish('manuallyInt'); // manually publish aspect of 'manuallyInt'
}
7. Manually publish multiple aspects #
Putting aspects in a list to publish multiple aspects.
MyModel.dart
int _foo = 0;
int _bar = 0;
void increaseBoth() {
_foo += 1;
_bar += 1;
publish(['foo', 'bar']); // manually publish multiple aspects in a list
}
8. Manually publish all aspects #
Publish null value to publish all aspects of the model.
MyModel.dart
void increaseAll() {
//...
publish(); // manually publish all aspects of the model
}
9. Future publish #
Use with an async method, for example,
MyModel.dart
int int1 = 0.rx;
Future<void> futureInt1() async {
await Future.delayed(const Duration(seconds: 1));
int1 += 1; // int1 is an rx variable, it'll automatically update the aspect-related-widget when updated
}
10. Rebuild only once per frame #
Rebuild only once per frame for the same aspect. InheritedModel use Set
to accumulate aspects, thus the same aspect only cause the related widget to rebuild once.
Following code only cause the aspect-related-widget to rebuild once.
MyModel.dart
int int1 = 0.rx;
void incermentInt1() async {
int1 += 1; // int1 is an rx variable, it'll automatically update the aspect-related-widget when updated
publish('int1'); // manually publish 'int1'
publish('int1'); // manually publish 'int1', again
// only cause the aspected-related-widget to rebuild once per frame
}
11. Writing model helper #
You can write model helpers to simplified the typing, for example,
MyModel.dart - helper of MyModel
/// Helper function of MyModel
MyModel getMyModel(BuildContext context) {
return Host.getInheritOfModel<MyModel>(context);
}
Subscriber<MyModel> subMyModel({Key key, Object aspects,
@required CreaterOfSubscriber<MyModel> create}) {
return aspects.subModel<MyModel>(key: key, create: create);
}
extension MyModelHelperT<T> on T {
Subscriber<MyModel> subMyModel({Key key,
@required CreaterOfSubscriber<MyModel> create}) {
return Subscriber<MyModel>(key: key, aspects: this, create: create);
}
}
ListModel.dart - helper of ListModel
/// Helper function of ListModel
ListModel getListModel(BuildContext context) {
return Host.getInheritOfModel<ListModel>(context);
}
Subscriber<ListModel> subListModel({Key key, Object aspects,
@required CreaterOfSubscriber<ListModel> create}) {
return aspects.subModel<ListModel>(key: key, create: create);
}
extension ListModelHelperT<T> on T {
Subscriber<ListModel> subListModel({Key key,
@required CreaterOfSubscriber<ListModel> create}) {
return Subscriber<ListModel>(key: key, aspects: this, create: create);
}
}
See also mediator_helper.dart for package helper.
12. Get the model #
To get the model, for example, getting MyModel
,
original form
final model = Host.getInheritOfModel<MyModel>(context);
by package helper of context extension
final model = context.getModel<MyModel>();
by model helper on your own
final model = getMyModel(context);
Get current triggered frame aspects of the model. See also AllListener@main.dart.
final model = context.getModel<MyModel>();
final aspects = model.frameAspect;
13. Subscribe an aspect #
For example, listen to a String
type aspect 'int1'
of class <MyModel>
.
original form
class Int1Listener extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Subscriber<MyModel>(
aspects: 'int1',
create: (context, model) {
return Text('Int1 is ${model.int1}');
},
);
}
}
with helper
class Int1Listener extends StatelessWidget {
@override
Widget build(BuildContext context) {
// return 'int1'.subMyModel( // with model helper on your own
return 'int1'.subModel<MyModel>( // with package helper
create: (context, model) {
return Text('Int1 is ${model.int1}');
},
);
}
}
14. Subscribe multiple aspects #
Putting aspects in a list to subscribe multiple aspects.
original form
class TwoListener extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Subscriber<MyModel>(
aspects: ['int1', 'star'],
create: (context, model) {
return Text('Int1 is ${model.int1} and Star is ${model.star}');
},
);
}
}
with helper
class TwoListener extends StatelessWidget {
@override
Widget build(BuildContext context) {
// return ['int1', 'star'].subMyModel( // with model helper on your own
return ['int1', 'star'].subModel<MyModel>( // with package helper
create: (context, model) {
return Text('Int1 is ${model.int1} and Star is ${model.star}');
},
);
}
}
15. Subscribe all aspects #
Provide no aspects parameter, or use null as aspect to listen to all aspects.
See also AllListener@main.dart.
original form
class AllListener extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Subscriber<MyModel>(
// aspects: , // no aspects parameter means listen to all aspects
create: (context, model) {
final aspects = model.frameAspect;
final str = aspects.isEmpty ? '' : '$aspects received';
return Text(str);
},
);
}
}
with helper
class AllListener extends StatelessWidget {
@override
Widget build(BuildContext context) {
// return null.subMyModel( // with model helper on your own
return null.subModel<MyModel>( // with package helper
create: (context, model) {
final aspects = model.frameAspect;
final str = aspects.isEmpty ? '' : '$aspects received';
return Text(str);
},
);
}
}
16. Subscribe with enum aspects #
You can use enum
as aspect type, for example, first, define the enum.
ListModel.dart
enum ListEnum {
ListUpdate,
}
Then everything is the same as String
type aspect, just to replace the String
with enum
.
See also ListItemView@main.dart.
original form
class ListItemView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Subscriber<ListModel>(
aspects: ListEnum.ListUpdate,
create: (context, model) {
//...
});
}
}
with helper
class ListItemView extends StatelessWidget {
@override
Widget build(BuildContext context) {
// return ListEnum.ListUpdate.subListModel(create: (context, model) { // with model helper on your own
return ListEnum.ListUpdate.subModel<ListModel>(create: (context, model) { // with package helper
//...
});
}
}
17. Manage rx aspects - Chain react aspects #
Chain react aspects: #
Supposed you need to rebuild a widget whenever a model variable is updated, but it has nothing to do with the variable. Then you can use chain react aspects.
For example, to rebuild a widget whenever str1
of class <MyModel>
is updated, and chained by the aspect 'chainStr1'
.
MyModel.dart
final str1 = 's'.rx..addRxAspects('chainStr1'); // to chain react aspects
main.dart
int httpResCounter = 0;
class ChainReactListener extends StatelessWidget {
Future<int> _futureHttpTask() async {
await Future.delayed(const Duration(milliseconds: 0));
return ++httpResCounter;
}
@override
Widget build(BuildContext context) {
return 'chainStr1'.subModel<MyModel>(create: (context, model) {
return FutureBuilder(
future: _futureHttpTask(),
initialData: httpResCounter,
builder: (BuildContext context, AsyncSnapshot snapshot) {
Widget child;
if (snapshot.hasData) {
child = Text('str1 chain counter: $httpResCounter');
} else {
child = Text('str1 init counter: $httpResCounter');
}
return Center(child: child);
},
);
});
}
}
Then whenever str1
of class <MyModel>
updates, the ChainReactListener
rebuild automatically.
Manage rx aspects: #
Add aspects to the rx variable:
- add an aspect:
rxVar.addRxAspects('chained-aspect')
- add multiple aspects:
rxVar.addRxAspects(['chained-as1', 'chained-as2'])
- add aspects from another rx variable:
rxVar.addRxAspects(otherRxVar)
- broadcast to the model:
rxVar.addRxAspects()
Remove aspects from the rx variable:
- remove an aspect:
rxVar.removeRxAspects('chained-aspect')
- remove multiple aspects:
rxVar.removeRxAspects(['chained-as1', 'chained-as2'])
- remove aspects from another rx variable:
rxVar.removeRxAspects(otherRxVar)
- don't broadcast to the model:
rxVar.removeRxAspects()
Retain aspects in the rx variable:
- retain an aspect:
rxVar.retainRxAspects('chained-aspect')
- retain multiple aspects:
rxVar.retainRxAspects(['chained-as1', 'chained-as2'])
- retain aspects from another rx variable:
rxVar.retainRxAspects(otherRxVar)
Clear all rx aspects:
rxVar.clearRxAspects()
18. Implement custom rx class #
If you need to write your own rx class, see custom_rx_class.dart for example.
19. Aspect type #
- Widget aspects - aspects that the widget subscribe with.
- Frame aspects - aspects that will be updated in this UI frame.
- Registered aspects - aspects that the model has been registered to.
- Rx aspects - aspects that the rx variables are attached. Once the rx variable gets updated, the aspects will be published.
Changelog #
Please see the Changelog page.