states_rebuilder 1.3.1

  • README.md
  • CHANGELOG.md
  • Example
  • Installing
  • Versions
  • 91

states_rebuilder #

A Flutter state management solution that allows you:

  • to separate your User Interface (UI) representation from your logic classes
  • to easily control how your widgets rebuild to reflect the actual state of your application.
  • to inject dependencies using Injector

For State Management see this Medium Article

For Dependency Injection and more elaborate State Management architecture see this example.

This Library provides Four classes and two methods:

  • The StatesRebuilder class. Your logics classes (viewModels) will extend this class to create your own business logic BloC (equally can be called ViewModel or Model).
  • The rebuildStates method. You call it inside any of your logic classes that extends StatesRebuilder. It rebuilds all the mounted 'StateBuilder' widgets. It can filter the widgets to rebuild by tag. this is the signature of the rebuildState:
  rebuildStates([List<dynamic> tags])
  • The StateBuilder Widget. You wrap any part of your widgets with it to add it to listeners list of your logic classes and hence can rebuild it using rebuildState method this is the constructor of the StateBuilder:
  StateBuilder( {
      Key key, 
      dynamic tag, // you define the tag of the state. This is the first way. You can provide a list of tags.
      List<StatesRebuilder> viewModels, // You give a list of the logic classes (BloC) you want this widget to listen to.
      @required (BuildContext, String) → Widget builder,
      (BuildContext, String) → void initState, // for code to be executed in the initState of a StatefulWidget
      (BuildContext, String) → void dispose, // for code to be executed in the dispose of a StatefulWidget
      (BuildContext, String) → void didChangeDependencies, // for code to be executed in the didChangeDependencies of a StatefulWidget
      (BuildContext, String, StateBuilder) → void didUpdateWidget // for code to be executed in the didUpdateWidget of a StatefulWidget
    });

tag is of type dynamic. It can be String (for small projects) or enum member (enums are preferred for big projects).When a list of dynamic tags is provided, states_rebuilder consider it as many tags and will rebuild this widget if any of theses tags are invoked by the rebuildStates method.

  • To extends the state with mixin (practical case is animation), use StateWithMixinBuilder
StateWithMixinBuilder<T>( {
      Key key, 
      dynamic tag, // you define the tag of the state. This is the first way
      List<StatesRebuilder> viewModels, // You give a list of the logic classes (BloC) you want this this widget to listen to.
      @required (BuildContext, String) → Widget builder, 
      @required (BuildContext, String,T) → void initState, // for code to be executed in the initState of a StatefulWidget
      @required (BuildContext, String,T) → void dispose, // for code to be executed in the dispose of a StatefulWidget
      (BuildContext, String,T) → void didChangeDependencies, // for code to be executed in the didChangeDependencies of a StatefulWidget
      (BuildContext, String,StateBuilder, T) → void didUpdateWidget // for code to be executed in the didUpdateWidget of a StatefulWidget,
      (String, AppLifecycleState) → void didChangeAppLifecycleState // 
      @required MixinWith mixinWith
});

Available mixins are: singleTickerProviderStateMixin, tickerProviderStateMixin, AutomaticKeepAliveClientMixin and WidgetsBindingObserver.

  • With rebuildFromStreams method you can control the rebuild of many widgets from single subscription StreamController. You can listen to many streams, merge them and combine them.
Streaming<T, S>({
  List<Stream<T>> streams, //You define a list of streams or
  List<StreamController<T>> controllers, // a list of controllers
  List<T> initialData, // List of initialData. the order of the list is the same as in controller or stream list.
  List<StreamTransformer<dynamic, dynamic>> transforms,  // list of transform to apply on streams.
  (List<AsyncSnapshot<T>>) → S combineFn // The combination function.
  })

 // To add a listener form any of your viewModels :
streaming.addListener(this, ["optional tag"]);

// To get any snapshot from your viewModel:
  AsyncSnapshot<T> get firstSnapshot => streamer.snapshots[0]; // Snapshot of the first stream
  AsyncSnapshot<T> get secondSnapshot => streamer.snapshots[1]; // Snapshot of the second stream
  .
  .
  AsyncSnapshot<T> get secondSnapshot => streamer.snapshots[n]; // Snapshot of the n th stream

// To get the merged result of the streams from your viewModel:
 AsyncSnapshot<T> get mergedSnapshot => streamer.snapshotMerged;

// To get the combined result of the streams from your viewModel:
 AsyncSnapshot<S> get combinedSnapshot => streamer.snapshotCombined;

  • BlocProvider widget. Used to provide your BloCs
   BlocProvider<YourBloc>({
     CounterBloc bloc
     Widget child,
   })
  • Injector widget as dependency injection: to register model and services use Injector the same way you use BlocProvider
  Injector<T>({
    List<() → dynamic> models, // List of models to register
    (BuildContext context, T model) → Widget builder, // The model with type T is the viewModel related to this view. When `rebuildStates()` is called in this model this widget will rebuild.
    () → void dispose, // a custom method to call when Injector is disposed.
    bool disposeModels: false // Whether Injector will automatically call dispose method from the registered models.
  }) 

To get the same instance of the model inside any class use:

  Injector.get<T>([String name]).

Where T is the type of the model and name is optional used if you want to call a named model.

To get a new instance of the model, you use:

  Injector.getNew<T>([String name]).

Model are automatically unregistered when the injector is disposed.

You can declare many Injectors where you want in the widget tree.

Prototype Example for dependency injection #

//Without generic type
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Injector(
      models: [
        () => ModelA(),
        () => ModelB(),
        () => ModelC(Injector.get<ModelA>()),// Directly inject ModelA in ModelC constructor
        () => ModelD(),
       // () => ModelD(),// Not allowed. Model can be registered only once.
        () => [ModelD(),"costumeName"], // To register many model of the same type use this approach
        ],
      builder: (context,model) => MyWidget(), // model is null, because no generic type is given
    );
  }

  //With generic type
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Injector<ModelA>( // `ModelA` should extend `StatesRebuilder`.
      models: [
        () => ModelA(),
        () => ModelB(),
        () => ModelC(Injector.get<ModelA>()),// Directly inject ModelA in ModelC constructor
        () => ModelD(),
       // () => ModelD(),// Not allowed. Model can be registered only once.
        () => [ModelD(),"costumeName"], // To register many model of the same type use this approach
        ],
      builder: (context,model) => MyWidget(), // model is of type `ModelA`. when `rebuildStates()` is called in `ModelA` this widget will rebuild
    );
  }

  // You can get your models from any class provided it is registered before calling it.
  class MyWidget extends StatelessWidget {

    final modelA = Injector.get<ModelA>();
    final modelA1 = Injector.getNew<ModelA>();
    final modelD = Injector.get<ModelD>();
    final modelDNamed = Injector.get<ModelD>("costumeName");


    @override
    Widget build(BuildContext context) {
      return Injector(
        models: [
          () => ModelF(),
        ],
      builder: (_) => AnotherWidget(),
      )
    }
  }

Prototype Example for state management #

your_bloc.dart file:

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


  // enum is preferred over String to name your `tag` for big projects.
  // The name of the enum is of your choice. You can have many enums.

  // -- Conventionally for each of your BloCs you define a corresponding enum.
  // -- For very large projects you can make all your enums in a single file.

  enum YourTagEnum {yourTag1};

  class YourViewModel extends StatesRebuilder{

      var yourVar;

      /// You have two ways:

      /// ************** First way: (tag way) **************

      yourMethod1() {
        // some logic staff;
        yourVar = yourNewValue;
        rebuildStates([YourState.yourTag1]);
      }

      // example of fetching data and rebuilding widgets after obtaining the data
      fetchData1() async {
        await yourRepository.fetchDate();
        rebuildStates([YourState.yourTag1]);
      }

      /// ************** Second way (tag way) **************

      yourMethod2(String tagID) {
        // some logic staff;
        yourVar = yourNewValue;
        rebuildStates([tagID]);
      }

      // example of fetching data and rebuild widgets after obtaining the data
      fetchData2(String tagID) async {
        await yourRepository.fetchDate();
        rebuildStates([tagID]);
      }

      /// ************** Combination of first and second ways **************

      yourMethod3(String tagID) {
        // some logic staff;
        yourVar = yourNewValue;
        rebuildStates([tagID, YourState.yourTag1]);
      }


      /// ************** Rebuild All **************
      yourMethod4() {
        // some logic staff;
        yourVar = yourNewValue;
         // `rebuildStates()` with no parameter: All widgets that are wrapped with
         //`StateBuilder` will rebuild to reflect the new counter value.
         // You get a similar behavior like in ``scoped_model`` or ``provider`` packages

        rebuildStates();
      }
  }

your main.dart file:

  // ************** First way: (tag way) ************** 
  class FirstWay extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
      return Column(
            children: <Widget> [
              StateBuilder(
                tag : YourTagEnum.yourTag1 // you can use just a String "yourTag1",
                blocs : [yourBloc],
                initState: (_)=> yourBloc.fetchData1(),
                builder: (_) => YourChildWidget(yourBloc.yourVar),
            ),
            RaisedButton(
              child: Text("first way"),
              onPressed : yourBloc.yourMethod1,
            )
          ],
      );
    }
  }

    // ************** Second way: (tag way) ************** 
    class SecondWay extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
      return StateBuilder(
              initState: yourBloc.fetchData2,
              builder: (String tagID) => Column(
                    children: <Widget> [
                      YourChildWidget(yourBloc.yourVar),
                      RaisedButton(
                        child: Text("Second way"),
                        onPressed :yourBloc.yourMethod2(tagID),
                      ), 
                    ],
                  ),
              );
    }
  }

1.3.1 #

  • remove rebuildFromStreams.
  • Initial release of Streaming class
  • The builder closure of the Injector takes (BuildContext context, T model) where T is the generic type.
  • Fix typos

1.3.0 #

  • Initial release of rebuildFromStreams method.
  • Initial release of Injector for Dependency Injection.
  • deprecate blocs parameter and use viewModels instead
  • StateBuilder can have many tags.

1.2.0 #

  • Remove stateID and replace it by tag parameter. tag is optional and many widgets can have the same tag.
  • rebuildStates() when called without parameters, it rebuilds all widgets that are wrapped with StateBuilder and StateWithMixinBuilder.
  • Each StateBuilder has an automatically generated cached address. It is stored in the second parameter of the builder, initState, dispose, and other closures. You can call it inside the closures to rebuild that particular widget.
  • add StateWithMixinBuilder widget to account for some of the most used mixins.
  • Optimize the code and improve performance

1.1.0 #

  • Add withTickerProvider parameter to StateBuilder widget.

1.0.0 #

  • Add BlocProviderto provide your BloCs.
  • You can use enums to name your StateBuilder widgets.
  • rebuildStates now has only one positioned parameter of List
  • If rebuildStates is given without parameter, it will rebuild all widgets that have stateID.
  • improve performance.

0.1.4 #

  • improve performance

0.1.3 #

  • Add getter and setter for the stateMap.

0.1.2 #

  • Remove print statements

0.1.1 #

  • Change readme.md of the example

0.1.0 #

  • Initial version

example/README.md

1- Rebuild All listeners #

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

class CounterBloc extends StatesRebuilder {
  int counter = 0;
  increment() {
    counter++;
    rebuildStates();
  }
}

class RebuildAllExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider<CounterBloc>(
      bloc: CounterBloc(),
      child: CounterGrid(),
    );
  }
}

class CounterGrid extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final bloc = BlocProvider.of<CounterBloc>(context);
    return Padding(
      padding: EdgeInsets.symmetric(vertical: 10, horizontal: 20),
      child: Column(
        children: <Widget>[
          Text("Rebuild All subscribed states"),
          Expanded(
            child: GridView.count(
              crossAxisCount: 3,
              children: <Widget>[
                for (var i = 0; i < 12; i++)
                  StateBuilder(
                    viewModels: [bloc],
                    builder: (_, __) => GridItem(
                          count: bloc.counter,
                          onTap: () => bloc.increment(),
                        ),
                  )
              ],
            ),
          ),
        ],
      ),
    );
  }
}

class GridItem extends StatelessWidget {
  final int count;
  final Function onTap;
  GridItem({this.count, this.onTap});
  @override
  Widget build(BuildContext context) {
    return InkWell(
      child: Container(
        margin: EdgeInsets.all(5),
        decoration: BoxDecoration(
          color: Colors.lightBlue,
          border:
              Border.all(color: Theme.of(context).primaryColorDark, width: 4),
          borderRadius: BorderRadius.circular(6),
        ),
        child: Center(
          child: Text(
            "$count",
            style: TextStyle(
              color: Colors.white,
              fontSize: 50,
            ),
          ),
        ),
      ),
      onTap: onTap,
    );
  }


    // The third alternative

2- Rebuild one listener by the automatically generated ID (address). #

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

class CounterBloc extends StatesRebuilder {
  int counter = 0;
  increment(tagID) {
    counter++;
    rebuildStates([tagID]);
  }
}

class RebuildOneExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider<CounterBloc>(
      bloc: CounterBloc(),
      child: CounterGrid(),
    );
  }
}

class CounterGrid extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final bloc = BlocProvider.of<CounterBloc>(context);
    return StateWithMixinBuilder(
      mixinWith: MixinWith.automaticKeepAliveClientMixin,
      builder: (_, __) => Padding(
            padding: EdgeInsets.all(10),
            child: Column(
              children: <Widget>[
                Text("Rebuild The tapped widget"),
                Text(
                    "This page is mixin with automaticKeepAliveClientMixin to not rebuild on sweep in"),
                Expanded(
                  child: GridView.count(
                    crossAxisCount: 3,
                    children: <Widget>[
                      for (var i = 0; i < 12; i++)
                        StateBuilder(
                          viewModels: [bloc],
                          builder: (_, tagID) => GridItem(
                                count: bloc.counter,
                                onTap: () => bloc.increment(tagID),
                              ),
                        )
                    ],
                  ),
                ),
              ],
            ),
          ),
    );
  }
}

class GridItem extends StatelessWidget {
  final int count;
  final Function onTap;
  GridItem({this.count, this.onTap});
  @override
  Widget build(BuildContext context) {
    return InkWell(
      child: Container(
        margin: EdgeInsets.all(5),
        decoration: BoxDecoration(
          color: Colors.lightBlue,
          border:
              Border.all(color: Theme.of(context).primaryColorDark, width: 4),
          borderRadius: BorderRadius.circular(6),
        ),
        child: Center(
          child: Text(
            "$count",
            style: TextStyle(
              color: Colors.white,
              fontSize: 50,
            ),
          ),
        ),
      ),
      onTap: onTap,
    );
  }
}

3- Rebuild filtered list of listeners by tag. #

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

class CounterBloc extends StatesRebuilder {
  int counter = 0;
  increment(tagID) {
    counter++;
    rebuildStates([tagID]);
  }
}

class RebuildSetExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider<CounterBloc>(
      bloc: CounterBloc(),
      child: CounterGrid(),
    );
  }
}

class CounterGrid extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final bloc = BlocProvider.of<CounterBloc>(context);
    return Padding(
      padding: EdgeInsets.all(10),
      child: Column(
        children: <Widget>[
          Text("Rebuild a set of widgets that have the same tag"),
          Expanded(
            child: GridView.count(
              crossAxisCount: 3,
              children: <Widget>[
                for (var i = 0; i < 12; i++)
                  StateBuilder(
                    tag: i % 2,
                    viewModels: [bloc],
                    builder: (_, tagID) => GridItem(
                          count: bloc.counter,
                          onTap: () => bloc.increment(i % 2),
                        ),
                  )
              ],
            ),
          ),
        ],
      ),
    );
  }
}

class GridItem extends StatelessWidget {
  final int count;
  final Function onTap;
  GridItem({this.count, this.onTap});
  @override
  Widget build(BuildContext context) {
    return InkWell(
      child: Container(
        margin: EdgeInsets.all(5),
        decoration: BoxDecoration(
          color: Colors.lightBlue,
          border:
              Border.all(color: Theme.of(context).primaryColorDark, width: 4),
          borderRadius: BorderRadius.circular(6),
        ),
        child: Center(
          child: Text(
            "$count",
            style: TextStyle(
              color: Colors.white,
              fontSize: 50,
            ),
          ),
        ),
      ),
      onTap: onTap,
    );
  }
}

3- Animation with StateWithMixinBuilder. #

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

class CounterBloc extends StatesRebuilder {
  int counter = 0;

  AnimationController controller;
  Animation animation;

  initAnimation(TickerProvider ticker) {
    controller =
        AnimationController(duration: Duration(seconds: 2), vsync: ticker);
    animation = Tween<double>(begin: 0, end: 2 * 3.14).animate(controller);
    animation.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        controller.reset();
      }
    });
  }

  VoidCallback listener;
  triggerAnimation(tagID) {
    animation.removeListener(listener);
    listener = () {
      rebuildStates([tagID]);
    };
    animation.addListener(listener);
    controller.forward();
    counter++;
  }

  dispose() {
    controller.dispose();
  }
}

class AnimateSetExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider<CounterBloc>(
      bloc: CounterBloc(),
      child: CounterGrid(),
    );
  }
}

class CounterGrid extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final bloc = BlocProvider.of<CounterBloc>(context);
    return Padding(
      padding: EdgeInsets.all(10),
      child: Column(
        children: <Widget>[
          Text("Animate a set of boxes"),
          Expanded(
            child: StateWithMixinBuilder(
              mixinWith: MixinWith.singleTickerProviderStateMixin,
              initState: (_, __, ticker) => bloc.initAnimation(ticker),
              dispose: (_, __, ___) => bloc.dispose(),
              builder: (_, __) => GridView.count(
                    crossAxisCount: 3,
                    children: <Widget>[
                      for (var i = 0; i < 12; i++)
                        StateBuilder(
                          tag: i % 2,
                          viewModels: [bloc],
                          builder: (_, tagID) => Transform.rotate(
                                angle: bloc.animation.value,
                                child: GridItem(
                                  count: bloc.counter,
                                  onTap: () => bloc.triggerAnimation(i % 2),
                                ),
                              ),
                        ),
                    ],
                  ),
            ),
          ),
        ],
      ),
    );
  }
}

class GridItem extends StatelessWidget {
  final int count;
  final Function onTap;
  GridItem({this.count, this.onTap});
  @override
  Widget build(BuildContext context) {
    return InkWell(
      child: Container(
        margin: EdgeInsets.all(5),
        decoration: BoxDecoration(
          color: Colors.lightBlue,
          border:
              Border.all(color: Theme.of(context).primaryColorDark, width: 4),
          borderRadius: BorderRadius.circular(6),
        ),
        child: Center(
          child: Text(
            "$count",
            style: TextStyle(
              color: Colors.white,
              fontSize: 50,
            ),
          ),
        ),
      ),
      onTap: onTap,
    );
  }
}

Use this package as a library

1. Depend on it

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


dependencies:
  states_rebuilder: ^1.3.1

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:states_rebuilder/states_rebuilder.dart';
  
Version Uploaded Documentation Archive
1.3.1 Jun 13, 2019 Go to the documentation of states_rebuilder 1.3.1 Download states_rebuilder 1.3.1 archive
1.3.0 Jun 4, 2019 Go to the documentation of states_rebuilder 1.3.0 Download states_rebuilder 1.3.0 archive
1.2.0 May 23, 2019 Go to the documentation of states_rebuilder 1.2.0 Download states_rebuilder 1.2.0 archive
1.1.0 May 13, 2019 Go to the documentation of states_rebuilder 1.1.0 Download states_rebuilder 1.1.0 archive
1.0.0 May 12, 2019 Go to the documentation of states_rebuilder 1.0.0 Download states_rebuilder 1.0.0 archive
0.1.4 Apr 1, 2019 Go to the documentation of states_rebuilder 0.1.4 Download states_rebuilder 0.1.4 archive
0.1.3 Mar 29, 2019 Go to the documentation of states_rebuilder 0.1.3 Download states_rebuilder 0.1.3 archive
0.1.2 Mar 1, 2019 Go to the documentation of states_rebuilder 0.1.2 Download states_rebuilder 0.1.2 archive
0.1.1 Mar 1, 2019 Go to the documentation of states_rebuilder 0.1.1 Download states_rebuilder 0.1.1 archive
0.1.0 Mar 1, 2019 Go to the documentation of states_rebuilder 0.1.0 Download states_rebuilder 0.1.0 archive
Popularity:
Describes how popular the package is relative to other packages. [more]
84
Health:
Code health derived from static analysis. [more]
100
Maintenance:
Reflects how tidy and up-to-date the package is. [more]
95
Overall:
Weighted score of the above. [more]
91
Learn more about scoring.

We analyzed this package on Jun 17, 2019, and provided a score, details, and suggestions below. Analysis was completed with status completed using:

  • Dart: 2.3.2
  • pana: 0.12.18
  • Flutter: 1.5.4-hotfix.2

Platforms

Detected platforms: Flutter

References Flutter, and has no conflicting libraries.

Maintenance issues and suggestions

Support latest dependencies. (-5 points)

The version constraint in pubspec.yaml does not support the latest published versions for 1 dependency.

Dependencies

Package Constraint Resolved Available
Direct dependencies
Dart SDK >=2.0.0-dev.68.0 <3.0.0
flutter 0.0.0
Transitive dependencies
collection 1.14.11
meta 1.1.6 1.1.7
sky_engine 0.0.99
typed_data 1.1.6
vector_math 2.0.8
Dev dependencies
flutter_test