momentum 1.2.7

  • Readme
  • Changelog
  • Example
  • Installing
  • 89

A super-powerful flutter state management library inspired with MVC pattern with very flexible dependency injection.

Pub Version CI documentation popularity score maintenance health GitHub stars style: effective dart GitHub license GitHub last commit



Features #

  • Very flexible Dependency Injection to easily instantiate any dependencies once and reuse multiple times across the app.
  • Persistence support for states and routing. Use any storage provider.
  • Time travel (undo/redo) support in one line of code out of the box.
  • Optional Equatable support. (Improves time travel).
  • Immutable states/models. There's only one way to rebuild a widget.
  • You can reset a state or all of the states.
  • Skip rebuilds. Widget specific.
  • Easy to use Event System for sending events to the widgets. For showing dialogs/snackbars/alerts/navigation/etc.
  • Momentum doesn't have any dependencies so it increases compatibility in other platforms.
  • Supports older versions of flutter.

Core Concepts #

  • Momentum only uses setState(...) under the hood.
  • The method model.update(...) is the setState of momentum.
  • Modular project structure because of the component system (MomentumController + MomentumModel).
  • Everything can be reusable from widgets, services, data, state to logic.
  • Everything is in the widget tree.

Preview #

In this image the process was like this:

  • Open the app (Home Page).
  • Go to Add New List page.
  • Input some data.
  • Close and Terminate on task view.
  • Reopen the app again.

And magic happens! All the inputs were retained and not just that but also including the page where you left off. Navigation history is also persisted which means pressing the system back button will navigate you to the correct previous page.

persistent preview

Dark Mode

This theming is done manually using momentum.

dark mode

Source Code for this Example App

This example app shows how powerful momentum is.

Quick Start #

You only have to install one package and momentum doesn't have any peer dependencies.

Create #

To get started, flutter create an app. Name it however you want.

Installing #

  1. Add this to your package's pubspec.yaml file:

     dependencies:
       momentum: ^1.2.7
    

    It is not recommended to use the one from GitHub because the changes there are subject to breaking changes on future pushes to the repository.

  2. You can install this package from the command-line:

     flutter pub get
    

    Alternatively, your editor might support flutter pub get.

  3. Now in your Dart code, you can use:

     import 'package:momentum/momentum.dart';
    

    You only have to import this one file alone and you'll be able to use all momentum API.

Counter App Example #

Copy this example counter app code and run it:

import 'package:flutter/material.dart';
import 'package:momentum/momentum.dart';

void main() {
  runApp(
    Momentum(
      controllers: [CounterController()],
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Momentum State Management',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: HomeWidget(),
    );
  }
}

class CounterController extends MomentumController<CounterModel> {
  @override
  CounterModel init() {
    return CounterModel(
      this,
      value: 0,
    );
  }

  void increment() {
    var value = model.value; // grab the current value
    model.update(value: value + 1); // update state (rebuild widgets)
    print(model.value); // new or updated value
  }
}

class CounterModel extends MomentumModel<CounterController> {
  CounterModel(
    CounterController controller, {
    this.value,
  }) : super(controller);

  final int value;

  @override
  void update({
    int value,
  }) {
    CounterModel(
      controller,
      value: value ?? this.value,
    ).updateMomentum();
  }
}

class HomeWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Momentum Counter'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            MomentumBuilder(
              controllers: [CounterController],
              builder: (context, snapshot) {
                var counter = snapshot<CounterModel>();
                return Text(
                  '${counter.value}',
                  style: Theme.of(context).textTheme.headline4,
                );
              },
            ),
          ],
        ),
      ),
      // we don't need to rebuild the increment button, we can skip the MomentumBuilder
      floatingActionButton: FloatingActionButton(
        onPressed: Momentum.controller<CounterController>(context).increment,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}
  • Inside main() method Momentum is set as the root widget of the app.
  • The CounterController is instantiated in controllers parameter.
  • Inside the CounterController there is an increment() method that updates the value. It calls the method model.update(...) which will rebuild the widget.
  • The CounterModel is where the props are defined and currently has the value property.
  • The HomeWidget uses MomentumBuilder which is used for displaying the model properties to the screen. You can call model.update(...) to rebuild this widget.

Please Note #

1.2.7 #

1.2.6 #

  • improve Momentum.restart function.

1.2.5 #

  • fixed image links in docs

1.2.4 #

  • The pub.dev package now excluded docs/ and test/ folders to speed up flutter pub get for environment with limited internet access.

1.2.3 #

  • Added testMode parameter on Momentum for easier testing on project level.
  • Fixed bugs related to testing asynchronous code.
  • Added more internal tests.

1.2.2 #

  • Fixed Router error that happens when persistSave is not specified.
  • Fixed MomentumError doesn't override toString() so detailed error logs are not printed on the console.

1.2.1 #

  • Issue #7 - Added new capability to globally disable persistent state for all models. Docs here.
  • skipPersist(), by default now returns null. Not a breaking change because this method is only meant to be used by momentum internally.

1.2.0 #

  • critical: fix snapshot<T>() where it throws a compile-time type error in older versions of flutter.
  • added and refactored test methods.
  • added new internal tests.

1.1.9 - Critical #

  • Fixed #5
  • Improve types system for dependency injection
  • Fix extending controllers that cause bug for snapshot<T>(). Click here for the docs.
  • Added internal tests.

1.1.8 - Important #

CI

  • Changed license to BSD-3
  • critical fix: reset() bug.
  • fix config check bug in the "persistSave" parameter.
  • (test) fixed dependOn
  • added public methods for testability.
  • Added internal tests.

TODO

  • Add more internal tests.
  • Find a workaround for issue #5
  • Write docs for project-level testing.
  • Refactor tests description/names.

1.1.7 - Major Update #

Major changes:

  • Persistent state.
  • Persistent navigation/routing.
  • Equatable support.
  • sendEvent<T>(...) and .listen<T>(...).
  • Official Documentation: https://xamdev.gq/momentum/#/

Minor changes:

  • Momentum.controller<T> is now the recommended way of getting a controller using context instead of Momentum.of<T>.
  • Momentum.service<T> is now the recommended way of getting a service using context instead of Momentum.getService<T>.
  • enableLogging now defaults to false.

✔ No breaking changes.


1.1.6 #

  • New function added: Momentum.restart(...)
    • Restart the app with a new momentum instance.
    • To easily implement, in you main.dart create a method that returns Momentum instance.
      void main() {
        runApp(momentum()); // call the method here instead of instantiating the Momentum.
      }
      
      Momentum momentum() {
        return Momentum(
          child: MyApp(),
          controllers: [
            LoginController(),
            SessionController()..config(lazy: false, enableLogging: true),
            TimerController()..config(maxTimeTravelSteps: 5),
            TimeclockController(),
            AppController()..config(lazy: false, enableLogging: true),
            SettingsController()..config(lazy: false),
          ],
          services: [
            Router([
              Login(),
              Home(),
              Settings(),
              LanguageSelection(),
              FontScale(),
            ]),
          ],
          enableLogging: false,
          onResetAll: (context, resetAll) async {
            await Momentum.of<SessionController>(context).clearSession();
            resetAll(context);
            Router.goto(context, Login);
          },
        );
      }
      
    • You can then call Momentum.restart(...) down the tree:
      Momentum.restart(context, momentum()); // call momentum() which is a top level function.
      

1.1.5 #

  • New feature added: Services.

  • Inject anything into momentum and use them down the tree inside widgets or controllers.

  • Example code:

    // main.dart
    Momentum(
      controllers: [...],
      services: [
        ApiService(),
      ],
      child: MyApp(),
      ...
    )
    
    // *.controller.dart
    void loadUser() async {
      model.update(loadingUser: true);
      var apiService = getService<ApiService>();
      var user = await apiService.getUser(); // load data from server. asynchronous
      model.update(loadingUser: false, user: user);
    }
    

1.1.4 #

  • exposed MomentumModel.controller property.

1.1.3 - Breaking Changes #

  • bootstrap() method is now synchronous only.
  • added bootstrapAsync() for separate asynchronous support.
  • For the execution order, bootstrap() gets called first then bootstrapAsync().
  • added detailed logging for both bootstrap() and bootstrapAsync() so you can see what gets executed first and the time it took.

1.1.2 #

  • Fixed health check: Fix lib/src/momentum_base.dart. (-1 points)

1.1.1 #

  • New feature: Asynchronous bootstrap() method now supports loading widget using appLoader parameter on Momentum root widget. If appLoader is not specified a default loading widget will be shown.
    • You have to turn lazy loading off to enable this feature.
    • Lets say one of your controllers implements bootstrap() method asynchronously and it loads a data that takes seconds to finish:
        @override
        void bootstrap() async {
          // assuming we are loading some complex and big data here.
          var appSettings = await apiService.getAppSettings();
          model.update(appSettings: appSettings);
        }
      
    • Now, imagine apiService.getAppSettings() takes 3-5 seconds to load. Before your MyApp() gets loaded, momentum will await this bootstrap method and show a loading widget until it finishes. Means, you can now do anything you want with the bootstrap() method synchronous or asynchronous. It is safe to call model.update(...) in this method.

1.1.0 #

  • Reformatted the whole readme, reduce the number of headings.

1.0.9 #

  • New feature: dontRebuildIf parameter for MomentumBuilder.

    • This method will be called after model.update(...) right before the builder.

    • isTimeTravel is also provided that indicates if the model was updated by time travel methods .backward() or .forward(), returning it directly means you don't want to rebuild if an update was done with the time travel method.

    • Two new properties were also added: MomentumController.prevModel and MomentumController.nextModel which are properties from model history and their meaning is quite obvious. The prevModel is the previous state and nextModel is the next state which will only have a value if you use .backward() method. If you are on the latest snapshot of the model nextModel will be null.

    • Take a look at this example, The widget is displaying a time format HH: mm and TimerController ticks every 500ms for accuracy. We only need to rebuild if minute property is changed, that's where the dontRebuildIf parameter comes.

        MomentumBuilder(
          controllers: [TimerController],
          dontRebuildIf: (controller, isTimeTravel) {
            var timer = controller<TimerController>();
            var prevMinute = timer.prevModel.dateTime.minute;
            var currentMinute = timer.model.dateTime.minute;
            var minuteUnchanged = currentMinute == prevMinute;
            return minuteUnchanged; // don't rebuild the widget if "minute" is unchanged.
          },
          builder: (context, snapshot) {...},
        )
      
    • WARNING: This method is the same as any other builder or build methods, do not call model.update(...) or anything that calls build method, for example, setState(...). You'll get an infinite loop.


1.0.8 #

  • Major update:
    • updated onResetAll to support widget operation before actually resetting all models.
    • improved README added lots of new content.

1.0.7 #

  • fixed typos on the readme.

1.0.6 #

  • updated readme added MomentumState listener section. and also fix typos.

1.0.5 #

  • fix readme not properly displaying in pub.dev

1.0.4 #

  • updated example project to link the correct momentum version from pub.dev.

1.0.3 #

  • updated readme, now most parts are covered.

1.0.2 #

  • added example app.

1.0.1 #

  • updated error message.

1.0.0 #

  • Initial version.

example/README.md

Source Code for this Example App

Preview #

In this image the process was like this:

  • Open the app (Home Page).
  • Go to Add New List page.
  • Input some data.
  • Close and Terminate on task view.
  • Reopen the app again.

And magic happens! All the inputs were retained and not just that but also including the page where you left off. Navigation history is also persisted which means pressing the system back button will navigate you to the correct previous page.

persistent preview

Dark Mode

This theming is done manually using momentum.

dark mode

Source Code for this Example App

This example app shows how powerful momentum is.

Use this package as a library

1. Depend on it

Add this to your package's pubspec.yaml file:


dependencies:
  momentum: ^1.2.7

2. Install it

You can install packages from the command line:

with Flutter:


$ flutter pub get

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

3. Import it

Now in your Dart code, you can use:


import 'package:momentum/momentum.dart';
  
Popularity:
Describes how popular the package is relative to other packages. [more]
79
Health:
Code health derived from static analysis. [more]
100
Maintenance:
Reflects how tidy and up-to-date the package is. [more]
100
Overall:
Weighted score of the above. [more]
89
Learn more about scoring.

We analyzed this package on Jul 8, 2020, and provided a score, details, and suggestions below. Analysis was completed with status completed using:

  • Dart: 2.8.4
  • pana: 0.13.14
  • Flutter: 1.17.5

Analysis suggestions

Package not compatible with SDK dart

Because:

  • momentum that is a package requiring null.

Health suggestions

Format lib/momentum.dart.

Run flutter format to format lib/momentum.dart.

Format lib/src/in_memory_storage.dart.

Run flutter format to format lib/src/in_memory_storage.dart.

Dependencies

Package Constraint Resolved Available
Direct dependencies
Dart SDK >=2.1.0 <3.0.0
flutter 0.0.0
Transitive dependencies
collection 1.14.12 1.14.13
meta 1.1.8 1.2.1
sky_engine 0.0.99
typed_data 1.1.6 1.2.0
vector_math 2.0.8 2.1.0-nullsafety
Dev dependencies
effective_dart ^1.2.1
flutter_test
test_coverage ^0.4.1