Skip to content

Commit

Permalink
Version 3.0.0: Dart 2 support
Browse files Browse the repository at this point in the history
  • Loading branch information
brianegan committed Mar 29, 2018
1 parent 6b55ed6 commit 748437d
Show file tree
Hide file tree
Showing 10 changed files with 240 additions and 111 deletions.
1 change: 0 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,3 @@ with_content_shell: false
dart_task:
- test: --platform vm
- dartanalyzer: --fatal-warnings lib
- dartfmt
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
# 3.0.0

* Dart 2 support
* Remove `ReducerBinding`, use `TypedReducer`
* Remove `combineTypedReducer`. Use `combineReducers` with normal reducers & `TypedReducer`s.
* Remove `MiddlewareBinding`, use `TypedMiddleware`.
* Remove `combineTypedMiddleware` -- no longer needed! Just create a normal `List<Middleware<State>>`!

# 2.1.1

* Ensure the repo is 100% healthy.
Expand Down
19 changes: 12 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,16 @@

[![Build Status](https://api.travis-ci.org/johnpryan/redux.dart.svg?branch=master)](https://travis-ci.org/johnpryan/redux.dart)

[Redux](http://redux.js.org/) for Dart using generics for typed State. It includes a rich ecosystem of [Docs](#docs), [Middleware](#middleware), [Dev Tools](#dev-tools) and [platform integrations](#platform-integrations).
[Redux](http://redux.js.org/) for Dart using generics for typed State. It includes a rich ecosystem of [Docs](#docs), [Middleware](#middleware), [Dev Tools](#dev-tools) and can be combined with Flutter using the [flutter_redux](https://pub.dartlang.org/packages/flutter_redux) package.

## Redux 3.0.0 Migration & Dart 2 support

In order to support Dart 2, some of the APIs needed to change. The good news: This actually simplifies Redux! The bad news: you may need to update your projects.

* Change `ReducerBinding` to `TypedReducer`
* Remove `combineTypedReducer`. Use `combineReducers` with a combination of normal reducers and/or `TypedReducer`s.
* Change `MiddlewareBinding` to `TypedMiddleware`.
* Remove `combineTypedMiddleware` -- no longer needed! Just create a normal `List<Middleware<State>>`!

## Usage

Expand Down Expand Up @@ -81,14 +90,14 @@ render(int state) {

* [Motivation and Principles](https://github.com/johnpryan/redux.dart/blob/master/doc/why.md) - Learn why Redux might make sense for your app and the principles behind it.
* [Basics](https://github.com/johnpryan/redux.dart/blob/master/doc/basics.md) - Introduction to the core concepts in Redux
* [Async](https://github.com/johnpryan/redux.dart/blob/master/doc/async.md) - Learn how to make async calls, such as to a web service or local database, with Redux.
* [Combining Reducers](https://github.com/johnpryan/redux.dart/blob/master/doc/combine_reducers.md) - `combineReducers` works a bit differently in Dart than it does in JS. Learn why, and how to combine reducers in a type-safe way!
* [Async with Middleware](https://github.com/johnpryan/redux.dart/blob/master/doc/async.md) - Learn how to make async calls, such as to a web service or local database, with Redux.
* [API Documentation](https://www.dartdocs.org/documentation/redux/latest) - Rich documentation included in the source code and generated by DartDoc.

## Flutter Examples

* [flutter_architecture_samples](https://gitlab.com/brianegan/flutter_architecture_samples/tree/master/example/redux) - A Todo List App with local storage. Includes a README describing how to combine Redux with Flutter effectively.
* [Counter example in the flutter_redux](https://gitlab.com/brianegan/flutter_redux/tree/master/example) library - A very simple counter example
* [Counter example from flutter_redux](https://gitlab.com/brianegan/flutter_redux/tree/master/example) library - A very simple counter example
* [flutter_redux_todo_list](https://github.com/xqwzts/flutter-redux-todo-list) - A basic flutter_redux todo list example by [xqwzts](https://github.com/xqwzts).
* [Weight Tracking App](https://github.com/MSzalek-Mobile/weight_tracker/) - Demonstrates how to combine Redux with Firebase to build a Flutter app by [MarcinusX](https://github.com/MarcinusX).
* [Reduxing Flutter Firebase App](https://marcinszalek.pl/flutter/reduxing-flutter/) - Article describing how the author combined Redux with Firebase.
Expand All @@ -110,10 +119,6 @@ To launch the examples in your browser:
* [redux_future](https://pub.dartlang.org/packages/redux_future) - For handling Dart Futures that are dispatched as Actions.
* [redux_epics](https://pub.dartlang.org/packages/redux_epics) - Middleware that allows you to work with Dart Streams of Actions to perform async work.

## Platform Integrations

* [flutter_redux](https://pub.dartlang.org/packages/flutter_redux) - A library that connects Widgets to a Redux Store

## Dev Tools

The [redux_dev_tools](https://pub.dartlang.org/packages/redux_dev_tools) library allows you to create a `DevToolsStore` during dev mode in place of a normal Redux `Store`.
Expand Down
19 changes: 9 additions & 10 deletions doc/combine_reducers.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ AppState reducer(AppState state, action) {
## Combining Reducers

Now, let's take a look at this same example with function composition. The app state reducer will work exactly as it
did before, but we'll break the reducer down into smaller parts!
did before, but we'll break the reducer down into smaller parts.

This will make it easier to split up and test your app, and even means you can move the reducer functions into their own
directories or even separate Dart packages!
Expand Down Expand Up @@ -166,11 +166,10 @@ AppState appStateReducer(AppState state, action) => new AppState(
Now that we know how to compose (combine) reducers, we can look at some utilities in this library to help make this
process easier.

We'll focus on two utilities included with Redux: `combineTypedReducers` and `ReducerBinding`s.

In this example, our `itemsReducer` will be created by the `combineTypedReducers` function. Instead of checking for each
type of action and calling it manually, we can setup a list of `ReducerBinding`s.
We'll focus on two utilities included with Redux: `combineReducers` and `TypedReducer`s.

In this example, our `itemsReducer` will be created by the `combineReducers` function. Instead of checking for each
type of action and calling it manually, we can setup a list of `TypedReducer`s.

```dart
// Start with our same type-safe, smaller reducers we had before.
Expand All @@ -183,13 +182,13 @@ List<String> removeItemReducer(List<String> items, RemoveItemAction action) {
}
// Compose these smaller functions into the full `itemsReducer`.
Reducer<List<String>> itemsReducer = combineTypedReducers([
// Each `ReducerBinding` will glue Actions of a certain type to the given
Reducer<List<String>> itemsReducer = combineReducers([
// Each `TypedReducer` will glue Actions of a certain type to the given
// reducer! This means you don't need to write a bunch of `if` checks
// manually, and can quickly scan the list of `ReducerBinding`s to see what
// manually, and can quickly scan the list of `TypedReducer`s to see what
// reducer handles what action.
new ReducerBinding<List<String>, AddItemAction>(addItemReducer),
new ReducerBinding<List<String>, RemoveItemAction>(removeItemReducer),
new TypedReducer<List<String>, AddItemAction>(addItemReducer),
new TypedReducer<List<String>, RemoveItemAction>(removeItemReducer),
]);
// Use the new itemsReducer just like we did before
Expand Down
111 changes: 103 additions & 8 deletions lib/src/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -83,13 +83,13 @@ import 'package:redux/src/store.dart';
/// // We will then wire up specific types of actions to our reducer functions
/// // above. This will return a new Reducer<AppState> which puts everything
/// // together!.
/// final Reducer<AppState> appReducer = combineTypedReducers([
/// new ReducerBinding<AppState, LoadTodosAction>(loadItemsReducer),
/// new ReducerBinding<AppState, UpdateItemsAction>(updateItemsReducer),
/// new ReducerBinding<AppState, AddItemAction>(addItemReducer),
/// new ReducerBinding<AppState, RemoveItemAction>(removeItemReducer),
/// new ReducerBinding<AppState, ShuffleItemAction>(shuffleItemsReducer),
/// new ReducerBinding<AppState, ReverseItemAction>(reverseItemsReducer),
/// final Reducer<AppState> appReducer = combineReducers([
/// new TypedReducer<AppState, LoadTodosAction>(loadItemsReducer),
/// new TypedReducer<AppState, UpdateItemsAction>(updateItemsReducer),
/// new TypedReducer<AppState, AddItemAction>(addItemReducer),
/// new TypedReducer<AppState, RemoveItemAction>(removeItemReducer),
/// new TypedReducer<AppState, ShuffleItemAction>(shuffleItemsReducer),
/// new TypedReducer<AppState, ReverseItemAction>(reverseItemsReducer),
/// ]);
/// ```
class TypedReducer<State, Action> implements ReducerClass<State> {
Expand All @@ -107,7 +107,102 @@ class TypedReducer<State, Action> implements ReducerClass<State> {
}
}

// A type-safe Middleware
/// A convenience type for binding a piece of Middleware to an Action
/// of a specific type. Allows for Type Safe Middleware and reduces boilerplate.
///
/// ### Example
///
/// In order to see what this utility function does, let's take a look at a
/// regular example of running Middleware based on the Type of an action.
///
/// ```
/// class AppState {
/// final List<Item> items;
///
/// AppState(this.items);
/// }
/// class LoadItemsAction {}
/// class UpdateItemsAction {}
/// class AddItemAction{}
/// class RemoveItemAction {}
/// class ShuffleItemsAction {}
/// class ReverseItemsAction {}
/// class ItemsLoadedAction<Item> {
/// final List<Item> items;
///
/// ItemsLoadedAction(this.items);
/// }
///
/// final loadItems = () { /* Function that loads a Future<List<Item>> */}
/// final saveItems = (List<Item> items) { /* Function that persists items */}
///
/// final middleware = (Store<AppState> store, action, NextDispatcher next) {
/// if (action is LoadItemsAction) {
/// loadItems()
/// .then((items) => store.dispatch(new ItemsLoaded(items))
/// .catchError((_) => store.dispatch(new ItemsNotLoaded());
///
/// next(action);
/// } else if (action is UpdateItemsAction ||
/// action is AddItemAction ||
/// action is RemoveItemAction ||
/// action is ShuffleItemsAction ||
/// action is ReverseItemsAction) {
/// next(action);
///
/// saveItems(store.state.items);
/// } else {
/// next(action);
/// }
/// };
/// ```
///
/// This works fine if you have one or two actions to handle, but you might
/// notice it's getting a bit messy already. Let's see how this lib helps clean
/// it up.
///
/// ```
/// // First, let's start by breaking up our functionality into two middleware
/// // functions.
/// //
/// // The loadItemsMiddleware will only handle the `LoadItemsAction`s that
/// // are dispatched, so we can annotate the Type of action.
/// final loadItemsMiddleware = (
/// Store<AppState> store,
/// LoadItemsAction action,
/// NextDispatcher next,
/// ) {
/// loadItems()
/// .then((items) => store.dispatch(new ItemsLoaded(items))
/// .catchError((_) => store.dispatch(new ItemsNotLoaded());
///
/// next(action);
/// }
///
/// // The saveItemsMiddleware handles all actions that change the Items, but
/// // does not depend on the payload of the action. Therefore, `action` will
/// // remain dynamic.
/// final saveItemsMiddleware = (
/// Store<AppState> store,
/// dynamic action,
/// NextDispatcher next,
/// ) {
/// next(action);
///
/// saveItems(store.state.items);
/// }
///
/// // We will then wire up specific types of actions to a List of Middleware
/// // that handle those actions.
/// final List<Middleware<AppState>> middleware = [
/// new TypedMiddleware<AppState, LoadTodosAction>(loadItemsMiddleware),
/// new TypedMiddleware<AppState, AddTodoAction>(saveItemsMiddleware),
/// new TypedMiddleware<AppState, ClearCompletedAction>(saveItemsMiddleware),
/// new TypedMiddleware<AppState, ToggleAllAction>(saveItemsMiddleware),
/// new TypedMiddleware<AppState, UpdateTodoAction>(saveItemsMiddleware),
/// new TypedMiddleware<AppState, TodosLoadedAction>(saveItemsMiddleware),
/// ];
/// ```
class TypedMiddleware<State, Action> implements MiddlewareClass<State> {
final void Function(
Store<State> store,
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ authors:
- Brian Egan <brian@brianegan.com>
description: Redux for Dart
homepage: http://github.com/johnpryan/redux.dart
version: 2.1.1
version: 3.0.0

environment:
sdk: '>=1.24.0 <3.0.0'
Expand Down
32 changes: 11 additions & 21 deletions test/middleware_test.dart
Original file line number Diff line number Diff line change
@@ -1,22 +1,16 @@
import 'package:redux/redux.dart';
import 'package:test/test.dart';

import 'test_data.dart';

void main() {
group('Middleware', () {
test('can be defined as a Class', () {
expect(
new IncrementMiddleware(),
const isInstanceOf<Middleware<String>>(),
);
});

test('are invoked by the store', () {
final middleware = new IncrementMiddleware();
final store = new Store(
final store = new Store<String>(
stringReducer,
initialState: 'hello',
middleware: <Middleware<String>>[middleware],
middleware: [middleware],
);
store.dispatch('test');
expect(middleware.counter, equals(1));
Expand All @@ -25,11 +19,10 @@ void main() {
test('are applied in the correct order', () {
final middleware1 = new IncrementMiddleware();
final middleware2 = new IncrementMiddleware();
final middleware = <Middleware<String>>[middleware1, middleware2];
final store = new Store(
final store = new Store<String>(
stringReducer,
initialState: 'hello',
middleware: middleware,
middleware: [middleware1, middleware2],
);

final order = <String>[];
Expand All @@ -44,11 +37,10 @@ void main() {
test('actions can be dispatched multiple times', () {
final middleware1 = new ExtraActionIncrementMiddleware();
final middleware2 = new IncrementMiddleware();
final middleware = <Middleware<String>>[middleware1, middleware2];
final store = new Store(
final store = new Store<String>(
stringReducer,
initialState: 'hello',
middleware: middleware,
middleware: [middleware1, middleware2],
);

final order = <String>[];
Expand All @@ -64,11 +56,10 @@ void main() {
test('actions can be dispatched through entire chain', () {
final middleware1 = new ExtraActionIfDispatchedIncrementMiddleware();
final middleware2 = new IncrementMiddleware();
final middleware = <Middleware<String>>[middleware1, middleware2];
final store = new Store(
final store = new Store<String>(
stringReducer,
initialState: 'hello',
middleware: middleware,
middleware: [middleware1, middleware2],
);

final order = <String>[];
Expand All @@ -88,11 +79,10 @@ void main() {
test('actions can be dispatched through entire chain', () {
final middleware1 = new ExtraActionIfDispatchedIncrementMiddleware();
final middleware2 = new IncrementMiddleware();
final middleware = <Middleware<String>>[middleware1, middleware2];
final store = new Store(
final store = new Store<String>(
stringReducer,
initialState: 'hello',
middleware: middleware,
middleware: [middleware1, middleware2],
);

final order = <String>[];
Expand Down
12 changes: 3 additions & 9 deletions test/store_test.dart
Original file line number Diff line number Diff line change
@@ -1,27 +1,21 @@
import 'package:redux/redux.dart';
import 'package:test/test.dart';

import 'test_data.dart';

void main() {
group('Store', () {
test('calls the reducer when an action is fired', () {
final store = new Store(stringReducer, initialState: 'Hello');
final store = new Store<String>(stringReducer, initialState: 'Hello');
final action = 'test';
store.dispatch(action);
expect(store.state, equals(action));
});

test('reducers can be a Class', () {
expect(
new StringReducer(),
const isInstanceOf<Reducer<String>>(),
);
});

test('canceled subscriber should not be notified', () {
var subscriber1Called = false;
var subscriber2Called = false;
final store = new Store(
final store = new Store<String>(
stringReducer,
initialState: 'hello',
syncStream: true,
Expand Down
1 change: 1 addition & 0 deletions test/test_data.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'dart:async';

import 'package:redux/redux.dart';

String reducer1(String state, dynamic action) {
Expand Down
Loading

0 comments on commit 748437d

Please sign in to comment.