flutter_mediator 1.0.0+3 copy "flutter_mediator: ^1.0.0+3" to clipboard
flutter_mediator: ^1.0.0+3 copied to clipboard

outdated

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 #

Pub

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.

Flutter Widget of the Week: InheritedModel Explained

Setting up #

Add the following dependency to pubspec.yaml of your flutter project:

dependencies:
  flutter_mediator: "^1.0.0+3"

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: #

1-1. Extend the model from Publisher
  1-2. Declare rx variables with .rx, which will automatically rebuild the aspect-related-widget when updated.
  1-3. Implement the update methods.

For example,

/// 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: #

  1. Subscribe the widget with one or multiple aspects.

    • Subscribe one aspect:
      aspect.subModel<Model>((context, model) {/*create method*/})
    • Subscribe multiple aspects: (place aspects in a list)
      [a1, a2].subModel<Model>((context, model) {/*create method*/})
    • Broadcast to all aspects of the model: (null aspect to broadcast)
      null.subModel<Model>((context, model) {/*create method*/})

    Place the subscriber in the widget tree then any rx variables inside the create method will automatically rebuild the aspect-related-widget when updated. (triggered by getter and setter)

    For example, subscribe the widget with aspect of String 'int1' of class <MyModel>

  • simple form - create the subscriber directly
'int1'.subModel<MyModel>((context, model) => Text('${model.int1}')),
  • class form - wrap the subscriber in a class
class Int1Subscriber extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return 'int1'.subModel<MyModel>((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: #

  1. Place the controller 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: #

  1. Place 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! #

  1. Then whenever rx variables updates, the aspect-related-widget will rebuild automatically!


These steps can help you in most situations. The following details 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 #

  1. Single model - host
  2. Multiple models - host
  3. Automatically update the widget with rx variable - Publisher
  4. Access the underlying value directly of rx variable - Publisher
  5. Update the value by call style of rx variable - Publisher
  6. Manually publish an aspect of the model - Publisher
  7. Manually publish multiple aspects of the model - Publisher
  8. Manually publish all aspects - broadcasting of the model - Publisher
  9. Future publish of the model - Publisher
  10. Rebuild only once per frame for the same aspect - Publisher
  11. Writing model helper - Publisher
  12. Get the model - Subscriber
  13. Subscribe an aspect of the model - Subscriber
  14. Subscribe multiple aspects of the model - Subscriber
  15. Subscribe all aspects of the model - Subscriber
  16. Subscribe with enum aspects of the model - Subscriber
  17. Manage rx aspects - Chain react aspects - advance topic
  18. Implement custom rx class - advance topic
  19. Aspect type - terminology

1. Single model #

void main() {
  runApp(
    Host(
      model: AppModel(), // model extends from Publisher
      child: MyApp(),
    ),
  );
}

back to detail


2. Multiple models #

MultiHost.create1 to MultiHost.create9 are provided by the package.
You can add more MultiHost.createN methods, 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(),
    ),
  );
}

back to detail


3. Automatically update the widget #

.rx wraps the variable into a rx variable, which will automatically rebuild the aspect-related-widget when updated

rx 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
}

rx 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

back to detail


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
}

back to detail


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;
}

back to detail


6. Manually publish an aspect #

Use publish() method of class Publisher to manually publish an aspect.

/// MyModel.dart

int manuallyInt = 0;
void manuallyPublishDemo(int value) {
  manuallyInt = value;
  publish('manuallyInt'); // manually publish aspect of 'manuallyInt'
}

back to detail


7. Manually publish multiple aspects #

Place 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
}

back to detail


8. Manually publish all aspects - broadcasting #

Publish null value to publish all aspects of the model.

/// MyModel.dart

void increaseAll() {
  //...
  publish(); // manually publish all aspects of the model
}

back to detail


9. Future publish #

Use rx variables within 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
}

back to detail


10. Rebuild only once per frame #

InheritedModel uses Set to accumulate aspects thus the same aspect only causes the related widget to rebuild once for the same aspect.
The following code only causes 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
}

back to detail


11. Writing model helper #

You can write model helpers to simplified the typing, for example,

/// Helper function of MyModel
MyModel getMyModel(BuildContext context) {
  return Host.getInheritOfModel<MyModel>(context);
}

Subscriber<MyModel> subMyModel(CreaterOfSubscriber<MyModel> create,
    {Key key, Object aspects}) {
  return Subscriber<MyModel>(key: key, aspects: aspects, create: create);
}

extension MyModelHelperT<T> on T {
  Subscriber<MyModel> subMyModel(CreaterOfSubscriber<MyModel> create,
      {Key key}) {
    return Subscriber<MyModel>(key: key, aspects: this, create: create);
  }
}
/// Helper function of ListModel
ListModel getListModel(BuildContext context) {
  return Host.getInheritOfModel<ListModel>(context);
}

Subscriber<ListModel> subListModel(CreaterOfSubscriber<ListModel> create,
    {Key key, Object aspects}) {
  return Subscriber<ListModel>(key: key, aspects: aspects, create: create);
}

extension ListModelHelperT<T> on T {
  Subscriber<ListModel> subListModel(CreaterOfSubscriber<ListModel> create,
      {Key key}) {
    return Subscriber<ListModel>(key: key, aspects: this, create: create);
  }
}

See also mediator_helper.dart for package helper.

back to detail


12. Get the model #

To get the model, for example, getting MyModel,

  • original form
final model = Host.getInheritOfModel<MyModel>(context);
  • package helper of context extension
final model = context.getModel<MyModel>();
  • model helper on your own
final model = getMyModel(context);

Get current triggered frame aspects of the model. See also AllSubscriber@main.dart.

final model = context.getModel<MyModel>();
final aspects = model.frameAspect;

back to detail


13. Subscribe an aspect #

For example, subscribe to a String aspect 'int1' of class <MyModel>.

  • original form within a class
class Int1Subscriber extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Subscriber<MyModel>(
      aspects: 'int1',
      create: (context, model) {
        return Text('Int1 is ${model.int1}');
      },
    );
  }
}
  • with helper
class Int1Subscriber extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // return 'int1'.subMyModel((_, m) => Text('${m.int1}')); // simple form
    // return 'int1'.subMyModel(     // with model helper on your own
    return 'int1'.subModel<MyModel>( // with package helper
      (context, model) {
        return Text('Int1 is ${model.int1}');
      },
    );
  }
}
  • simple form
'int1'.subModel<MyModel>((context, model) => Text('Int1 is ${model.int1}')),

back to detail


14. Subscribe multiple aspects #

Place aspects in a list to subscribe multiple aspects.

  • original form within a class
class TwoSubscriber 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}',
          softWrap: true,
          textAlign: TextAlign.center,
        );
      },
    );
  }
}
  • with helper
class TwoSubscriber 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
      (context, model) {
        return Text(
          'Int1 is ${model.int1} and Star is ${model.star}',
          softWrap: true,
          textAlign: TextAlign.center,
        );
      },
    );
  }
}
  • simple form
['int1', 'star'].subModel<MyModel>(
  (context, model) => Text(
    'Int1 is ${model.int1} and Star is ${model.star}',
    softWrap: true,
    textAlign: TextAlign.center,
  ),
),

back to detail


15. Subscribe all aspects #

Provide no aspects parameter, or use null as aspect to subscribe to all aspects.
See also AllSubscriber@main.dart.

  • original form within a class
class AllSubscriber extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Subscriber<MyModel>(
      // aspects: , // no aspects parameter means subscribe to all aspects
      create: (context, model) {
        final aspects = model.frameAspect;
        final str = aspects.isEmpty ? '' : '$aspects received';
        return Text(str, softWrap: true, textAlign: TextAlign.center);
      },
    );
  }
}
  • with helper
class AllSubscriber extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // return null.subMyModel(     // with model helper on your own
    return null.subModel<MyModel>( // with package helper
      (context, model) {
        final aspects = model.frameAspect;
        final str = aspects.isEmpty ? '' : '$aspects received';
        return Text(str, softWrap: true, textAlign: TextAlign.center);
      },
    );
  }
}
  • simple form
null.subModel<MyModel>(
  (context, model) {
    final aspects = model.frameAspect;
    final str = aspects.isEmpty ? '' : '$aspects received';
    return Text(str, softWrap: true, textAlign: TextAlign.center);
  },
),

back to detail


16. Subscribe with enum aspects #

You can use enum as aspect, for example, first, define the enum.

/// ListModel.dart
enum ListEnum {
  ListUpdate,
}

Then everything is the same as String aspect, just to replace the String with enum.
See also ListItemView@main.dart.

  • original form within a class
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((context, model) { // with model helper on your own
    return ListEnum.ListUpdate.subModel<ListModel>((context, model) { // with package helper
      //...
    });
  }
}
  • simple form
ListEnum.ListUpdate.subModel<ListModel>((context, model) { ... }),

back to detail


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 ChainReactSubscriber extends StatelessWidget {
  Future<int> _futureHttpTask() async {
    await Future.delayed(const Duration(milliseconds: 0));
    return ++httpResCounter;
  }

  @override
  Widget build(BuildContext context) {
    return 'chainStr1'.subModel<MyModel>((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 widget 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()

back to detail


18. Implement custom rx class #

If you need to write your own rx class, see custom_rx_class.dart for example.

back to detail


19. Aspect type #

  • Widget aspects - aspects that the widget subscribes.
  • Frame aspects - aspects that this UI frame will update.
  • Registered aspects - aspects that the model has registered.
  • Rx aspects - aspects that the rx variable is attached. Once the rx variable gets updated, it will publish these aspects to the host.

back to detail


Changelog #

Please see the Changelog page.


License #

MIT

6
likes
0
pub points
0%
popularity

Publisher

unverified uploader

Flutter mediator is a MVC state management package base on InheritedModel with automatic aspect management to make them simpler, easier, and intuitive to use.

Repository (GitHub)
View/report issues

License

unknown (LICENSE)

Dependencies

flutter

More

Packages that depend on flutter_mediator