Giter VIP home page Giter VIP logo

redarx's Introduction

Unstart ( ex Redarx )

(Experimental) Unidirectional data-flow State management for Dart humbly inspired by ngrx <= Redux <= Elm, André Stalz work & Parsley.

redarx-principles

Examples

Principles

Requests to Commands Mapping via CommanderConfig

Goal : decouple request from action. View only dispatch dumb requests, the logic to execute the request is delegated to a command.

final requestMap =
    <RequestType, CommandBuilder<TodoModel>>{
  RequestType.ADD_TODO: AddTodoCommand.constructor(),
  RequestType.UPDATE_TODO: UpdateTodoCommand.constructor(),
  RequestType.CLEAR_ARCHIVES: ClearArchivesCommand.constructor(),
  RequestType.COMPLETE_ALL: CompleteAllCommand.constructor(),
  RequestType.LOAD_ALL: AsyncLoadAllCommand.constructor(DATA_PATH),
  RequestType.TOGGLE_SHOW_COMPLETED: ToggleShowArchivesCommand.constructor()
};

Initialization

final config = new CommanderConfig<RequestType>(requestMap);
final store = new Store<TodoModel>(() => new TodoModel.empty());
final dispatcher = new Dispatcher();

final cmder = new Commander(config, store, dispatcher.onRequest);

Flutter

final requestMap = <RequestType, CommandBuilder>{
  RequestType.LOAD_ALL: AsyncLoadAllCommand.constructor(DATA_PATH),
  RequestType.ADD_TODO: AddTodoCommand.constructor(),
  RequestType.UPDATE_TODO: UpdateTodoCommand.constructor(),
  RequestType.CLEAR_ARCHIVES: ClearArchivesCommand.constructor(),
  RequestType.COMPLETE_ALL: CompleteAllCommand.constructor(),
  RequestType.TOGGLE_SHOW_COMPLETED: ToggleShowArchivesCommand.constructor()
};

void main() {
  runApp(new StoreProvider(requestMap: requestMap, child:new TodoApp()));
}

Vanilla Dart

var app = new AppComponent(querySelector('#app') )
..model$ = store.data$
..dispatch = dispatcher.dispatch
..render();

Dispatching requests

RequestType are defined via an enum

enum RequestType {
  ADD_TODO,
  UPDATE_TODO,
  ARCHIVE,
  CLEAR_ARCHIVES,
  TOGGLE_SHOW_COMPLETED
}

// you can enforce the typing by creating an optionnal request class
class TodoRequest<T extends Todo> extends Request<RequestType, T> {
  TodoRequest.loadAll() : super(RequestType.LOAD_ALL);
  TodoRequest.clearArchives() : super(RequestType.CLEAR_ARCHIVES);
  TodoRequest.completeAll() : super(RequestType.COMPLETE_ALL);
  TodoRequest.toggleStatusFilter() : super(RequestType.TOGGLE_SHOW_COMPLETED);
  TodoRequest.add(T todo) : super(RequestType.ADD_TODO, withData: todo);
  TodoRequest.cancel(T todo) : super(RequestType.CANCEL_TODO, withData: todo);
  TodoRequest.update(T todo) : super(RequestType.UPDATE_TODO, withData: todo);
}

Requests are defined by a type and an optional payload.

dispatch( new TodoRequest.add(new Todo(fldTodo.value)));

or

Commands

Requests are mapped to commands by Commander and passed to the store.update()

exec(Request a) {
    store.update(config[a.type](a.payload));
  }

The commands define a public exec method which receive the currentState and return the new one.

class AddTodoCommand extends Command<TodoModel> {
  Todo todo;

  AddTodoCommand(this.todo);

  @override
  TodoModel exec(TodoModel model) => model..items.add(todo);

  static CommandBuilder constructor() {
    return (Todo todo) => new AddTodoCommand(todo);
  }
}

Async Commands

Async commands allows async evaluation of the new state

Store

Basically a (stream<Command<Model>>) => stream<Model> transformer

Receives new commands, and executes those with the current state/Model

Use a CommandStreamReducer<S extends Command, T extends AbstractModel> to stream reduced states

The store manage a stream of immutable states instances.

Reversible Store

The reversible store keep a history list of all executed commands and allow cancelling.

it provide an access to currentState by reducing all the commands history.

State listening

The store exposes a stream of immutable states

Stream<TodoModel> _model$;

set model$(Stream<TodoModel> value) {
_model$ = value;
    
modelSub = _model$.listen((TodoModel model) {
      list.todos = model.todos;
      footer.numCompleted = model.numCompleted;
      footer.numRemaining = model.numRemaining;
      footer.showCompleted = model.showCompleted;
    });
  }

Experimental multi-channel dispatcher (0.6.0)

Async commands are maybe not the best way to connect a Firebase data-source.

The redarx_ng_firebase example shows a way to dispatch firebase queries via a new dispatcher method : query.

Queries are dispatched to a Firebase service, which update the base. The service handles firebase.database child and values events and dispatch update request via the dispatch() method.

Event$ » Request$ » Command$ » state$

The Application State is managed in a Store.

State is updated by commands, and the store keep a list of executed commands.

State is evaluated by commands updates,

In reversible-store, cancellation is allowed by simply remove the last command from "history".

A Commander listen to a stream of Requests dispatched by a Dispatcher injected in the application components | controllers | PM | VM

Each Request is defined by an RequestType enum, and can contains data.

Requests are "converted" to commands by the Commander, based on the CommanderConfig.map definition

  • the dispatcher.dispatch function is injected in view || controller || PresentationModel || ViewModel

  • Request are categorized by types, types are defined in RequestType enum

  • the dispatcher stream Requests

  • the dispatcher requestStream is injected in Commander, the commander listen to it, transforms Request to Command and transfer to the store.apply( command ) method

  • each Request is tied to a command via a CommanderConfig which is injected in Commander

// instanciate commands form requests 
config[request.type](request.payload);
  • Commander need a CommanderConfig containing a Map<RequestType,CommandBuilder>
  • the store then execute commandHistory and push the new model value to a model stream

TODO

  • fix the generic/command ( mess)

  • implements a Scan stream transformer » to allow only run the last commands & emit the last reduced state

  • async commands

  • test Angular integration

  • test with Firebase

  • typed Request ? BookRequest, UserRequest ...? => TodoRequest cf. flutter example

  • use values types cf built_value

  • multiple stores ?

  • time travel / history UI

  • tests

  • external config file ?

  • ...

Doubts

  • use a EnumClass implementation rather than dart enum type
  • dispatcher : use a streamController.add rather than dispatch method ?
  • multiple store ? dispatcher ? commander ?
  • each component could set an Request stream and the commander could maybe listen to it

Goals

  • study Dart : streams, generics, annotations, asynchrony...
  • study Redux & ngrx, play with reducers & Request/Commands mapping...
  • and more studies, more experiments, more play...
  • define a solid architecture for my coming projects

redarx's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.