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#

Added in 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 distinc value, so you can dispatcher the same state as many times as you want. In StreamStore you can force a update, setLoading or setError;
  • ValueNotifier is same ChangeNotifier implementation(Listenable class). The flutter_triple else use RxNotifier (rx_notifier package). One of its standard features is to prevent the same die 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 from StreamStore:

class Counter extends StreamStore {}

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

class Counter extends StreamStore<Exception, int> {}

We ended 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 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 not can 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 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 for add 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 when "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 on distinct). This is a behavior 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 then have 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 in 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 in their values ​​in a transparent way as does the MobX.

A Store based on ValueNotifier its 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 not can be > 4'));        }        setLoading(false);    }}

Our selectors (selectState, selectError, and selectBool) now will be ValueListenable that can be listen 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);