Skip to main content

Using Flutter Triple

The SSP segments the state into 3 reactive parts, the state value (state), the error object (error), and the state loading action (loading).

These segments are observed in a listener or separate listeners. They can also be combined to obtain a new segment, always starting from the 3 main segments.

Installation#

Add to your project's pubspec.yaml.

...dependencies:  flutter_triple: any...

Choose a Reactivity#

The package flutter_triple implements the SSP using two reactivities: Streams and ValueNotifier/RxNotifier, where we have the StreamNotifier(for Streams) and NotifierStore(for ValueNotifier).

Stream and Notifier Store differences#

  • Streams work without distinct value, so you can dispatcher the same state as many times as you want. In StreamStore you can force an update, setLoading or setError;
  • ValueNotifier is same ChangeNotifier implementation(Listenable class). The flutter_triple package also uses RxNotifier (rx_notifier package). One of its standard features is to prevent the same state from being fired (distinct); NotifierStore don't support force update, setLoading or setError.

Maintaining the State with Streams#

To create a Store that will be responsible for the State Logic, create a class and inherit it from StreamStore:

class Counter extends StreamStore {}

You can also put types into the state value and into the exception object that will be working on this Store:

class Counter extends StreamStore<Exception, int> {}

We finish by assigning an initial value for the state of this Store by invoking the constructor of the parent class (super):

class Counter extends StreamStore<Exception, int> {
    Counter() : super(0);}

It is available in the Store the 3 methods to change the segments (update, setError, and setLoading). Let's start by incrementing the state:

class Counter extends StreamStore<Exception, int> {
    Counter() : super(0);
    void increment(){        update(state + 1);    }}

This code is enough to make the counter work. Let's add a little bit of asynchronous code to introduce the methods setError and setLoading

class Counter extends StreamStore<Exception, int> {
    Counter() : super(0);
    Future<void> increment() async {        setLoading(true);
        await Future.delayed(Duration(seconds: 1));
        int value = state + 1;        if(value < 5) {            update(value);        } else {            setError(Exception('Error: state can\'t be > 4'));        }        setLoading(false);    }}

Here we experience the change of states and the other segments of loading and error.

NOTE: To use NotifierStore it is the same process as we saw on StreamStore.

The 3 segments operate separately but can be "heard" together. Now we will see how to observe this store.

Observers and Builders#

observer#

We can observe the segments separately or together by using store.observer();

counter.observer(    onState: (state) => print(state),    onError: (error) => print(error),    onLoading: (loading) => print(loading),);

On Widgets we can observe on a Builder with ScopedBuilder or observe all changes with TripleBuilder.

ScopedBuilder#

Use ScopedBuilder to listen the segments, likewise the method store.observer();

ScopedBuilder(    store: counter,    onState: (context, state) => Text('$state'),    onError: (context, error) => Text(error.toString()),    onLoading: (context) => CircularProgressIndicator(),);

ScopedBuilder.transition#

Use it for adding a custom transition on state change:

ScopedBuilder.transition(    store: counter,    transition: (_, child) {    return AnimatedSwitcher(        duration: Duration(milliseconds: 400),        child: child,      );    },    onLoading: (_) => Text('Loading...'),    onState: (_, state) => Text('$state'),  ),

NOTE: On ScopedBuilder the onLoading is only called on "true". This means that if the state is modified or an error is added, the widget to be built will be the onState or onError. However, it is very important to change Loading to "false" when the loading action is completed. observers of Triple DO NOT PROPAGATE REPEATED OBJECTS (more on this in the section distinct). This behavior is exclusive to ScopedBuilder.

TripleBuilder#

Use TripleBuilder to listen all segment modifications and reflect them in the Widgets tree.

TripleBuilder(    store: counter,    builder: (context, triple) => Text('${triple.state}'),);

NOTE: The TripleBuilder builder is called when there is any change in the segments. Its use is recommended only if you are interested in listening to all segments at the same time.

Distinct#

By default, the Store's observer does not react to repeated objects. This behavior is beneficial as it avoids state reconstructions and notifications if the segment has not been changed.

It is good practice to overwrite the operation== of the state value and error. A good tip is also to use the package equatable to simplify this type of comparison.

Selectors#

We can recover the reactivity of the segments individually for transformations or combinations. We have then 3 selectors that can be retrieved as Store properties: store.selectState, store.selectError and store.selectLoading.

The type of selectors changes depending on the reactive tool you are using in the Stores. For example, if you are using StreamStore then your selectors will be Streams, however, if you are using NotifierStore then your selectors will be ValueListenable;

//StreamStoreStream<int> myState$ = counter.selectState;Stream<Exception> myError$ = counter.selectError;Stream<bool> myLoading$ = counter.selectLoading;
//NotifierStoreValueListenable<int> myState$ = counter.selectState;ValueListenable<Exception?> myError$ = counter.selectError;ValueListenable<bool> myLoading$ = counter.selectLoading;

Maintaining the State with ValueNotifier#

ValueNotifier is an implementation of ChangeNotifier and is present in the entire ecosystem of Flutter, from ScrollController to TabController.

Using the ChangeNotifier API means reusing everything that already exists on Flutter.

The ValueNotifier used in this Store is extended by the library rx_notifier which brings the possibility of applying functional reactive programming (TFRP), listening to changes on their values ​​in such a transparent way as MobX does.

A Store based on ValueNotifier is called NotifierStore:

class Counter extends NotifierStore<Exception, int> {
    Counter() : super(0);
    Future<void> increment() async {        setLoading(true);
        await Future.delayed(Duration(seconds: 1));
        int value = state + 1;        if(value < 5) {            update(value);        } else {            setError(Exception('Error: state can\'t be > 4'));        }        setLoading(false);    }}

Our selectors (selectState, selectError, and selectBool) now will be ValueListenable that can be listened separately using .addListener() or in the Widget Tree with AnimatedBuilder, both from Flutter:


store.selectError.addListener(() => print(store.state));
...
Widget builder(BuildContext context){    return AnimatedBuilder(        animation: store.selectState,        builder: (_, __, ___) => Text(store.state);    );}

Or listen to reactions transparently using the rxObserver or in the widget tree with the RxBuilder:


rxObserver(() => print(store.state));
...
Widget builder(BuildContext context){    return RxBuilder(        builder: (_) => Text(store.state),    );}

For more information about the extension read the documentation for rx_notifier

IMPORTANT: You can also continue to use the Triple (observer, ScopedBuilder and TripleBuilder);