MVC pattern for flutter. Works as state management, dependency injection and service locator.

Overview

MVC pattern for flutter. Works as state management, dependency injection and service locator.

Pub Version Test GitHub stars GitHub license GitHub last commit


Model View Controller

Here's a diagram describing the flow between the state (model), widget (view) and the logic (controller):

Both MomentumController and MomentumModel are abstract classes that needs to be implemented. A pair of model and controller is called a component. MomentumBuilder is simply a widget. This is used to listen to controllers for rebuilds and accessing models to display their values.

Example

If you want to see a full code example that runs. Visit the example tab for more details or you can visit the official webpage. Otherwise, if you only want to see a glimpse of how momentum works, read the Overview and FAQs below.

Advance Example: Listify (clone the repo and run the app, requires Flutter 2.0.0)

Overview

MomentumModel - the data or state. Must be Immutable.

class ProfileModel extends MomentumModel<ProfileController> {
  // ...

  final int userId;
  final String username;

  // ...
}

MomentumBuilder - the view or widget to display the state.

MomentumBuilder(
  controllers: [ProfileController], /// injects both `ProfileController` and `ProfileModel`.
  builder: (context, snapshot) {
    var profileState = snapshot<ProfileModel>(); /// grab the `ProfileModel` using snapshot.
    var username = profileState.username;
    return // some widgets here ...
  }
)

MomentumController - the logic to manipulate the model or state.

class ProfileController extends MomentumController<ProfileModel> {
  // ...

  Future<void> loadProfile() async {
    var profile = await http.get(...);
    // update the model's properties.
    model.update(
      userId: profile.userId,
      username: profile.username,
    );
  }

  // ...
}

FAQs

How to rebuild the widget?

Calling model.update(...) from inside the controller rebuilds all the MomentumBuilders that are listening to it.


How to access the model object?

It is automatically provided by MomentumController for you to use. Inside a controller class, you can access it directly. It's never null.


How to initialize the model or state?

By implementing the T init() method which is required by MomentumController. Like this:

class ShopController extends MomentumController<ShopModel> {

  @override
  ShopModel init() {
    return ShopModel(
      this, // required
      shopList: [],
      productList: [],
    );
  }
}

Can I access the model properties inside my controller?

Of course. The model object is already provided by MomentumController meaning you can also directly access its properties like this:

class ShopController extends MomentumController<ShopModel> {

  bool hasProducts() {
    return model.productList.isNotEmpty;
  }
}

Is there a special setup required for Momentum to run?

Yes, definitely. This is the required setup for Momentum in a flutter app:

void main() {
  runApp(momentum());
}

Momentum momentum() {
  return Momentum(
    child: MyApp(),
    controllers: [
      ProfileController(),
      ShopController(),
    ],
    // and more optional parameters here.
  );
}

Testing

Momentum is highly testable. This is how a basic widget testing for momentum would look like:

void main() {

  testWidgets('should display username', (tester) async {
    var profileCtrl = ProfileController();

    await tester.pumpWidget(
      Momentum(
        child: MyApp(),
        controllers: [profileCtrl],
      ),
    );
    await tester.pumpAndSettle();

    profileCtrl.updateUsername("johndoe");
    await tester.pumpAndSettle(); // ensure rebuilds

    expect(profileCtrl.model.username, "johndoe"); // unit check
    expect(find.text("johndoe"), findsOneWidget); // widget check
  });
}

Or you might not be a fan of widget testing and only want to test your components:

void main() {

  test('should display username', () async {
    var profileCtrl = ProfileController();

    var tester = MomentumTester(
      Momentum(
        controllers: [profileCtrl],
      ),
    );
    await tester.init();

    profileCtrl.updateUsername("johndoe");
    expect(profileCtrl.model.username, "johndoe"); // unit check
  });
}

Other optional features

  • Routing - Navigation system that supports persistence. The app will open the page where the user left off.
  • Event System - For showing dialogs, prompts, navigation, alerts.
  • Persistence State - Restore state when the app opens again.
  • Testing - Tests your widgets and logic. Built-in helper class for unit testing.

Momentum leverages the power of setState(..) and StatefulWidget behind the scenes. The feature Event System uses Stream.

Router issues

  • The router doesn't support named routes yet.
  • The parameter handling for router is slightly verbose. And might be complicated for some. But it works magically.
  • Needs to explicitly implement RouterPage widget in order to handle the system's back button.
  • (FIXED ) The router breaks after hot reload. Only a problem during development but it should work in normal execution.

API Reference

Visit the official webpage of momentum to browse the full api reference, guides, and examples.


Thanks for checking out momentum. I hope you try it soon and don't hesitate to file on issue on github. I always check them everyday.

Comments
  • Model is not initialised on Testing

    Model is not initialised on Testing

    Here are some example of my controllers,

      PreloadController()..config(lazy: false),
      ProfileController(),
    

    Screenshot 2020-07-31 at 6 02 14 PM . But on the testing documentation, it seems that the model should've been initialised.

    Published 
    opened by suhailpcv 35
  • Error on new Flutter

    Error on new Flutter

    momentum-1.3.1/lib/src/momentum_base.dart:99:18: Error: 'Router' is imported from both 'package:flutter/src/widgets/router.dart' and 'package:momentum/src/momentum_router.dart'. var result = Router.getParam(_mRootContext);

    momentum_base.dart:6:1: Error: 'Router' is imported from both 'package:flutter/src/widgets/router.dart' and 'package:momentum/src/momentum_router.dart'. import '../momentum.dart';

    good first issue Published 
    opened by Bilonik 12
  • Feature Request: Add DI between services

    Feature Request: Add DI between services

    In my current project, I have a StorageService, DownloadService and an APIService. More often than not, whatever is downloaded needs to be stored in the database and the API needs to be called to get the bucket URL for downloading the file.

    Would it make sense to be able to access services using getService inside a MomentumService? Or should I stick to injecting them through the bootStrap of a non-lazy controller?

    enhancement Published 
    opened by hwnprsd 11
  • More on Time Travel (undo/redo)

    More on Time Travel (undo/redo)

    Hello @xamantra i have looked at the listify example and the documentation, it looks like time-travel works best with TextFields is there a way it can work for other models

    Imagine a scenario: I add my todos which are in a list just like listify example, but i want to do undo and redo on my todos when i delete a particular todo in my list of todos and not on text fields like what the example listify is doing Is this possible with momentum's time travel?

    opened by DonnC 10
  • bootstrapAsync doesn't affect the appLoader

    bootstrapAsync doesn't affect the appLoader

    As per the documentation, the appLoader widget should be shown till the bootstrapAsync method of a controller finishes executing. But, it does not seem to work.

    The following is my code snippet from main.dart:

        Momentum(
          controllers: <MomentumController<dynamic>>[
            AuthController()..config(lazy: false)
          ],
          services: [StorageService()],
          appLoader: MaterialApp(
            home: Scaffold(
              backgroundColor: Colors.black,
              body: Center(
                child: CircularProgressIndicator(),
              ),
            ),
          ),
          child: MaterialApp(
            debugShowCheckedModeBanner: false,
            theme: ThemeData(
              accentColor: Colors.indigoAccent,
              primaryColor: Colors.green,
              textTheme: GoogleFonts.montserratTextTheme(),
            ),
            home: Root(),
          ),
        ),
    

    The following is the snippet from the controller.dart file

    @override
      Future<void> bootstrapAsync() async {
        await getService<StorageService>().init();
        final token = getService<StorageService>().getAuthToken();
        if (token == null) {
          model.update(
            token: null,
            isAuthenticated: false,
          );
        } else {
          model.update(
            token: token,
            isAuthenticated: true,
          );
        }
        await Future.delayed(const Duration(seconds: 10));
        return super.bootstrapAsync();
      }
    

    I expected a 10 second circular progress bar. But it see the progress bar for about 200 ms.

    opened by hwnprsd 9
  • Add a way (and or option) to allow lazy loading of controller's bootstrapping when called via Momentum.controller<T>()

    Add a way (and or option) to allow lazy loading of controller's bootstrapping when called via Momentum.controller()

    That was kind of a tough title, so let me expand on that.

    I've notice (working on my 2nd project now) that the way I've been using momentum I don't always need to use snapshot data from momentum builder and I don't always need to disable lazy loading of controllers in the momentum configuration. Sometimes I have a screen that just uses a single method from a controller w/out accessing the model data at all and since that's the main usage of momentum builder (outside of managing the state), which means I don't necessarily need to access snapshot when I setup my controllers in the InitMomentumState method 9 times out of 10.

    Suggestion

    Allow a setting or just outright default functionality for when we call Momentum.controller<T>(context) to trigger bootstrap the first time its called when lazy is set to true. So you don't have to pass the controller to controllers property of the momentum builder in the event of not needing to use snapshot for whatever reason.

    enhancement Published 
    opened by exts 8
  • Add support for RouteSettings in the Router & how do we deal w/ screen constructors w/ Routers?

    Add support for RouteSettings in the Router & how do we deal w/ screen constructors w/ Routers?

    Currently there's two ways you would probably want to pass data to a route. Say you have a new todo item that belongs to a category. Maybe you want to create a separate page screen for adding that todo item to said category. Instead of redirecting to an empty constructor you might want to initialize that constructor by passing the category id, but a category isn't required so maybe sometimes you won't add a todo item to a parent id at all and no constructor initialization is needed. The other way would be just passing basic data to the build method using RouteSettings when traveling to another page for instance displaying the results from the previous page's calculations.

    Router.goto(context, AddItemScreen) is the default way to do this with Momentum which is great, simple love it. Maybe there's optional named arguments though in that screen class that takes a value. Currently there's no way to pass data to that. Not sure what the cleanest way to do it would be, but I do know Function.apply is a thing, not sure how you would do it for constructors though.

    void main() {
      var hmm = Function.apply(Test.init, ["hey"]);
      print(hmm.data);
    }
    
    
    class Test {
      String data;
      Test({this.data});
      static Test init(String data) {
        return Test(data: data);
      }
    }
    

    As for RouteSettings, that seems like that could just be passed to the MaterialPageRoute that gets created. I wouldn't mind submitting a PR for RouteSettings support later this week if you don't get to it, but the other constructor stuff is interesting and would be a lovely feature.

    enhancement Published 
    opened by exts 8
  • Async documentation is kind of confusing

    Async documentation is kind of confusing

    I was reading it and it said future builder might not be needed, but no where does it show how you're executing loadingSomeData. I checked the listify example and the source code and there's no magic being applied here.

    Is there something missing from the documentation here: https://xamdev.gq/momentum/#/asynchronous

    My guess was the call was meant to be inside of a initMomentumState before the builder was ran. But you forgot to create the example code for that.

    documentation Published 
    opened by exts 7
  • Question regarding persistance

    Question regarding persistance

    Was reading the docs, does the Router require persistSave to work? Also is persistSave tied up to models as well? If yes to both of these questions, my last question would be what if I didn't want to use persistSave with model data is there a way to only have this work with the Router only?

    opened by exts 6
  • Make controllers in momentum builder optional

    Make controllers in momentum builder optional

    image

    It can already accept an empty list and nothing happens when you don't pass anything but the IDE complains; Why not just make it optional because sometimes you might instantiate the whole controller in a momentum state object which makes the call to snapshot almost unnecessary at times. I get there's many ways you could set it up, I just notice the way I've been working with momentum I do a lot of initiating of controllers before using them and access what I need w/out actually using snapshot (or making direct calls to model) as much as I would or should.

    I have an example: I have a screen which has only one purpose and that's to add an item to the database, complain on any errors and then redirect to another route. Since it's using TextFields + TextEditingControllers it's not displaying much directly from the model which doesn't warrant a direct call to the model itself from outside of the controller.

    Shouldn't be a breaking change either and probably a two second change since you already handle null controller values after checking the source.

    enhancement Published 
    opened by exts 5
  • Router for not persitant navigation

    Router for not persitant navigation

    Hi, Use Router in service param in Momentum bring confusion with standard Router Navigation Name in Flutter. Because Router require persistSave and persistGet param and I must manage the persistance...

    So if I want use Navigation without persistance, I've 2 options:

    • Use classic Navigator in widget (example: called in onPressed method)

    If I want extract logic in Controller component

    • So, create a third component: MomentumState and use classic Navigator instead Router in .addListener or .listen (with .sendEvent) method for have a context ?

    Thank you for your response :)

    opened by Victor-Dev77 5
  • MomentumRouter.resetWithContext and Momentum.restart not working

    MomentumRouter.resetWithContext and Momentum.restart not working

    I updated my flutter sdk to sdk: ">=2.15.1 <3.0.0", and updated the whole app to be null-safe, but that resulted in the MomentumRouter.resetWithContext and Momentum.restart not working. The issue after countless tries seems to be the .resetWithContext function, because upon calling those functions to reset initial page say upon login, or wanting to do Navigate and Replace, the app clearly restarts, but goes back to app initial or whatever was your welcome or first page thereby not persisting login.

    I have resorted to use MomentumRouter.goto for the time being just so the app can login since MomentumRouter.resetWithContext and Momentum.restart won't allow navigation to new page from Login Page, but the issue of persisting login is still an issue. Before the sdk update they were working as expected. As the video shows the last try that gets in the app uses MomentumRouter.goto, the other first tries was using MomentumRouter.resetWithContext and Momentum.restart.

    Screenshot (308)

    https://user-images.githubusercontent.com/54586967/152695890-073ca4e1-055d-487b-b018-3f4d0ee0731a.mp4

    opened by auric-co 0
  • Routing interceptor - initial page based on authorization logic

    Routing interceptor - initial page based on authorization logic

    Hi! Is there any way to handle some kind of "guard" for routing? How to handle token expiration? Right now the only solution is to check whether the token has expired on each page? Should I be able to override MomentumRouter.getActivePage(context), to apply my own logic for ActivePage?

    enhancement 
    opened by troncomputers 2
  • Improve code doc

    Improve code doc

    Unfortunately, it isn't totally clear what the parameters do.

    It would be great to explain it better in the code docs.

      /// Configure your app with [Momentum] root widget.
      ///
      /// The parameter `child` is not required for *unit testing*.
      factory Momentum({
        Key? key,
        Future<void> Function()? initializer,
        Widget? child,
        Widget? appLoader,
        required List<MomentumController> controllers,
        List<MomentumService>? services,
        ResetAll? onResetAll,
        bool? disabledPersistentState,
        bool? enableLogging,
        int? maxTimeTravelSteps,
        bool? lazy,
        int? minimumBootstrapTime,
        BootstrapStrategy? strategy,
        PersistSaver? persistSave,
        PersistGet? persistGet,
        String? testSessionName,
        void Function()? restartCallback,
      })
    
    opened by RicardoRB 0
  • Is this package production ready?

    Is this package production ready?

    Hello, As the title says I am wondering if you'd vouch for this package to be production ready. I have to say that after reading through the documentation I am really impressed with your work and Momentum seems to be what I was looking for , for a long time. However I do not really like using the dev branch inside a serious project(the problem with Route name ambiguity) and the fixed version doesn't seem to be on pub yet. Thank you for an answer and keep up the great work !

    opened by dkohout 4
  • Named Routing

    Named Routing

    Flutter for web matches the names of the routes to the URL in the browser. I didn't find any information how to use named routes in Momentum. Does it support it?

    opened by vasil-boshnyakov 1
Owner
xamantra
Traveling to Hanamura
xamantra
Flutter getx template - A Flutter Template using GetX package for State management, routing and Dependency Injection

Flutter GetX Template (GetX, Dio, MVVM) This Flutter Template using GetX package

Tareq Islam 6 Aug 27, 2022
Arisprovider - A mixture between dependency injection (DI) and state management, built with widgets for widgets

A mixture between dependency injection (DI) and state management, built with wid

Behruz Hurramov 1 Jan 9, 2022
Movie App used MVC pattern, Getx for state managment, sqflite for backend database

movie_app A new Flutter application. Getting Started This project used MVC pattern, Getx for state managment, sqflite for backend database, firebase/W

HM Badhon 3 Sep 13, 2022
Get It - Simple direct Service Locator that allows to decouple the interface from a concrete implementation and to access the concrete implementation from everywhere in your App. Maintainer: @escamoteur

❤️ Sponsor get_it This is a simple Service Locator for Dart and Flutter projects with some additional goodies highly inspired by Splat. It can be used

Flutter Community 1k Jan 1, 2023
Another way to build Flutter applications for mobile, web and desktop using the powerful of MVC Design Pattern.

Karee Another way to build Flutter applications for mobile, web and desktop using the powerful of MVC Design Pattern. + = About Karee Karee is a frame

@LeCode 44 Sep 29, 2022
Youtube Clone App with GetX & Youtube Data Api V3 [MVC pattern]

Youtube App ✍?? Youtube App Clone with GetX & Youtube Data Api V3 [MVC pattern] ????‍?? Youtube App app is open-source Youtube App app for Android & i

null 6 Nov 6, 2022
A simple dependency injection plugin for Flutter and Dart.

A super simple dependency injection implementation for flutter that behaviours like any normal IOC container and does not rely on mirrors

Jon Samwell 91 Dec 13, 2022
A Dart dependency injection library aimed to be flexible, predictable and easy to use.

dino Dino is a Dart dependency injection library with optional code generation. It was inspired by DI in .NET and aimed to be flexible, predictable an

null 3 Dec 20, 2022
[Flutter SDK V.2] - Youtube Video is a Flutter application built to demonstrate the use of Modern development tools with best practices implementation like Clean Architecture, Modularization, Dependency Injection, BLoC, etc.

[Flutter SDK V.2] - Youtube Video is a Flutter application built to demonstrate the use of Modern development tools with best practices implementation like Clean Architecture, Modularization, Dependency Injection, BLoC, etc.

R. Rifa Fauzi Komara 17 Jan 2, 2023
Raden Saleh 20 Aug 12, 2023
Clean Architecture + TDD + SOLID + Dependency Injection + GitFlow + Mobx

Clean Architecture + TDD + SOLID + Dependency Injection + GitFlow + Mobx Flutter Interview Challenge This app is part of an interview process. It took

Vinicius Souza 13 Dec 28, 2022
Petrus Nguyễn Thái Học 193 Dec 29, 2022
Practice code of simple flutter app to explain how provider works as across the widget state management.

vdo A new Flutter project. Getting Started This project is a starting point for a Flutter application. A few resources to get you started if this is y

Syed Uzair 9 Nov 7, 2022
Home-Service-App - Home Service App Built With Flutter

Home-Service-App Home Service App Sample Images

Justin Roy 2 Sep 4, 2022
Our application, MyArmyPal serves to be an all in one service for our service men.

Our application, MyArmyPal serves to be an all in one service for our service men. It seeks to provide convenience and useful features just one tap away. Its main features include an IPPT Calculator, reservist checklist, customized IPPT training plan according to the user's current fitness level and a canteen order pick up service in all army camps. We are also implementing an anytime Eliss system using computer vision for users to check on their push up form easily.

Poh Wei Pin 3 Jun 17, 2022
Excess Food Locator helps food providers connect with food distributing NGOs.

Excess Food Locator Excess Food Locator helps people having excess food connect with other resources and distributors. This application is based on a

Pranav Kale 6 Nov 6, 2022
This is template toolkit fasten your project setup within a minute. The toolkit is based on MVC+S structure.

BWeird Flutter Toolkit! Hi! From a Weirder Flutter has been great on mobile development and I took this opportunity to make it even greater with templ

Setak Varaman !!!! 6 Aug 22, 2021
Flutter bloc example - An app for State management using BLoC pattern in Flutter

Flutter BLoC My first app for State management using BLoC pattern in Flutter Col

Harshil Patel 1 Jun 16, 2022
Easy Form State Management using BLoC pattern

?? Dart and Flutter Package ?? Easy Form State Management using BLoC pattern ?? Wizard/stepper forms, asynchronous validation, dynamic and conditional fields, submission progress, serialization and more! ??

GiancarloCode 406 Jan 8, 2023