flutter_triple 0.0.27
flutter_triple: ^0.0.27 copied to clipboard

Flutter Android iOS web

Implementation of the Segmented State Pattern (SSP) nicknamed Triple.

flutter_triple #

Implementation of the Segmented State Pattern, nicknamed Triple.

State Segmentation #

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

.

Triple

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.

The Package #

This package introduces Stores in the pre-implemented segment pattern using the Streams API (StreamStore) and the ValueNotifier object (NotifierStore).

Stores already offer by default an observer (store.observer()) and store.update() (Update State), store.setLoading() (To change the loading), store.setError() (To change the error). It also has the mixin MementoMixin that uses the pattern design Memento to undo or redo the state value, therefore, the store.undo() and store.redo() methods are also added to Store by this mixin. Using the ValueNotifier (NotifierStore), the properties state, loading, and error are reactive thanks to the extension rx_notifier.

The Package also has Builder Widgets to observe changes in the state in the Flutter widget tree.

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.delayer(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(),
);

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;

//StreamStore
Stream<int> myState$ = counter.selectState;
Stream<Exception> myError$ = counter.selectError;
Stream<bool> myLoading$ = counter.selectLoading;

//NotifierStore
ValueListenable<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.delayer(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);

Middleware #

We can add interceptors and modify the triple when the setLoading, setError or update action is executed.

class Counter extends StreamStore<Exception, int> {

  Counter(0): super(0);

  ...
  @override
  Triple<Exception, int> middleware(triple){
    if(triple.event == TripleEvent.state){
      return triple.copyWith(state + 2);
    }

    return triple;
  }

}

Executors #

A very common pattern in an asynchronous request is:


  @override
  Future<void> fetchData(){
    setLoading(true);
    try {
      final result = await repository.fetch();
      update(result);
    } catch(e){
      setError(e);
    }
    setLoading(false);
  }

You can use the ** execute method and pass on Future to perform the same steps described in the previous example:


  @override
  Future<void> fetchData(){
   execute(() => repository.fetch());
  }

for users using dartz using Clean Architecture for example, they can also run the Either class using the executeEither method:

 @override
  Future<void> fetchData(){
   executeEither(() => myUsecase());
  }

Memento with MementoMixin #

You can add, undo or redo a state using the Memento Pattern. This means that you can return to the previous state using the method undo() and also advance with the method redo().


class Counter extends StreamStore<Exception, int> with MementoMixin {}

Questions and Problems #

The issues channel is open for questions, to report problems and suggestions, do not hesitate to use this communication channel.

LET'S BE REFERENCES TOGETHER

7
likes
100
pub points
63%
popularity

Publisher

flutterando.com.br

Implementation of the Segmented State Pattern (SSP) nicknamed Triple.

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (LICENSE)

Dependencies

flutter, rx_notifier, triple

More

Packages that depend on flutter_triple