Skip to content

Commit b3496aa

Browse files
committed
feature: documentation
1 parent abdc96a commit b3496aa

File tree

9 files changed

+351
-5
lines changed

9 files changed

+351
-5
lines changed

.travis.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
language: dart
2+
sudo: false
3+
dart:
4+
- 2.0.0
5+
with_content_shell: false
6+
dart_task:
7+
- dartanalyzer: --fatal-warnings lib
8+
- dartfmt
9+
script:
10+
- set -e
11+
- pub run test
12+
- pub get --packages-dir
13+
- pub global activate coverage
14+
- pub global run coverage:collect_coverage --port=8111 -o coverage.json --resume-isolates --wait-paused &
15+
- pub global run coverage:format_coverage --package-root=packages --report-on lib --in coverage.json --out lcov.info --lcov
16+
after_success:
17+
- bash <(curl -s https://codecov.io/bash)

CHANGELOG.md

Whitespace-only changes.

README.md

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
# redux_toolkit
2+
3+
[![pub package](https://img.shields.io/pub/v/redux_toolkit.svg)](https://pub.dartlang.org/packages/redux_toolkit)
4+
[![Build Status](https://travis-ci.com/mrnkr/redux_toolkit.svg?branch=master)](https://travis-ci.com/mrnkr/redux_toolkit)
5+
[![codecov](https://codecov.io/gh/mrnkr/redux_toolkit/branch/master/graph/badge.svg)](https://codecov.io/gh/mrnkr/redux_toolkit)
6+
7+
Dart port of the official, opinionated, batteries-included toolset for efficient Redux development. Do check out the original [`redux-toolkit`](https://redux-toolkit.js.org/) to see what this lib is inspired on.
8+
9+
## Store setup
10+
11+
A friendly abstraction over the standard way of instantiating the `Store` class. It aims to provide good defaults to provide a smoother experience for us developers.
12+
13+
The defaults are:
14+
15+
- [`redux-thunk`](https://github.com/brianegan/redux_thunk) as the standard way to handle async operations.
16+
- Readily available [`redux_dev_tools`](https://github.com/brianegan/redux_dev_tools) and [`redux_remote_devtools`](https://github.com/MichaelMarner/dart-redux-remote-devtools)
17+
18+
```dart
19+
final store = await configureStore<AppState>((builder) {
20+
builder.withReducer(reducer);
21+
builder.withPreloadedState(AppState.initialState());
22+
23+
if (Config.reduxDevtoolsEnabled) {
24+
builder.usingDevtools(Config.reduxDevtoolsUrl);
25+
}
26+
});
27+
```
28+
29+
### API Reference
30+
31+
```dart
32+
Store<State> createStore<State>(StoreBuilderCallback<State>);
33+
typedef StoreBuilderCallback<State> = Function(StoreBuilder<State> builder);
34+
35+
abstract class StoreBuilder<State> {
36+
StoreBuilder<State> withPreloadedState(State preloadedState);
37+
StoreBuilder<State> withReducer(Reducer<State> reducer);
38+
StoreBuilder<State> withMiddleware(Middleware<State> middleware);
39+
StoreBuilder<State> usingDevtools(String devToolsIpAddr);
40+
}
41+
```
42+
43+
### Quick walkthrough to get `redux_remote_devtools` to work
44+
45+
1. Make sure you have the `remotedev-server` command available in your computer. If you have it, skip until step 2, otherwise, read-on. You have the option of installing it as a dockerized container or as an npm package, I'll show you how to do both:
46+
47+
```bash
48+
# First: the npm installation
49+
npm i -g remotedev-server
50+
# or
51+
yarn global add remotedev-server
52+
53+
# Then launch it
54+
remotedev --port 8000
55+
56+
# Second: the docker way
57+
# The following command will pull the image if you don't have it
58+
# and will leave the server running, no further setup required
59+
docker run -p 8000:8000 jhen0409/remotedev-server
60+
```
61+
62+
2. Use the `usingDevtools` method in your `StoreBuilder` to pass the IP address and port in which you're running your server.
63+
64+
```dart
65+
// To use any IP address within your LAN
66+
builder.usingDevTools('192.168.1.2:5000');
67+
68+
// Or if you want to use loopback
69+
builder.usingDevTools('127.0.0.1:5000');
70+
71+
// Important: THIS WON'T WORK
72+
builder.usingDevTools('localhost:5000'); // ASSERTION ERROR - REQUIRES AN IP ADDRESS STRING
73+
```
74+
75+
3. Make sure everything you want to see in your devtools is json serializable, this means, all your model classes and your state itself. If you want to see your actions properly with all their payloads and stuff they should be json serializable too. The recommended way to achieve this is via the `json_serializable` package, you can check out the example project for that. Basically, all you do is this:
76+
77+
```dart
78+
// todo.dart
79+
80+
import 'package:json_annotation/json_annotation.dart';
81+
82+
// specify the name of the file where the generated code will be
83+
part 'todo.g.dart';
84+
85+
// annotate your class with @JsonSerializable() from json_annotation
86+
@JsonSerializable()
87+
class Todo {
88+
final int id;
89+
final String title;
90+
final bool completed;
91+
92+
const Todo({
93+
this.id,
94+
this.title,
95+
this.completed,
96+
});
97+
98+
// Use the generated code in the factory and in the toJson methods
99+
factory Todo.fromJson(Map<String, dynamic> json) => _$TodoFromJson(json);
100+
Map<String, dynamic> toJson() => _$TodoToJson(this);
101+
}
102+
```
103+
104+
```bash
105+
flutter pub run build_runner build # or replace build for watch if you want the generated code to be automatically updated as you write more code :)
106+
```
107+
108+
4. Run your app and see your redux store in real time. You'll also have time travel debugging available for you.
109+
110+
If I missed anything be sure to check out the official docs for [`redux_remote_devtools`](https://github.com/MichaelMarner/dart-redux-remote-devtools) and let me know or make a PR with the correction.
111+
112+
## Reducers and Actions
113+
114+
### `createReducer`
115+
116+
Here is your alternative to writing reducers like the next one:
117+
118+
```dart
119+
State reducer(State s, dynamic a) {
120+
if (a is Action1) {
121+
return sPrime;
122+
}
123+
124+
if (a is Action2) {
125+
return sSecond;
126+
}
127+
128+
// Tons of if statements like the ones before
129+
130+
return s;
131+
}
132+
```
133+
134+
The previous reducer would have to be written like this:
135+
136+
```dart
137+
final reducer = createReducer<AppState>(
138+
AppState.initialState,
139+
(builder) => builder
140+
.addCase<Action1>((state, action) => sPrime)
141+
.addCase<Action2>((state, action) => sSecond),
142+
);
143+
```
144+
145+
If you need to run some code everytime an action is dispatched that is an instance of `MyGenericAction<T>` regardless of what `T` is you'll have to use `addMatcher`.
146+
147+
```dart
148+
final reducer = createReducer<AppState>(
149+
AppState.initialState,
150+
(builder) => builder
151+
.addMatcher(
152+
(action) => action.runtimeType.toString().startsWith('MyGenericAction'),
153+
(state, action) => sPrime
154+
),
155+
);
156+
```
157+
158+
Lastly, if you need to change what your reducer does when it receives an action you didn't add a case or matcher for you can just add a default case using `addDefaultCase`.
159+
160+
```dart
161+
final reducer = createReducer<AppState>(
162+
AppState.initialState,
163+
(builder) => builder
164+
.addDefaultCase((state) => sPrime),
165+
);
166+
```
167+
168+
#### API Reference
169+
170+
```dart
171+
typedef ActionMatcher<Action> = bool Function(Action action);
172+
typedef CaseReducer<State, Action> = State Function(State state, Action action);
173+
typedef DefaultCaseReducer<State> = State Function(State state);
174+
typedef BuilderCallback<State> = Function(ActionReducerMapBuilder<State> builder);
175+
176+
abstract class ActionReducerMapBuilder<State> {
177+
ActionReducerMapBuilder<State> addCase<Action>(
178+
CaseReducer<State, Action> reducer);
179+
ActionReducerMapBuilder<State> addMatcher<Action>(
180+
ActionMatcher<Action> actionMatcher, CaseReducer<State, Action> reducer);
181+
ActionReducerMapBuilder<State> addDefaultCase(
182+
DefaultCaseReducer<State> reducer);
183+
}
184+
185+
Reducer<State> createReducer<State>(State initialState, BuilderCallback<State> builderCallback);
186+
```
187+
188+
### `PayloadAction` interface
189+
190+
Since in flutter we use a different class for each action and that's how we differentiate them there is no `createAction` function like there is in the original `redux-toolkit` for `js` but there is a `PayloadAction` interface for you to implement so that all your actions follow the same format.
191+
192+
```dart
193+
@immutable
194+
class MyAction implements PayloadAction<Payload, Meta, Error> {
195+
const PayloadAction({
196+
Payload payload,
197+
Meta meta,
198+
Error error,
199+
}) : super(
200+
payload: payload,
201+
meta: meta,
202+
error: error,
203+
);
204+
}
205+
```
206+
207+
### `AsyncThunk` abstract class
208+
209+
Again, no `createAsyncThunk` like in the original but an abstract class. Notice how I usually call abstract classes that I expect you to implement interfaces regardless of them being abstract classes in dart, well, in this case I expect you to extend. This is an application of the template method design pattern so I'll allow you to specify your operation that returns a `Future` by overriding the `run` method and I'll take care of dispatching actions as the state of your `Future` evolves.
210+
211+
The next example shows a thunk that fetches a list of todos from the [JSON Placeholder API](https://jsonplaceholder.typicode.com) and transforms the json it receives into a model class.
212+
213+
```dart
214+
@immutable
215+
class FetchTodos extends AsyncThunk<FetchTodos, AppState, void, List<Todo>> {
216+
@override
217+
Future<List<Todo>> run() async {
218+
final response = await http.get('${Config.apiBaseUrl}/todos');
219+
final list = jsonDecode(response.body) as List<dynamic>;
220+
return list.map((e) => Todo.fromJson(e)).toList();
221+
}
222+
}
223+
```
224+
225+
### Convenience exports
226+
227+
Just like the original `redux-toolkit` I re-exported some useful functions and even entire libraries just for convenience.
228+
229+
- `nanoid` -> An inlined copy of nanoid/nonsecure. Generates a non-cryptographically-secure random ID string.
230+
- [`reselect`](https://github.com/brianegan/reselect_dart) -> Everything that `reselect` exports I export for convenience.

lib/src/action.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'package:meta/meta.dart';
22

3+
/// Interface to use for your custom actions
34
@immutable
45
abstract class PayloadAction<Payload, Meta, Error> {
56
final Payload payload;

lib/src/async_thunk.dart

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'package:redux_thunk/redux_thunk.dart';
66
import './action.dart';
77
import './nanoid.dart';
88

9+
/// Metadata used by `AsyncThunk`
910
@immutable
1011
class Meta<T> {
1112
final T arg;
@@ -14,21 +15,62 @@ class Meta<T> {
1415
const Meta(this.arg, this.requestId);
1516
}
1617

18+
/// Action that gets dispatched when an `AsyncThunk`
19+
/// starts being processed.
20+
///
21+
/// Will come with the `payload` passed to the thunk
22+
/// within its `meta` member.
1723
@immutable
1824
class Pending<T, M> extends PayloadAction<dynamic, Meta<M>, dynamic> {
1925
Pending(M meta, String requestId) : super(meta: Meta(meta, requestId));
2026
}
2127

28+
/// Action that gets dispatched when an `AsyncThunk`
29+
/// finishes successfully.
30+
///
31+
/// Will have the `payload` that was initially passed
32+
/// to the thunk within its `meta` and the result
33+
/// of the operation in its `payload`.
2234
@immutable
2335
class Fulfilled<T, P, M> extends PayloadAction<P, Meta<M>, dynamic> {
2436
Fulfilled(P payload, M meta, String requestId) : super(payload: payload, meta: Meta(meta, requestId));
2537
}
2638

39+
40+
/// Action that gets dispatched when an `AsyncThunk`
41+
/// finishes with an error.
42+
///
43+
/// Will have the error that occurred in its `error`
44+
/// member and within its `meta` you'll find the
45+
/// `payload` that was initially passed to the thunk.
2746
@immutable
2847
class Rejected<T, M, E> extends PayloadAction<dynamic, Meta<M>, E> {
2948
Rejected(M meta, E error, String requestId) : super(meta: Meta(meta, requestId), error: error);
3049
}
3150

51+
/// Abstraction to make thunks that just deal with a `Future`
52+
/// adhere to a standard.
53+
///
54+
/// Before the `Future` starts processing this will dispatch a `Pending` action.
55+
///
56+
/// After the `Future` resolves successfully this will dispatch a `Fulfilled` action.
57+
///
58+
/// After the `Future` fails this will dispatch a `Rejected` action.
59+
///
60+
///
61+
/// ### Example
62+
///
63+
/// ```dart
64+
/// @immutable
65+
/// class FetchTodos extends AsyncThunk<FetchTodos, AppState, void, List<Todo>> {
66+
/// @override
67+
/// Future<List<Todo>> run() async {
68+
/// final response = await http.get('https://jsonplaceholder.typicode.com/todos');
69+
/// final list = jsonDecode(response.body) as List<dynamic>;
70+
/// return list.map((e) => Todo.fromJson(e)).toList();
71+
/// }
72+
/// }
73+
/// ```
3274
@immutable
3375
abstract class AsyncThunk<Self, State, Payload, Result> implements CallableThunkAction<State> {
3476
final Payload payload;

lib/src/configure_store.dart

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,48 @@ import 'package:redux_dev_tools/redux_dev_tools.dart';
44
import 'package:redux_thunk/redux_thunk.dart';
55
import 'package:redux_remote_devtools/redux_remote_devtools.dart';
66

7+
/// Type used for the argument passed to `configureStore`.
78
typedef StoreBuilderCallback<State> = Function(StoreBuilder<State> builder);
89

10+
/// A friendly abstraction over the standard way of instantiating the `Store` class.
11+
/// Build a store with `redux_thunk` and opt-in to `redux_dev_tools` and `redux_remote_devtools`.
12+
///
13+
/// ### Example
14+
///
15+
/// ```dart
16+
/// final store = await configureStore<AppState>((builder) {
17+
/// builder.withReducer(reducer);
18+
/// builder.withPreloadedState(AppState.initialState());
19+
///
20+
/// if (Config.reduxDevtoolsEnabled) {
21+
/// builder.usingDevtools(Config.reduxDevtoolsUrl);
22+
/// }
23+
/// });
24+
/// ```
925
Future<Store<State>> configureStore<State>(StoreBuilderCallback<State> builderCallback) {
1026
final builder = _StoreBuilder<State>();
1127
builderCallback(builder);
1228
return builder.build();
1329
}
1430

31+
/// Interface used to build `Store` instances with `createStore`.
1532
abstract class StoreBuilder<State> {
33+
/// Defines the `initialState`.
1634
StoreBuilder<State> withPreloadedState(State preloadedState);
35+
36+
/// Defines the root reducer of tour `Store`.
1737
StoreBuilder<State> withReducer(Reducer<State> reducer);
38+
39+
/// Adds a middleware to the end of your middleware array.
40+
///
41+
/// Important: The `RemoteDevToolsMiddleware`, should you choose to use it,
42+
/// is handled separately and is therefore always kept in the end of the array
43+
/// as the guys at `redux_remote_devtools` recommend.
1844
StoreBuilder<State> withMiddleware(Middleware<State> middleware);
45+
46+
/// Receives the IP address in which you are running the `remote-devtools`
47+
/// server. It will assert that you provide a valid IP address and port like
48+
/// `127.0.0.1:5000`.
1949
StoreBuilder<State> usingDevtools(String devToolsIpAddr);
2050
}
2151

0 commit comments

Comments
 (0)