ajwah_bloc 2.0.1+1 ajwah_bloc: ^2.0.1+1 copied to clipboard
A reactive state management library. Manage your application's states, effects, and actions easy way. Make apps more scalable with a unidirectional data-flow.
ajwah_bloc #
A reactive state management library. Manage your application's states, effects, and actions easy way. Make apps more scalable with a unidirectional data-flow. flutter demo | src
Declare your store as a global variable or enable exposeApiGlobally:true
.
final store = createStore();
//or
createStore( exposeApiGlobally:true);
Now register states as much as you want and consume them where ever you want in your app.
//register [counter] state
store.registerState<int>(
stateName: 'counter',
initialState: 0,
mapActionToState: (state, action, emit) {
switch (action.type) {
case 'inc':
emit(state + 1);
break;
case 'dec':
emit(state - 1);
break;
default:
}
},
);
//consuming
store.select('counter').listen(print); // 0,1,0,1
//dispatching actions
store.dispatch(Action(type: 'inc'));
store.dispatch(Action(type: 'dec'));
store.dispatch(Action(type: 'inc'));
You can easily filter out actions using the optional filterActions:(action)=>bool
param of registerState
method.
For filtering out the dec
action from the counter
state:
//register [counter] state
store.registerState<int>(
stateName: 'counter',
initialState: 0,
filterActions: (action) => action.type != 'dec',
mapActionToState: (state, action, emit) {
switch (action.type) {
case 'inc':
emit(state + 1);
break;
case 'dec':
emit(state - 1);
break;
default:
}
},
);
//consuming
store.select('counter').listen(print); // 0,1,2
//dispatching actions
store.dispatch(Action(type: 'inc'));
store.dispatch(Action(type: 'dec'));
store.dispatch(Action(type: 'inc'));
Now dec
action is useless. Let's add an effect
on dec
action:
store.registerEffect(
(action$, store) =>
action$.whereType('dec')
.map((event) => Action(type: 'inc')),
effectKey: 'test');
Here we are capturing the dec
action using whereType
and then map the action as an inc
action
//register [counter] state
store.registerState<int>(
stateName: 'counter',
initialState: 0,
filterActions: (action) => action.type != 'dec',
mapActionToState: (state, action, emit) {
switch (action.type) {
case 'inc':
emit(state + 1);
break;
case 'dec':
emit(state - 1);
break;
default:
}
},
);
//consuming
store.select('counter').listen(print); // 0,1,2,3
//effect on dec action - so that it works as inc
store.registerEffect(
(action$, store) =>
action$.whereType('dec').map((event) => Action(type: 'inc')),
effectKey: 'test');
//dispatching actions
store.dispatch(Action(type: 'inc'));
store.dispatch(Action(type: 'dec'));
store.dispatch(Action(type: 'inc'));
If you want to log all the action and state changed - just use the exportState()
function. Call this function just after your createStore()
function.
var store = createStore();
store.exportState().listen(print);
output looks like:
[Action(type: @@INIT), {counter: 0}]
[Action(type: registerState(counter)), {counter: 0}]
0
[Action(type: registerEffect(test)), {counter: 0}]
[Action(type: inc), {counter: 1}]
1
[Action(type: dec), {counter: 1}]
[Action(type: inc), {counter: 2}]
2
[Action(type: inc), {counter: 3}]
3
You can import state also:
store.importState({'counter': 100});
We have covered the basic of ajwah_bloc. Now we see:
- how to consume states in the flutter widget?
- how to make your app a way more declaratives?
- how to combine multiple states and make a single stream?
- testing ajwah_bloc
Consuming counter
state through StreamBuilder
widget:
StreamBuilder<int>(
stream: store.select('counter'),
initialData: 0,
builder:(context, snapshot) => Text(snapshot.data.count.toString()),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
RaisedButton(
onPressed: () => store.dispatch(store.Action(type: 'inc')),
child: Text('Inc'),
),
RaisedButton(
onPressed: () => sstore.dispatch(store.Action(type: 'dec')),
child: Text('Dec'),
),
],
),
Make your app a way more declaretives simply dispatching the action, here you see an example of conditionaly rendering a widged having taps on two buttons [Show] and [Hide], and consuming those actions as you needed.
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
RaisedButton(
onPressed: () => store.dispatch(store.Action(type: 'show-widget')),
child: Text('Show'),
),
RaisedButton(
onPressed: () => sstore.dispatch(store.Action(type: 'hide-whiget')),
child: Text('Hide'),
),
],
),
StreamBuilder<String>(
stream: store.actions
.whereTypes(['show-widget', 'hide-widget'])
.map((action) => action.type),
initialData: '',
builder:(context, snapshot) =>
snapshot.data == 'show-widget' ? DynamicWidget() : Container(),
),
Using selectMany(callback)
function we can easily combine multiple states and retuen single stream data.
Suppose we have two states:
todo
search-category
Now we want to consume these two states together. So that we get the latest filtered result on todo
as well as search-category
states.
Stream<List<Todo>> getFilteredTodos() => store.selectMany((state) {
final todos = ['todo'] as List<Todo>;
switch (state['search-category']) {
case TodoActionTypes.active:
return todos.where((todo) => !todo.completed).toList();
case TodoActionTypes.completed:
return todos.where((todo) => todo.completed).toList();
default:
return todos;
}
});
Testing: We need to add the testing dependency ajwah_bloc_test
then here you go:
todo_test.dart
import 'package:ajwah_bloc/ajwah_bloc.dart';
import 'package:ajwah_bloc_test/ajwah_bloc_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:example/main.dart';
void main() {
AjwahStore store;
setUp(() {
store = createStore(exposeApiGlobally: true);
registerTodoStates();
});
tearDown(() {
store.dispose();
});
ajwahTest<List<Todo>>(
'Render the default todos',
build: () => getFilteredTodos(),
expect: [isA<List<Todo>>()],
verify: (models) {
expect(3, models[0].length);
},
);
ajwahTest<List<Todo>>(
'Editing the todo on done',
build: () => getFilteredTodos(),
act: () =>
store.dispatch(TodoAction(type: TodoActionTypes.toggle, id: 'todo-0')),
skip: 1,
expect: [isA<List<Todo>>()],
verify: (models) {
expect(true, models[0][0].completed);
},
);
ajwahTest<List<Todo>>(
'Add new todo',
build: () => getFilteredTodos(),
act: () => store.dispatch(
TodoAction(type: TodoActionTypes.add, description: 'new todo')),
skip: 1,
expect: [isA<List<Todo>>()],
verify: (models) {
expect('new todo', models[0][3].description);
},
);
ajwahTest<List<Todo>>(
'removing the first todo',
build: () => getFilteredTodos(),
act: () =>
store.dispatch(TodoAction(type: TodoActionTypes.remove, id: 'todo-0')),
skip: 1,
expect: [isA<List<Todo>>()],
verify: (models) {
expect('todo-1,todo-2', models[0].map((e) => e.id).join(','));
},
);
ajwahTest<List<Todo>>(
'show only uncomplete todos',
build: () => getFilteredTodos(),
act: () {
store.dispatch(TodoAction(type: TodoActionTypes.toggle, id: 'todo-0'));
store.dispatch(TodoFilterAction(type: TodoActionTypes.active));
},
skip: 2,
expect: [isA<List<Todo>>()],
verify: (models) {
expect(2, models[0].length);
},
);
}
Api #
dispatch(Action action)
Stream<T> select<T>(String stateName)
Stream<T> selectMany<T>(T callback(Map<String, dynamic> state))
void registerState<S>(
{@required String stateName,
@required S initialState,
@required MapActionToStateCallback<S> mapActionToState})
void unregisterState({@required String stateName})
void registerEffect(EffectCallback callback, {@required String effectKey})
void unregisterEffect({@required String effectKey})
BehaviorSubject<Action> get dispatcher
Actions get actions
T getState<T>({@required String stateName})
Stream<List<dynamic>> exportState()
void importState(Map<String, dynamic> state)
void dispose()