Flutter Mediator Persistence

Flutter Mediator pub.dev License Build Status Global Mode + Model Mode
Lite pub.dev License Build Status Global Mode only
Persistence pub.dev License Build Status Lite + Persistence
Example Logins to a REST server with i18n, theming, persistence and state management.

Flutter Mediator Persistence is a super easy state management package with built in persistence capability, using SharedPreference as the persistent storage, InheritedModel as the state management mechanism, and base on the works of Flutter Mediator Lite.



Table of Contents


Setting up

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

dependencies:
  flutter_mediator_persistence: "^1.0.2"

Import flutter_mediator_persistence in files that will be used:

import 'package:flutter_mediator_persistence/mediator.dart';

For help getting started with Flutter, view the online documentation.

Table of Contents

Steps

  1. Declare the watched variable with globalWatch.
    Declare the persistent wathced variable with defaultVal.globalPersist('key')
    Suggest to put the watched variables into a file var.dart and then import it.

  2. Initial the persistent storage with await initGlobalPersist(); and create the host with globalHost(child: MyApp()) at the top of the widget tree.

  3. Create a consume widget with globalConsume or watchedVar.consume to register the watched variable to the host to rebuild it when updating.

  4. Make an update to the watched variable, by watchedVar.value or watchedVar.ob.updateMethod(...).

Table of Contents

Case 1: Int

Step 1: Declare variable in var.dart.

const DefaultLocale = 'en';
//* Declare a persistent watched variable with `defaultVal.globalPersist('key')`
///    int: 0.globalPersist('intKey');
/// double: 0.0.globalPersist('doubleKey');
/// String: ''.globalPersist('StringKey');
///   bool: false.globalPersist('boolKey');
final locale = DefaultLocale.globalPersist('locale');
final themeIdx = 1.globalPersist('themeIdx');

//* Declare the watched variable with `globalWatch(initialValue)`.
final touchCount = globalWatch(0);

Step 2: Initialization in main.dart.

Future<void> main() async {
  //* Step2: Initial the persistent storage.
  await initGlobalPersist();

  runApp(
    //* Step2: Create the host with `globalHost`
    //* at the top of the widget tree.
    globalHost(child: MyApp())
  );
}

Step 3: Create consume widget in example/lib/pages/home_page.dart.

Scaffold(
  appBar: AppBar(title: const Text('Int Demo')),
  body: Column(
    mainAxisAlignment: MainAxisAlignment.center,
    children: [
      const Text('You have pushed the button this many times:'),
      //* Step3: Create a consume widget with
      //* `globalConsume` or `watchedVar.consume` to register the
      //* watched variable to the host to rebuild it when updating.
      globalConsume(
        () => Text(
          '${touchCount.value}',
          style: Theme.of(context).textTheme.headline4,
        ),
      ),
   // ...

Step 4: Implement update function in example/lib/pages/home_page.dart.

FloatingActionButton(
  //* Stet4: Make an update to the watched variable.
  onPressed: () => touchCount.value++,
  tooltip: 'Increment',
  child: const Icon(Icons.add),
  heroTag: null,
),

Table of Contents

Case 2: List

example/lib/pages/list_page.dart

Step 1: Declare variable in var.dart.

//* Step1: Declare the watched variable with `globalWatch` in the var.dart.
//* And then import it in the file.
final data = globalWatch(<ListItem>[]);

Step 3: Create consume widget.

return Scaffold(
  appBar: AppBar(title: const Text('List Demo')),
  //* Step3: Create a consume widget with
  //* `globalConsume` or `watchedVar.consume` to register the
  //* watched variable to the host to rebuild it when updating.
  body: globalConsume(
    () => GridView.builder(
      itemCount: data.value.length,

    // ...

Step 4: Implement update function.

void updateListItem() {
  // ...

  //* Step4: Make an update to the watched variable.
  //* watchedVar.ob = watchedVar.notify() and then return the underlying object
  data.ob.add(ListItem(itemName, units, color));
}

Table of Contents

Case 3: Locale setting with Built in Persistence

Step 1: Declare variable in var.dart.

const DefaultLocale = 'en';
//* Declare a persistent watched variable with `defaultVal.globalPersist('key')`
///    int: 0.globalPersist('intKey');
/// double: 0.0.globalPersist('doubleKey');
/// String: ''.globalPersist('StringKey');
///   bool: false.globalPersist('boolKey');
final locale = DefaultLocale.globalPersist('locale');
final themeIdx = 1.globalPersist('themeIdx');

Step 2-1: Initialization in main.dart.

Future<void> main() async {
  //* Initial the persistent storage.
  await initGlobalPersist();

  runApp(
    //* Step2: Create the host with `globalHost` at the top of the widget tree.
    globalHost(child: MyApp())
  );
}

Step 2-2: Initial i18n in main.dart.

//* Initialize the locale with the persistent value.
localizationsDelegates: [
  FlutterI18nDelegate(
    translationLoader: FileTranslationLoader(
      forcedLocale: Locale(locale.value),
      fallbackFile: DefaultLocale,
      // ...
    ),
    // ...
  ),
],

Step 2-3: Add assets in pubspec.yaml and prepare locale files in the folder

flutter:
  # ...
  assets:
    - assets/images/
    - assets/flutter_i18n/

Step 3: Create consume widget in example/lib/pages/locale_page.dart.

return SizedBox(
  child: Row(
    children: [
      //* Step3: Create a consume widget with
      //* `globalConsume` or `watchedVar.consume` to register the
      //* watched variable to the host to rebuild it when updating.
      //* `watchedVar.consume()` is a helper function to
      //* `touch()` itself first and then `globalConsume`.
      locale.consume(() => Text('${'app.hello'.i18n(context)} ')),
      Text('$name, '),
      //* Or use the ci18n extension
      'app.thanks'.ci18n(context),
      // ...
    ],
  ),
);

Step 4: Implement update function in var.dart.

Future<void> changeLocale(BuildContext context, String countryCode) async {
  if (countryCode != locale.value) {
    final loc = Locale(countryCode);
    await FlutterI18n.refresh(context, loc);
    //* Step4: Make an update to the watched variable.
    //* The persistent watched variable will update the persistent value automatically.
    locale.value = countryCode; // will rebuild the registered widget
  }
}

Table of Contents

Case 4: Scrolling effect

example/lib/pages/scroll_page.dart

Step 1: Declare variable.

//* Declare a persistent watched variable with `defaultVal.globalPersist('key')`
final scrollOffset = 0.0.globalPersist('ScrollOffsetDemo');

Step 3: Create cousume widget.

class CustomAppBar extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    //* Step3: Create a consume widget with
    //* `globalConsume` or `watchedVar.consume` to register the
    //* watched variable to the host to rebuild it when updating.
    return globalConsume(
      () => Container(
        color: Colors.black
            .withOpacity((scrollOffset.value / 350).clamp(0, 1).toDouble()),
        // ...
      ),
    );
  }
}

Step 4: Initialize the initial offset and Add an offset change listener.

class _ScrollPageState extends State<ScrollPage> {
  //* Step4: Initialize the scroll offset with the persistent value.
  final _scrollController =
      ScrollController(initialScrollOffset: scrollOffset.value);

  @override
  void initState() {
    _scrollController.addListener(() {
      //* Step4: Make an update to the watched variable.
      scrollOffset.value = _scrollController.offset;
    });
    super.initState();
  }

Table of Contents

Recap

  • At step 1, globalWatch(variable) creates a watched variable from the variable.
    Declare a persistent watched variable with defaultVal.globalPersist('key').

    Support types are int, double, String, and bool.

///    int: 0.globalPersist('intKey');
/// double: 0.0.globalPersist('doubleKey');
/// String: ''.globalPersist('StringKey');
///   bool: false.globalPersist('boolKey');
  • At step 3, create a consume widget and register it to the host to rebuild it when updating,
    use globalConsume(() => widget) if the value of the watched variable is used inside the consume widget;
    or use watchedVar.consume(() => widget) to touch() the watched variable itself first and then globalConsume(() => widget).

  • At step 4, update to the watchedVar.value will notify the host to rebuild; or the underlying object would be a class, then use watchedVar.ob.updateMethod(...) to notify the host to rebuild.
    watchedVar.ob = watchedVar.notify() and then return the underlying object.

Table of Contents

Persistence

await initGlobalPersist()

Initial the persistent storage.

Use await initGlobalPersist(); in the main() before runApp().

ex.

Future<void> main() async {
  //* Initial the persistent storage.
  await initGlobalPersist();

  runApp(
    //* Create the host with `globalHost` at the top of the widget tree.
    globalHost(child: MyApp())
  );
}

defaultVal.globalPersist('key')

Create a persistent watched variable. Support types are int, double, String, and bool.

ex.

///    int: 0.globalPersist('intKey');
/// double: 0.0.globalPersist('doubleKey');
/// String: ''.globalPersist('StringKey');
///   bool: false.globalPersist('boolKey');
/// ex:
const DefaultLocale = 'en';
final locale = DefaultLocale.globalPersist('locale');
final themeIdx = 1.globalPersist('themeIdx');

initGlobal(child)

Initial the most common case main(), with the [child] widget, e.g.

await initGlobal(MyApp())

is equivalent to

await initGlobalPersist();
runApp(globalHost(child: MyApp()));

ex.

Future<void> main() async {
  await initGlobal(MyApp());
}

getPersistStore()

Return the backend persistent storage, a SharedPreferences instance.

ex.

final prefs = getPersistStore();

persistVar.remove()

Remove the key/value of the persistent watched variable from the persistent storage, and set it's value to the default.

Won't notify the host to rebuild.

persistVar.store(input)

Store the input to the persistent watched variable.

Won't notify the host to rebuild.

Table of Contents

Global Get

Note: Suggest to put the watched variables into a file var.dart and then import it.

globalGet<T>({Object? tag}) to retrieve the watched variable from another file.

  • With globalWatch(variable), the watched variable will be retrieved by the Type of the variable, i.e. retrieve by globalGet<Type>().

  • With globalWatch(variable, tag: object), the watched variable will be retrieved by the tag, i.e. retrieve by globalGet(tag: object).

Table of Contents

Case 1: By Type

//* Step1: Declare the watched variable with `globalWatch`.
final touchCount = globalWatch(0);

lib/pages/locale_page.dart example/lib/pages/locale_page.dart

class LocalePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    //* Get the watched variable by it's [Type] from `../main.dart`
    final mainInt = globalGet<int>();

    return Container(
      // ...
          const SizedBox(height: 25),
          //* `globalConsume` the watched variable from `../main.dart`
          globalConsume(
            () => Text(
              'You have pressed the button at the first page ${mainInt.value} times',
            ),
      // ...

Table of Contents

Case 2: By tag

//* Step1: Declare the watched variable with `globalWatch`.
final touchCount = globalWatch(0, tag: 'tagCount');

lib/pages/locale_page.dart example/lib/pages/locale_page.dart

class LocalePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    //* Get the watched variable by [tag] from `../main.dart`
    final mainInt = globalGet('tagCount');

    return Container(
      // ...
          const SizedBox(height: 25),
          //* `globalConsume` the watched variable from `../main.dart`
          globalConsume(
            () => Text(
              'You have pressed the button at the first page ${mainInt.value} times',
            ),
      // ...

Note

  • Make sure the watched variable is initialized, only after the page is loaded.

  • When using Type to retrieve the watched variable, only the first one of the Type is returned.

Table of Contents

Global Broadcast

  • globalBroadcast(), to broadcast to all the consume widgets.
  • globalConsumeAll(Widget Function() create, {Key? key}), to create a consume widget which will be rebuilt whenever any watched variables changes are made.
  • globalFrameAspects, a getter, to return the updated aspects.
  • globalAllAspects, a getter, to return all the aspects that has been registered.

Table of Contents

Versions

Table of Contents

Example: Logins to a REST server

A boilerplate example that logins to a REST server with i18n, theming, persistence and state management.

Please see the login to a REST server example for details.

Table of Contents


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

Changelog

Please see the Changelog page.


License

Flutter Mediator Persistence is distributed under the MIT License. See LICENSE for more information.

Table of Contents

Libraries

assert
flutter_mediator_persistence
global
host
rx
rx_impl
subscriber