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 931e1b5 commit b898626
Show file tree
Hide file tree
Showing 11 changed files with 193 additions and 182 deletions.
1 change: 0 additions & 1 deletion .travis.yml
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
@@ -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
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
3 changes: 1 addition & 2 deletions analysis_options.yaml
Expand Up @@ -2,7 +2,6 @@ analyzer:
strong-mode:
implicit-casts: false
implicit-dynamic: false

linter:
rules:
- cancel_subscriptions
Expand All @@ -12,4 +11,4 @@ linter:
- list_remove_unrelated_type
- test_types_in_equals
- unrelated_type_equality_checks
- valid_regexps
- valid_regexps
19 changes: 9 additions & 10 deletions doc/combine_reducers.md
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),

This comment has been minimized.

Copy link
@kmanadkat

kmanadkat Jun 20, 2022

Hi @brianegan
Do we need to add return for default state if none of the types match? Or this is implicit by use of combineReducers ?

thanks

]);
// Use the new itemsReducer just like we did before
Expand Down
116 changes: 46 additions & 70 deletions lib/src/utils.dart
@@ -1,22 +1,7 @@
import 'package:redux/src/store.dart';

/// A type safe Reducer
typedef TypedReducer<State, Action> = State Function(
State state,
Action action,
);

/// A class that binds an Action of a given type to a specific TypedReducer
class ReducerBinding<State, Action> {
final TypedReducer<State, Action> reducer;

ReducerBinding(this.reducer);

bool handlesAction(dynamic action) => action is Action;
}

/// A convenience function for binding Reducers to Actions of a given Type. This
/// allows for type safe [Middleware] and reduces boilerplate.
/// A convenience class for binding Reducers to Actions of a given Type. This
/// allows for type safe [Reducer]s and reduces boilerplate.
///
/// ### Example
///
Expand Down Expand Up @@ -98,45 +83,31 @@ class ReducerBinding<State, Action> {
/// // 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),
/// ]);
/// ```
Reducer<State> combineTypedReducers<State>(
List<ReducerBinding<State, dynamic>> bindings) {
return (State state, dynamic action) {
return bindings.fold(state, (currentState, binder) {
if (binder.handlesAction(action)) {
return binder.reducer(state, action);
} else {
return currentState;
}
});
};
}
class TypedReducer<State, Action> implements ReducerClass<State> {
final State Function(State state, Action action) reducer;

/// A type safe Middleware.
typedef TypedMiddleware<State, Action> = void Function(
Store<State> store,
Action action,
NextDispatcher next,
);
TypedReducer(this.reducer);

/// A class that binds an Action of a given type to a specific TypedMiddleware
class MiddlewareBinding<State, Action> {
final TypedMiddleware<State, Action> middleware;

MiddlewareBinding(this.middleware);
@override
State call(State state, dynamic action) {
if (action is Action) {
return reducer(state, action);
}

bool handlesAction(dynamic action) => action is Action;
return state;
}
}

/// A convenience function for binding a piece of Middleware to an Action
/// 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
Expand Down Expand Up @@ -213,7 +184,7 @@ class MiddlewareBinding<State, Action> {
/// // remain dynamic.
/// final saveItemsMiddleware = (
/// Store<AppState> store,
/// action,
/// dynamic action,
/// NextDispatcher next,
/// ) {
/// next(action);
Expand All @@ -223,27 +194,32 @@ class MiddlewareBinding<State, Action> {
///
/// // We will then wire up specific types of actions to a List of Middleware
/// // that handle those actions.
/// final List<Middleware<AppState>> middleware = combineTypedMiddleware([
/// new MiddlewareBinder<AppState, LoadTodosAction>(loadItemsMiddleware),
/// new MiddlewareBinder<AppState, AddTodoAction>(saveItemsMiddleware),
/// new MiddlewareBinder<AppState, ClearCompletedAction>(saveItemsMiddleware),
/// new MiddlewareBinder<AppState, ToggleAllAction>(saveItemsMiddleware),
/// new MiddlewareBinder<AppState, UpdateTodoAction>(saveItemsMiddleware),
/// new MiddlewareBinder<AppState, TodosLoadedAction>(saveItemsMiddleware),
/// ]);
/// 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),
/// ];
/// ```
List<Middleware<State>> combineTypedMiddleware<State>(
List<MiddlewareBinding<State, dynamic>> bindings) {
return bindings
.map((binder) =>
(Store<State> store, dynamic action, NextDispatcher next) {
if (binder.handlesAction(action)) {
binder.middleware(store, action, next);
} else {
next(action);
}
})
.toList();
class TypedMiddleware<State, Action> implements MiddlewareClass<State> {
final void Function(
Store<State> store,
Action action,
NextDispatcher next,
) middleware;

TypedMiddleware(this.middleware);

@override
void call(Store<State> store, dynamic action, NextDispatcher next) {
if (action is Action) {
middleware(store, action, next);
} else {
next(action);
}
}
}

/// Defines a utility function that combines several reducers.
Expand Down
4 changes: 2 additions & 2 deletions pubspec.yaml
Expand Up @@ -4,10 +4,10 @@ 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 <2.0.0'
sdk: '>=1.24.0 <3.0.0'

dev_dependencies:
browser: '>=0.10.0+2 <0.11.0'
Expand Down
32 changes: 11 additions & 21 deletions test/middleware_test.dart
@@ -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
@@ -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

0 comments on commit b898626

Please sign in to comment.