flutter_mediator 1.0.0 flutter_mediator: ^1.0.0 copied to clipboard
flutter mediator is a MVC state management package base on InheritedModel with aspects to make them simpler, easier, and intuitive to use
Flutter Mediator #
Flutter mediator is a MVC state management package base on InheritedModel with aspects 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
- place on top of the widget tree, dispatch aspects using InheritedModel
Setting up #
Add the following dependency to pubspec.yaml of your flutter project:
dependencies:
flutter_mediator: "^1.0.0"
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
, and 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
#
- In the Widget tree, execute the update method.
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!
Example #
You can find the example in the example folder.
Try once, then 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 more than one aspect 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 model value - Subscriber
- subscribe a aspect of the model - Subscriber
- subscribe more than one aspect of the model - Subscriber
- subscribe all aspects of the model - Subscriber
- subscribe with enum aspects of the model - Subscriber
- writing custom rx class
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 more than one aspect #
Putting aspects in a list to publish more than one aspect.
MyModel.dart
int _foo = 0;
int _bar = 0;
void increaseBoth() {
_foo += 1;
_bar += 1;
publish(['foo', 'bar']); // manually publish aspects in a list
}
8. manually publish all aspects #
Publish null value to publish all aspects of the model.
MyModel.dart
void increaseAll() {
//...
publish(); // publish all aspects of the model
}
9. future publish #
Use with a future method, for example,
MyModel.dart
int int1 = 0.rx;
Future<void> futureInt1() async {
await Future.delayed(const Duration(seconds: 1));
int1 += 1; // int1 is Rx, automatically update the aspect-related-widget
}
10. rebuild only once per frame #
Rebuild only once per frame for the same aspect.
Following code only cause the aspect-related-widget to rebuild once.
MyModel.dart
int int1 = 0.rx;
void incermentInt1() async {
int1 += 1; // int1 is Rx, automatically update the aspect-related-widget
publish('int1'); // manually publish 'int1'
publish('int1'); // manually publish 'int1', again
// only cause the related widgets to rebuild once per frame
}
11. writing model helper #
You can write model helpers to simplified the typing. For example,
MyModel.dart
/// 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 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);
}
}
12. get model value #
To get the model, for example, getting MyModel
,
original form
final model = Host.getInheritOfModel<MyModel>(context);
by package helper of context extension
(See mediator_helper.dart for package helper.)
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 a 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 more than one aspect #
Putting aspects in a list to subscribe more than one aspect.
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 == null ? '' : '$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 == null ? '' : '$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. writing custom rx class #
If you need to write your own rx class, see custom_rx_class.dart for example.
Changelog #
Please see the Changelog page.