ajwah_bloc 1.7.0

ajwah_bloc #

Rx based state management library. Manage your application's states, effects, and actions easy way.

States #

Every state class must derived from BaseState<T> class. And it is mandatory to pass the state name and initialState. The BaseState<T> class has an abstract method Stream<T> mapActionToState(T state, Action action);. This method should be invoked by sysytem passing current state and action. You should mutate the state based on action.

Example CounterState #

import 'package:ajwah_bloc/ajwah_bloc.dart';
import 'package:ajwah_block_examples/actionTypes.dart';

class CounterModel {
  int count;
  bool isLoading;

  CounterModel({this.count, this.isLoading});

  copyWith({int count, bool isLoading}) {
    return CounterModel(
        count: count ?? this.count, isLoading: isLoading ?? this.isLoading);
  }

  CounterModel.init() : this(count: 10, isLoading: false);
}

class CounterState extends BaseState<CounterModel> {
  CounterState() : super(name: 'counter', initialState: CounterModel.init());

  Stream<CounterModel> mapActionToState(
      CounterModel state, Action action) async* {
    switch (action.type) {
      case ActionTypes.Inc:
        state.count++;
        yield state.copyWith(isLoading: false);
        break;
      case ActionTypes.Dec:
        state.count--;
        yield state.copyWith(isLoading: false);
        break;
      case ActionTypes.AsyncInc:
        yield state.copyWith(isLoading: true);
        yield await getCount(state.count);
        break;
      default:
        yield state;
    }
  }

  Future<CounterModel> getCount(int count) {
    return Future.delayed(Duration(milliseconds: 500),
        () => CounterModel(count: count + 1, isLoading: false));
  }
}

Example TodoState #

import 'package:ajwah_bloc/ajwah_bloc.dart';
import 'package:ajwah_block_examples/actionTypes.dart';
import 'package:ajwah_block_examples/todoApi.dart';

class Todo {
  final int id;
  final String title;
  bool completed;
  Todo({this.id, this.title, this.completed});
  factory Todo.fromJson(dynamic json) {
    return Todo(
        id: json['id'] as int,
        title: json['title'] as String,
        completed: json['completed'] as bool);
  }
  dynamic toJson() {
    return {'id': id, 'title': title, 'completed': completed};
  }
}

class TodoModel {
  String message;
  List<Todo> todoList = [];
  TodoModel({this.message, this.todoList});
  TodoModel copyWith({String message, List<Todo> todoList}) {
    return TodoModel(
        message: message ?? this.message, todoList: todoList ?? this.todoList);
  }
}

class TodoState extends BaseState<TodoModel> {
  TodoState()
      : super(name: 'todo', initialState: TodoModel(message: '', todoList: []));

  Stream<TodoModel> mapActionToState(TodoModel state, Action action) async* {
    try {
      switch (action.type) {
        case ActionTypes.LoadingTodos:
          yield state.copyWith(message: 'Loading todos.');
          state.todoList = await TodoApi.getTodos();
          yield state.copyWith(message: '');
          break;
        case ActionTypes.AddTodo:
          yield state.copyWith(message: 'Adding todo.');
          var todo = await TodoApi.addTodo(action.payload);
          state.todoList = List.from(state.todoList)..insert(0, todo);
          yield state.copyWith(message: '');
          break;
        case ActionTypes.UpdateTodo:
          yield state.copyWith(message: 'Updating todo.');
          await TodoApi.updateTodo(action.payload);
          state.todoList = List<Todo>.from(state.todoList);
          yield state.copyWith(message: '');
          break;
        case ActionTypes.RemoveTodo:
          yield state.copyWith(message: 'Removing todo.');
          var todo = await TodoApi.removeTodo(action.payload);
          state.todoList =
              state.todoList.where((it) => it.id != todo.id).toList();
          yield state.copyWith(message: '');
          break;
        default:
          yield state;
      }
    } catch (err) {
      yield state.copyWith(message: err.toString());
    }
  }
}

Effects #

Every effect class must derived from BaseEffect class. And it is optional to pass the effectKey. But it's mandatory if you want conditionally remove the effects by using store.removeEffectsByKey('effectKey'). The BaseEffect class has one abstract method List<Observable<Action>> registerEffects(Actions action$, Store store$);. This function should be invoked by system passing reference of Actions and Store classes. Please keep in mind that effects should not work until you register them.

Example #

import 'package:ajwah_bloc/ajwah_bloc.dart';
import 'package:rxdart/rxdart.dart';

import '../../actionTypes.dart';

class CounterEffect extends BaseEffect {
  Observable<Action> effectForAsyncInc(Actions action$, Store store$) {
    return action$
        .ofType(ActionTypes.AsyncInc)
        .debounceTime(Duration(milliseconds: 500))
        .mapTo(Action(type: ActionTypes.Inc));
  }

  List<Observable<Action>> registerEffects(Actions action$, Store store$) {
    return [effectForAsyncInc(action$, store$)];
  }
}

Using state in components #

Ajwah provides a comfortable way to use states in components and dispatching actions.

Just call the createStore(states:[], effects:[]) method from main() function. Now store instance should be available by the helper function store() throughout the application.

Note: createStore(...) method return store instance so that you can make a sate provider class(InheritedWidget) as your convenient.

We can use select method to get state data (passing state name): select('counter'). or select2(...). These methods return Observable<T>. Now pass this Observable inside a StreamBuilder to make a reactive widget.

Example #

StreamBuilder<CounterModel>(
    stream: select<CounterModel>('counter'),
    builder:(BuildContext context, AsyncSnapshot<CounterModel> snapshot) {
        if (snapshot.data.isLoading) {
          return CircularProgressIndicator();
        }
        return Text(
            snapshot.data.count.toString(),
            style: Theme.of(context).textTheme.title,
          );
    },
)        

And also for dispatching state's action - we can use dispatch(...) or store().dispatch(Action(type:'any', payload:any)) method.

Please have a look at here for progressive examples

0.1.0 #

Rx based state management library for Dart. Manage your application's states, effects, and actions easy way.

0.1.1 #

Fix lib/src/effectSubscription.dart. (-0.50 points)

Analysis of lib/src/effectSubscription.dart reported 1 hint:

line 10 col 19: The class 'Stream' was not exported from 'dart:core' until version 2.1, but this code is required to be able to run on earlier versions.

1.0.0 #

added testing and update doc

1.0.1 #

Fix lib/src/storeHelper.dart. (-0.50 points)

Analysis of lib/src/storeHelper.dart reported 1 hint:

line 39 col 19: Avoid empty catch blocks.

1.0.2 #

Added select2(...) method. This method takes a callback which has a single Map<String, dynamic> type arg. If you pass Map key as a state name then you will get corresponding model instance as value.

Example

final _message$ = store()
    .select2<TodoModel>((states) => states['todo'])
    .map((tm) => tm.message)
    .distinct();

1.0.3 #

updating doc and param type changed in select() function

1.5.0 #

param type changed in dispatch() function and exposed select() and select2() functions into the global scope

1.6.0 #

T reduce(T state, Action action) function has been replaced with Stream<T> mapActionToState(T state, Action action) into the BaseState<T> class.

1.7.0 #

Remove dependency async

example/README.md

Please have a look at here for progressive examples

actionTypes.dart #

class ActionTypes {
  static const String Inc = 'inc';
  static const String Dec = 'dec';
  static const String AsyncInc = 'AsyncInc';
}

counterState.dart #

import 'package:ajwah_bloc/ajwah_bloc.dart';
import 'actionTypes.dart';

class CounterModel {
  int count;
  bool isLoading;

  CounterModel({this.count, this.isLoading});

  copyWith({int count, bool isLoading}) {
    return CounterModel(
        count: count ?? this.count, isLoading: isLoading ?? this.isLoading);
  }

  CounterModel.init() : this(count: 10, isLoading: false);
}

class CounterState extends BaseState<CounterModel> {
  CounterState() : super(name: 'counter', initialState: CounterModel.init());

  Stream<CounterModel> mapActionToState(
      CounterModel state, Action action) async* {
    switch (action.type) {
      case ActionTypes.Inc:
        state.count++;
        yield state.copyWith(isLoading: false);
        break;
      case ActionTypes.Dec:
        state.count--;
        yield state.copyWith(isLoading: false);
        break;
      case ActionTypes.AsyncInc:
        yield state.copyWith(isLoading: true);
        yield await getCount(state.count);
        break;
      default:
        yield state;
    }
  }

  Future<CounterModel> getCount(int count) {
    return Future.delayed(Duration(milliseconds: 500),
        () => CounterModel(count: count + 1, isLoading: false));
  }
}


CounterComponent.dart #


import 'package:ajwah_bloc/ajwah_bloc.dart';
import 'actionTypes.dart';
import 'counterState.dart';
import 'package:flutter_web/material.dart';

class CounterComponent extends StatelessWidget {
  const CounterComponent({Key key}) : super(key: key);

  void increment() {
    dispatch(actionType: ActionTypes.Inc);
  }

  void decrement() {
    dispatch(actionType: ActionTypes.Dec);
  }

  void asyncIncrement() {
    dispatch(actionType: ActionTypes.AsyncInc);
  }

  @override
  Widget build(BuildContext context) {
    return Row(
      children: <Widget>[
        ButtonBar(mainAxisSize: MainAxisSize.min, children: <Widget>[
          RaisedButton(
            textColor: Colors.white,
            color: Colors.blue,
            child: Icon(Icons.add),
            onPressed: increment,
          ),
          new RaisedButton(
            textColor: Colors.white,
            color: Colors.blue,
            child: Text('Async(+)'),
            onPressed: asyncIncrement,
          ),
          RaisedButton(
            textColor: Colors.white,
            color: Colors.blue,
            child: Icon(Icons.remove),
            onPressed: decrement,
          )
        ]),
        SizedBox(
          width: 10.0,
        ),
        StreamBuilder<CounterModel>(
          stream: store().select<CounterModel>('counter'),
          initialData: CounterModel.init(),
          builder:
              (BuildContext context, AsyncSnapshot<CounterModel> snapshot) {
                
            if (snapshot.data.isLoading) {
              return CircularProgressIndicator();
            }
            return Text(
              snapshot.data.count.toString(),
              style: Theme.of(context).textTheme.title,
            );
          },
        )
      ],
    );
  }
}

main.dart #

import 'package:ajwah_bloc/ajwah_bloc.dart';
import 'counterComponent.dart';
import 'counterState.dart';
import 'package:flutter_web/material.dart';

void main(){
  createStore(states: [CounterState()]]);
  return runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'Ajwah_bloc Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        debugShowCheckedModeBanner: false,
        home: CounterComponent());
  }
}

Use this package as a library

1. Depend on it

Add this to your package's pubspec.yaml file:


dependencies:
  ajwah_bloc: ^1.7.0

2. Install it

You can install packages from the command line:

with pub:


$ pub get

with Flutter:


$ flutter pub get

Alternatively, your editor might support pub get or flutter pub get. Check the docs for your editor to learn more.

3. Import it

Now in your Dart code, you can use:


import 'package:ajwah_bloc/ajwah_bloc.dart';
  
Popularity:
Describes how popular the package is relative to other packages. [more]
19
Health:
Code health derived from static analysis. [more]
100
Maintenance:
Reflects how tidy and up-to-date the package is. [more]
100
Overall:
Weighted score of the above. [more]
60
Learn more about scoring.

We analyzed this package on Aug 17, 2019, and provided a score, details, and suggestions below. Analysis was completed with status completed using:

  • Dart: 2.4.0
  • pana: 0.12.19

Platforms

Detected platforms: Flutter, web, other

No platform restriction found in primary library package:ajwah_bloc/ajwah_bloc.dart.

Dependencies

Package Constraint Resolved Available
Direct dependencies
Dart SDK >=2.1.0 <3.0.0
meta >=1.0.0 <3.0.0 1.1.7
rxdart >=0.19.0 <2.0.0 0.22.1+1
Dev dependencies
build_runner ^1.6.6
build_test ^0.10.7+3
dartdoc ^0.28.3+2
test ^1.6.3
test_coverage ^0.2.3