Modulisto

A advanced state management system that based on the concepts of Trigger and Store, which are handled by groupings and subscriptions

Quick Start

1. Create a module that extends Module

class SimpleCounter extends Module {
    ...
}

2. Declare in this module a Units that you will use

    final state = Store(0);

     // Generic <()> means that trigger doesn't have any payload
    final increment = Trigger<()>();
    final decrement = Trigger<()>();

    // Trigger can be private to describe some internal functionality
    final _setValue = Trigger<int>();

3. Initialize module and create subscriptions with Module.initialize in constructor body

SimpleCounter(...) {
    // ref is protected ModuleRef that used to create Module subscriptions
    // and initialize declared `Unit`s
    Module.initialize(
        this,
        (ref) => ref
        ..asyncPipelineGroup(
            // `eventTransformers.sequental` is used by default
            transformer: eventTransformers.sequental,
            ($) => $
            ..on(increment, _increment)
            ..on(decrement, _decrement)
            ..on(reset, _reset),
        )
        ..syncGroup(
            ($) => $
            ..on(state, print)
            ..on(ref.lifecycle.init, (_) => print('inited'))
            ..on(ref.lifecycle.dispose, (_) => print('disposed')),
        ),
    );
}

4. Use newly created module in your project

final counter = SimpleCounter(...);

counter.increment().invoke(); // 1
counter.increment().invoke(); // 2

counter.decrement().invoke(); // 1

await Future.delayed(
    Duration.zero,
    counter.dispose,
);

PLEASE: Read this small brief about using Trigger<T>'s

Entities

Unit

An abstract, observable entity that encapsulates the relationship between modules and their ability to produce meaningful intention, denoted as [Unit]Intent, which describes the purpose or objective associated with the Unit

[Unit]Intent

A private entity that serves a description of an intentional action around Unit, holding a reference to the this unit and target payload.

It should be emitted exclusively within the zone of IntentStream.

Store<T>

A subtype of the Unit that represents a holder/storage/container for T value

When the .update method is invoked, it returns a private _StoreIntent<T> object that stores new value with type T

Trigger<T>

A subtype of the Unit that represents a unary (synchronous) call.

When the .call method is invoked (with the appropriate payload of type T), it returns a private _TriggerIntent<T> object that brings T payload to subscribers

This rule streamlines the process of describing and invoking triggers, eliminating the need to create custom Event classes, as is done in the bloc.

Small brief about using Trigger

Any Trigger produces relevant _TriggerIntent on .call, this means that it only creates an intention, but does not execute it

Any _TriggerIntent has an .invoke(), which allows us to notify the referenced Trigger about our purpose to invocation

You MUST NOT call .invoke() directly in the body of an IntentStream

The context of processing each IntentStream prohibits direct invocation of .invoke(), instead you need to yield your _TriggerIntent that you got after calling .call(...) onto your Trigger

IntentStream _test(() _) async* {
    // myCoolTrigger is Trigger<int>

    // BAD
    myCoolTrigger(123).invoke(); // throws Exception

    // GOOD
    yield myCoolTrigger(123);
}

// Somewhere in your code
void main() {
    ...
    final module = CoolModule();

    // GOOD
    module.myCoolTrigger(123).invoke();
}

Generally, .invoke() method exists only for "pull the Trigger" in "open-world" context (out of the module)

Grouping and subscribing (of the Unit's)

Modulisto uses two ways of "group and subscribe":

  1. Asynchronous pipeline group - asyncPipelineGroup
  2. Synchronous group - syncGroup

Each one can be created using the appropriate methods provided by ModuleRef, which is accessible through the static method Module.initialize

ModuleRef

Protected reference to this module that allow us to create groups and subscriptions during the initialization phase

asyncPipelineGroup

Used to subscribe one or more Unit's to a function that returns an IntentStream based on the provided payload

Each such function passes through an internal StreamController (shared for all events in this group) and is processed according to the provided transformer

Any EventTransformer used in a bloc or (delivered via bloc_concurrency) can be used here in the same manner

TL;DR: asyncPipelineGroup - Grouped subscriptions to specific events that generate stream of asynchronous feedback intentions

syncGroup

Used to subscribe Units to synchronous callbacks that doesn't returns anything (void)

Under the hood, syncGroup does not utilize any form of pipelining, so each callback is executed synchronously as reaction to the Unit

Can be used for debugging purposes or cross-invokation other Trigger's

Libraries

modulisto