Redux Saga Middleware Dart and Flutter
Redux Saga Middleware for Dart and Flutter is a library that aims to make application side effects (i.e. asynchronous things like data fetching and impure things like accessing the browser cache) easier to manage, more efficient to execute, easy to test, and better at handling failures.
The mental model is that a saga is like a separate thread in your application that's solely responsible for side effects. Redux Saga
is a redux middleware, which means this thread can be started, paused and cancelled from the main application with normal redux actions, it has access to the full redux application state and it can dispatch redux actions as well.
It uses synchronous generator functions to make those asynchronous flows easy to read, write and test. By doing so, these asynchronous flows look like your standard synchronous code. (kind of like async
/await
, but generators have a few more awesome features we need)
You might've used redux_thunk
before to handle your data fetching. Contrary to redux thunk, you don't end up in callback hell, you can test your asynchronous flows easily and your actions stay pure.
Redux Saga
is ported and compatible with javascript redux-saga implementation and its documentation. If you use Javascript redux-saga before than you can check Migration from Javascript documentation to get help about migration.
Package and installation details can be found at pub.dev.
Usage Example
Suppose we have a UI to fetch some user data from a remote server when a button is clicked. (For brevity, we'll just show the action triggering code.)
class MyComponent extends StatelessWidget {
@override
Widget build(BuildContext context) {
...
RaisedButton(
onPressed: () => StoreProvider.of(context).dispatch(UserFetchRequested()),
child: Text('Fetch Users'),
)
...
);
}
}
The Component dispatches a plain Object action to the Store. We'll create a Saga that watches for all UserFetchRequested
actions and triggers an API call to fetch the user data.
sagas.dart
import 'package:redux_saga/redux_saga.dart';
import 'Api.dart';
// worker Saga: will be fired on UserFetchRequested actions
fetchUser({dynamic action}) sync* {
yield Try(() sync* {
var user = Result();
yield Call(Api.fetchUser, args: [action.payload.userId], result: user);
yield Put(UserFetchSuceeded(user: user.value));
}, Catch: (e, s) sync* {
yield Put(UserFetchFailed(message: e.message));
});
}
// Starts fetchUser on each dispatched `UserFetchRequested` action.
// Allows concurrent fetches of user.
mySaga() sync* {
yield TakeEvery(fetchUser, pattern: UserFetchRequested);
}
// Alternatively you may use TakeLatest.
//
// Does not allow concurrent fetches of user. If "UserFetchRequested" gets
// dispatched while a fetch is already pending, that pending fetch is cancelled
// and only the latest one will be run.
mySaga() sync* {
yield TakeLatest(fetchUser, pattern: UserFetchRequested);
}
To run our Saga, we'll have to connect it to the Redux Store using the Redux Saga
middleware.
main.dart
import 'package:redux/redux.dart';
import 'package:redux_saga/redux_saga.dart';
// create the saga middleware
var sagaMiddleware = createSagaMiddleware();
// Create store and apply middleware
final store = Store(
counterReducer,
initialState: 0,
middleware: [applyMiddleware(sagaMiddleware)],
);
//connect to store
sagaMiddleware.setStore(store);
// then run the saga
sagaMiddleware.run(mySaga);
// render the application
Documentation
Examples
Vanilla Counter
Web based counter demo.
Counter
Demo using flutter
and high-level API TakeEvery
.
Shopping Cart
A basic shopping cart example using flutter
.
Async App
A demo using async functions to fetch reddit posts.
License
Copyright (c) 2020 Bilal Uslu.
Licensed under The MIT License (MIT).
Libraries
- redux_saga
redux_saga
is a library that aims to make application side effects (i.e. asynchronous things like data fetching and impure things like accessing the browser cache) easier to manage, more efficient to execute, easy to test, and better at handling failures.