A set of utilities that allow you to easily consume a Redux Store to build Flutter Widgets.
This package is built to work with Redux.dart 3.0.0+.
StoreProvider
- The base Widget. It will pass the given Redux Store to all descendants that request it.StoreBuilder
- A descendant Widget that gets the Store from a StoreProvider
and passes it to a Widget builder
function.StoreConnector
- A descendant Widget that gets the Store from the nearest StoreProvider
ancestor, converts the Store
into a ViewModel
with the given converter
function, and passes the ViewModel
to a builder
function. Any time the Store emits a change event, the Widget will automatically be rebuilt. No need to manage subscriptions!Dart 2 requires more strict typing (yay!), and gives us the option to make getting the Store from the StoreProvider more convenient!
new StoreProvider(...)
to new StoreProvider<StateClass>(...)
in your Widget tree. new StoreProvider.of(context).store
to StoreProvider.of<StateClass>(context)
if you're directly fetching the Store<AppState>
yourself from the StoreProvider<AppState>
. No need to access the store
field directly any more since Dart 2 can now infer the proper type with a static function :)Let's demo the basic usage with the all-time favorite: A counter example!
Note: This example requires flutter_redux 0.4.0+ and Dart 2! If you're using Dart 1, see the old example.
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart';
// One simple action: Increment
enum Actions { Increment }
// The reducer, which takes the previous count and increments it in response
// to an Increment action.
int counterReducer(int state, dynamic action) {
if (action == Actions.Increment) {
return state + 1;
}
return state;
}
void main() {
// Create your store as a final variable in a base Widget. This works better
// with Hot Reload than creating it directly in the `build` function.
final store = new Store<int>(counterReducer, initialState: 0);
runApp(new FlutterReduxApp(
title: 'Flutter Redux Demo',
store: store,
));
}
class FlutterReduxApp extends StatelessWidget {
final Store<int> store;
final String title;
FlutterReduxApp({Key key, this.store, this.title}) : super(key: key);
@override
Widget build(BuildContext context) {
// The StoreProvider should wrap your MaterialApp or WidgetsApp. This will
// ensure all routes have access to the store.
return new StoreProvider<int>(
// Pass the store to the StoreProvider. Any ancestor `StoreConnector`
// Widgets will find and use this value as the `Store`.
store: store,
child: new MaterialApp(
theme: new ThemeData.dark(),
title: title,
home: new Scaffold(
appBar: new AppBar(
title: new Text(title),
),
body: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
new Text(
'You have pushed the button this many times:',
),
// Connect the Store to a Text Widget that renders the current
// count.
//
// We'll wrap the Text Widget in a `StoreConnector` Widget. The
// `StoreConnector` will find the `Store` from the nearest
// `StoreProvider` ancestor, convert it into a String of the
// latest count, and pass that String to the `builder` function
// as the `count`.
//
// Every time the button is tapped, an action is dispatched and
// run through the reducer. After the reducer updates the state,
// the Widget will be automatically rebuilt with the latest
// count. No need to manually manage subscriptions or Streams!
new StoreConnector<int, String>(
converter: (store) => store.state.toString(),
builder: (context, count) {
return new Text(
count,
style: Theme.of(context).textTheme.display1,
);
},
)
],
),
),
// Connect the Store to a FloatingActionButton. In this case, we'll
// use the Store to build a callback that with dispatch an Increment
// Action.
//
// Then, we'll pass this callback to the button's `onPressed` handler.
floatingActionButton: new StoreConnector<int, VoidCallback>(
converter: (store) {
// Return a `VoidCallback`, which is a fancy name for a function
// with no parameters. It only dispatches an Increment action.
return () => store.dispatch(Actions.Increment);
},
builder: (context, callback) {
return new FloatingActionButton(
// Attach the `callback` to the `onPressed` attribute
onPressed: callback,
tooltip: 'Increment',
child: new Icon(Icons.add),
);
},
),
),
),
);
}
}
One question that reasonable people might ask: Why do you need all of this if StatefulWidget
exists?
My advice is the same as the original Redux.JS author: If you've got a simple app, use the simplest thing possible. In Flutter, StatefulWidget
is perfect for a simple counter app.
However, say you have more complex app, such as an E-commerce app with a Shopping Cart. The Shopping Cart should appear on multiple screens in your app and should be updated by many different types of Widgets on those different screens (An "Add Item to Cart" Widget on all your Product Screens, "Remove Item from Cart" Widget on the Shopping Cart Screen, "Change quantity" Widgets, etc).
Additionally, you definitely want to test this logic, as it's the core business logic to your app!
Now, in this case, you could create a Testable ShoppingCart
class as a Singleton or Create a Root StatefulWidget
that passes the ShoppingCart
Down Down Down through your widget hierarchy to the "add to cart" or "remove from cart" Widgets .
Singletons can be problematic for testing, and Flutter doesn't have a great Dependency Injection library (such as Dagger2) just yet, so I'd prefer to avoid those.
Yet passing the ShoppingCart all over the place can get messy. It also means it's way harder to move that "Add to Item" button to a new location, b/c you'd need up update the Widgets throughout your app that passes the state down.
Furthermore, you'd need a way to Observe when the ShoppingCart
Changes so you could rebuild your Widgets when it does (from an "Add" button to an "Added" button, as an example).
One way to handle it would be to simply setState
every time the ShoppingCart
changes in your Root Widget, but then your whole app below the RootWidget would be required to rebuild as well! Flutter is fast, but we should be smart about what we ask Flutter to rebuild!
Therefore, redux
& redux_flutter
was born for more complex stories like this one. It gives you a set of tools that allow your Widgets to dispatch
actions in a naive way, then write the business logic in another place that will take those actions and update the ShoppingCart
in a safe, testable way.
Even more, once the ShoppingCart
has been updated in the Store
, the Store
will emit an onChange
event. This lets you listen to Store
updates and rebuild your UI in the right places when it changes! Now, you can separate your business logic from your UI logic in a testable, observable way, without having to Wire up a bunch of stuff yourself!
Similar patterns in Android are the MVP Pattern, or using Rx Observables to manage a View's state.
flutter_redux
simply handles passing your Store
down to all of your descendant StoreConnector
Widgets. If your State emits a change event, only the StoreConnector
Widgets and their descendants will be automatically rebuilt with the latest state of the Store
!
This allows you to focus on what your app should look like and how it should work without thinking about all the glue code to hook everything together!
onDidChange
-- This callback will be run after the ViewModel has changed and the builder method is calledonInitialBuild
-- This callback will be run after the builder method is called the first timeStoreProvider
now requires generic type info: new StoreProvider<AppState>
new StoreProvider.of(context).store
is now StoreProvider.of<AppState>(context)
onWillChange
. This function will be called before the builder and can be used for working with Imperative APIs, such as Navigator, TextEditingController, or TabController.onWillChange
. This function will be called before the builder and can be used for working with Imperative APIs, such as Navigator, TextEditingController, or TabController.onInit
was not called before the initial ViewModel is constructed. onDispose
function - The StoreConnector
and StoreBuilder
Widgets now accept an onDispose
function that will be run when the Widget is removed from the Widget tree (using State.dispose under the hood). The onDispose
function takes the Store as the first parameter, and can be used to dispatch actions that remove stale data from your State tree.onInit
function - The StoreConnector
and StoreBuilder
Widgets now accept an onInit
function that will be run the first time the Widget is created (using State.initState under the hood). The onInit
function takes the Store as the first parameter, and can be used to dispatch actions when your Widget is first starting up. This can be useful for data fetching.ignoreChange
function - StoreConnector
now takes an advanced usage / optional function ignoreChange
. It will be run on every store onChange
event to determine whether or not the ViewModel
and Widget
should be rebuilt. This can be useful in some edge cases, such as displaying information that has been deleted from the store while it is animating off screen. StoreProvider.of
a factory rather than a static methodInitial Version of the library.
Store
down to descendant Widgets using a StoreProvider
. StoreConnector
and StoreBuilder
Widgets that capture the Store
from the StoreProvider
and build a Widget in response.example/README.md
Add this to your package's pubspec.yaml file:
dependencies:
flutter_redux: ^0.5.3
You can install packages from the command line:
with Flutter:
$ flutter packages get
Alternatively, your editor might support flutter packages get
.
Check the docs for your editor to learn more.
Now in your Dart code, you can use:
import 'package:flutter_redux/flutter_redux.dart';
Version | Uploaded | Documentation | Archive |
---|---|---|---|
0.5.3 | Jan 7, 2019 |
|
|
0.5.2 | Jun 19, 2018 |
|
|
0.5.1 | May 31, 2018 |
|
|
0.5.0 | Mar 30, 2018 |
|
|
0.4.1 | Mar 27, 2018 |
|
|
0.4.0 | Mar 20, 2018 |
|
|
0.3.6 | Mar 19, 2018 |
|
|
0.3.5 | Feb 14, 2018 |
|
|
0.3.4 | Nov 25, 2017 |
|
|
0.3.3 | Nov 25, 2017 |
|
|
Popularity:
Describes how popular the package is relative to other packages.
[more]
|
99
|
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]
|
100
|
We analyzed this package on Feb 14, 2019, and provided a score, details, and suggestions below. Analysis was completed with status completed using:
Detected platforms: Flutter
References Flutter, and has no conflicting libraries.
Package | Constraint | Resolved | Available |
---|---|---|---|
Direct dependencies | |||
Dart SDK | >=2.0.0-dev.28.0 <3.0.0 | ||
flutter | 0.0.0 | ||
meta | >=1.1.1 <2.0.0 | 1.1.6 | 1.1.7 |
redux | >=3.0.0 <4.0.0 | 3.0.0 | |
Transitive dependencies | |||
collection | 1.14.11 | ||
sky_engine | 0.0.99 | ||
typed_data | 1.1.6 | ||
vector_math | 2.0.8 | ||
Dev dependencies | |||
flutter_test | |||
mockito | ^4.0.0 |