triple 0.0.27 copy "triple: ^0.0.27" to clipboard
triple: ^0.0.27 copied to clipboard

abstraction for the Segmented State Pattern

Triple #

Este package é uma abstração do Segmented State Pattern( Padrão de Estado Segmentado) que impõem barreiras arquiteturais para reatividades individuais.

Essa abstração server para a crição de implementações do SSP usando qualquer objeto Reativo como base para criar uma Store(Objeto Responsável pela Lógica do Estado de um componente).

Como criar uma Store? #

.

Triple

Seguindo o SSP, nossa Store precisa segmentar os dados do estado em 3 vias, um State(contendo o valor do Estado), um Error(Contendo o objeto de exception do estado) e o Loading(indicando se o valor do estado está sendo carregado). Essas 3 propriedades fazem parte do objeto Triple que é herdado como propriedade na classe abastrata Store. Vamos entao ver passo-a-passo como criar um Store baseado em qualquer sistema de Reatividade existente.

PASSO 1: Escolha uma forma de Reatividade. #

O SSP não coloca nenhum requerimento sobre o tipo de reatividade que poderá ser utilizada no padrão, então o desenvolvedor deve escolher a que mais lhe agrada para criar uma Store. Alguns exemplos de ferramentas:

  • Streams
  • ValueNotifier/ChangeNotifier
  • MobX

Para os próximos passos usaremos "Streams", mas fique a vontade sobre essa escolha.

PASSO 2: Crie uma classe que herde de Store #

Como já falamos, um objeto Store serve para armazenar a Lógica de Estado de um componente.

abstract class StreamStore extends Store {}

Também é prudente colocar "tipos genéricos" para o "error" e "state", faremos isso no StreamStore e depois reatribuiremos na Store.

IMPORTANTE: Herde os tipos genéricos de Object para impedir o uso de dynamics.

e assim temos:

abstract class StreamStore<Error extends Object, State extends Object> extends Store<Error, State> {}

Precisamos ainda declarar o construtor da classe pai com um valor inicial do state e assim concluimos essa etapa:

abstract class StreamStore<Error extends Object, State extends Object> extends Store<Error, State> {

  StreamStore(State state) : super(state);

}

PASSO 3: Inicie um objeto com a reatividade escolhida. #

Inclua de forma privada uma propriedade reativa que trabalhe com o tipo Triple<Error, State>():

abstract class StreamStore<Error extends Object, State extends Object> extends Store<Error, State> {

  //main stream
  final _tripleController = StreamController<Triple<Error, State>>.broadcast(sync: true);

  StreamStore(State state) : super(state);

}

PASSO 4: Encerre o objeto reativo #

Sobrescreva o método destroy que será chamado quando a Store for descartada.

abstract class StreamStore<Error extends Object, State extends Object> extends Store<Error, State> {

  //main stream
  final _tripleController = StreamController<Triple<Error, State>>.broadcast(sync: true);

  StreamStore(State state) : super(state);

  @override
  Future destory() async {
    await _tripleController.dispose();
  }

}

PASSO 5: Sobrescreva o método de Propagação. #

Quando o Store decide propagar um valor do tipo Triple, ele o faz chamando o método propagate(). Sobreescreva esse método para direcionar o fluxo para o seu controle principal de reatividade. Não se esqueça de chamar o método super.propagate().

abstract class StreamStore<Error extends Object, State extends Object> extends Store<Error, State> {

  //main stream
  final _tripleController = StreamController<Triple<Error, State>>.broadcast(sync: true);

  StreamStore(State state) : super(state);

  @protected
  @override
  void propagate(Triple<Error, State> triple){
    super.propagate(triple);
    _tripleController.add(triple);
  }

  @override
  Future destory() async {
    await _tripleController.dispose();
  }

}

IMPORTANTE: O método propagate está assinado com @protected porque ele só deve ser usado dentro da classe StreamStore.

PASSO 6: Sobreescreva o método observer #

Esse método é chamado para escutar os eventos segmentados do estado(state, error e loading). Sobreescreva chamando as funções de cada segmento.

abstract class StreamStore<Error extends Object, State extends Object> extends Store<Error, State> {

  //main stream
  final _tripleController = StreamController<Triple<Error, State>>.broadcast(sync: true);

  StreamStore(State state) : super(state);

  @protected
  @override
  void propagate(Triple<Error, State> triple){
    _tripleController.add(triple);
  }

  @override
  Disposer observer({
    void Function(State state)? onState,
    void Function(Error error)? onError,
    void Function(bool loading)? onLoading,
  }){
    final _sub = _tripleController.listen((triple){
      if(triple.event == TripleEvent.state){
        onState(triple.state);
      } else if(triple.event == TripleEvent.error){
        onError(triple.error);
      } else if(triple.event == TripleEvent.loading){
        onLoading(triple.loading);
      }
    });
    return () async {
      await _sub.cancel();
    }
  }

  @override
  Future destory() async {
    await _tripleController.dispose();
  }

}

PASSO 7 (OPCIONAL): Defina Seletores #

Pode ser interessande ter seletores de quada segmento do estado de forma reativa. Isso é um Error, State e loading reativo. Se dejesa ter essa possibilidade no Store implemente a interface Selectors:

abstract class StreamStore<Error extends Object, State extends Object> extends Store<Error, State>
implements Selectors<Stream<Error>, Stream<State>, Stream<bool>>
 {

  //main stream
  final _tripleController = StreamController<Triple<Error, State>>.broadcast(sync: true);

  @override
  late final Stream<State> selectState = _tripleController.stream
      .where((triple) => triple.event == TripleEvent.state)
      .map((triple) => triple.state);

  @override
  late final Stream<Error> selectError = _tripleController.stream
      .where((triple) => triple.event == TripleEvent.error)
      .where((triple) => triple.error != null)
      .map((triple) => triple.error!);

  @override
  late final Stream<bool> selectLoading = _tripleController.stream
      .where((triple) => triple.event == TripleEvent.loading)
      .map((triple) => triple.loading);

  StreamStore(State state) : super(state);

  ...

Middleware #

Podemos adicionar interceptadores e modificar o triple quando for executado a ação de setLoading, setError ou update.

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 #

Um padráo muito comum em uma requisição assincrona é:


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

Você pode utilizar o método execute e passar a Future para executar os mesmos passos descritos no exemplo anterior:


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

para usuários que utilizam o dartz utilizando o Clean Architecture por exemplo, também podem executar os eithers utilizando o método executeEither:

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

Usando o Padrão Memento com o MementoMixin #

Você pode adicionar Desfazer ou refazer um estado usando o Memento Pattern. Isso significa que poderá retornar ao estado anterior usando o método undo() e também prosseguir com o método redo().


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

Exemplos #

Features and bugs #

Please file feature requests and bugs at the issue tracker.

55
likes
115
points
2.79k
downloads

Publisher

verified publisherflutterando.com.br

Weekly Downloads

abstraction for the Segmented State Pattern

Documentation

API reference

License

MIT (license)

Dependencies

async, dartz, meta

More

Packages that depend on triple