Skip to content

Commit 2d47f6b

Browse files
authored
Merge pull request #1 from brianegan/github
Add onDispose, move to github
2 parents 15e1c40 + b806776 commit 2d47f6b

File tree

6 files changed

+160
-69
lines changed

6 files changed

+160
-69
lines changed

.travis.yml

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
os:
2+
- linux
3+
sudo: false
4+
addons:
5+
apt:
6+
sources:
7+
- ubuntu-toolchain-r-test
8+
packages:
9+
- libstdc++6
10+
- fonts-droid
11+
before_script:
12+
- git clone https://github.com/flutter/flutter.git -b alpha --depth 1
13+
- ./flutter/bin/flutter doctor
14+
script:
15+
- ./flutter/bin/flutter test --coverage --coverage-path=lcov.info
16+
after_success:
17+
- bash <(curl -s https://codecov.io/bash)
18+
cache:
19+
directories:
20+
- $HOME/.pub-cache

CHANGELOG.md

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
# Changelog
22

3+
## 0.3.3
4+
5+
* Optional `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.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.
6+
* Move to github
7+
38
## 0.3.2
49

5-
* Optional `onInit` function - The `StoreConnector` and `StoreBuilder` Widgets now accept an `onInit` function that will be run the first time the Widget is created (using Store.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.
10+
* Optional `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.
611
* `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.
712
* Documentation updates
813

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# flutter_redux
22

3-
[![build status](https://gitlab.com/brianegan/flutter_redux/badges/master/build.svg)](https://gitlab.com/brianegan/flutter_redux/commits/master) [![coverage report](https://gitlab.com/brianegan/flutter_redux/badges/master/coverage.svg)](https://brianegan.gitlab.io/flutter_redux/coverage/)
3+
[![Build Status](https://travis-ci.org/brianegan/flutter_redux.svg?branch=master)](https://travis-ci.org/brianegan/flutter_redux) [![codecov](https://codecov.io/gh/brianegan/flutter_redux/branch/master/graph/badge.svg)](https://codecov.io/gh/brianegan/flutter_redux)
44

55
A set of utilities that allow you to easily consume a [Redux](https://pub.dartlang.org/packages/redux) Store to build Flutter Widgets.
66

lib/flutter_redux.dart

+64-38
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,17 @@ typedef OnInitCallback<S> = void Function(
4848
Store<S> store,
4949
);
5050

51+
/// A function that will be run when the StoreConnector is removed from the
52+
/// Widget Tree.
53+
///
54+
/// It is run in the [State.dispose] method.
55+
///
56+
/// This can be useful for dispatching actions that remove stale data from
57+
/// your State tree.
58+
typedef OnDisposeCallback<S> = void Function(
59+
Store<S> store,
60+
);
61+
5162
/// A test of whether or not your `converter` function should run in response
5263
/// to a State change. For advanced use only.
5364
///
@@ -96,6 +107,15 @@ class StoreConnector<S, ViewModel> extends StatelessWidget {
96107
/// when it is first displayed.
97108
final OnInitCallback onInit;
98109

110+
/// A function that will be run when the StoreConnector is removed from the
111+
/// Widget Tree.
112+
///
113+
/// It is run in the [State.dispose] method.
114+
///
115+
/// This can be useful for dispatching actions that remove stale data from
116+
/// your State tree.
117+
final OnDisposeCallback onDispose;
118+
99119
/// Determines whether the Widget should be rebuilt when the Store emits an
100120
/// onChange event.
101121
final bool rebuildOnChange;
@@ -122,6 +142,7 @@ class StoreConnector<S, ViewModel> extends StatelessWidget {
122142
@required this.converter,
123143
this.distinct = false,
124144
this.onInit,
145+
this.onDispose,
125146
this.rebuildOnChange = true,
126147
this.ignoreChange,
127148
})
@@ -130,15 +151,18 @@ class StoreConnector<S, ViewModel> extends StatelessWidget {
130151
super(key: key);
131152

132153
@override
133-
Widget build(BuildContext context) => new _StoreStreamListener<S, ViewModel>(
134-
store: new StoreProvider.of(context).store,
135-
builder: builder,
136-
converter: converter,
137-
distinct: distinct,
138-
onInit: onInit,
139-
rebuildOnChange: rebuildOnChange,
140-
ignoreChange: ignoreChange,
141-
);
154+
Widget build(BuildContext context) {
155+
return new _StoreStreamListener<S, ViewModel>(
156+
store: new StoreProvider.of(context).store,
157+
builder: builder,
158+
converter: converter,
159+
distinct: distinct,
160+
onInit: onInit,
161+
onDispose: onDispose,
162+
rebuildOnChange: rebuildOnChange,
163+
ignoreChange: ignoreChange,
164+
);
165+
}
142166
}
143167

144168
/// Build a Widget by passing the [Store] directly to the build function.
@@ -164,22 +188,35 @@ class StoreBuilder<S> extends StatelessWidget {
164188
/// when it is first displayed.
165189
final OnInitCallback onInit;
166190

191+
/// A function that will be run when the StoreBuilder is removed from the
192+
/// Widget Tree.
193+
///
194+
/// It is run in the [State.dispose] method.
195+
///
196+
/// This can be useful for dispatching actions that remove stale data from
197+
/// your State tree.
198+
final OnDisposeCallback onDispose;
199+
167200
StoreBuilder({
168201
Key key,
169202
@required this.builder,
170203
this.onInit,
204+
this.onDispose,
171205
this.rebuildOnChange = true,
172206
})
173207
: assert(builder != null),
174208
super(key: key);
175209

176210
@override
177-
Widget build(BuildContext context) => new StoreConnector<S, Store<S>>(
178-
builder: builder,
179-
converter: _identity,
180-
rebuildOnChange: rebuildOnChange,
181-
onInit: onInit,
182-
);
211+
Widget build(BuildContext context) {
212+
return new StoreConnector<S, Store<S>>(
213+
builder: builder,
214+
converter: _identity,
215+
rebuildOnChange: rebuildOnChange,
216+
onInit: onInit,
217+
onDispose: onDispose,
218+
);
219+
}
183220
}
184221

185222
/// Listens to the [Store] and calls [builder] whenever [store] changes.
@@ -190,42 +227,22 @@ class _StoreStreamListener<S, ViewModel> extends StatefulWidget {
190227
final bool rebuildOnChange;
191228
final bool distinct;
192229
final OnInitCallback onInit;
230+
final OnDisposeCallback onDispose;
193231
final IgnoreChangeTest ignoreChange;
194232

195-
_StoreStreamListener._({
233+
_StoreStreamListener({
196234
Key key,
197235
@required this.builder,
198236
@required this.store,
199237
@required this.converter,
200238
this.distinct = false,
201239
this.onInit,
240+
this.onDispose,
202241
this.rebuildOnChange = true,
203242
this.ignoreChange,
204243
})
205244
: super(key: key);
206245

207-
factory _StoreStreamListener({
208-
Key key,
209-
@required Store<S> store,
210-
@required StoreConverter<S, ViewModel> converter,
211-
@required ViewModelBuilder<ViewModel> builder,
212-
bool distinct = false,
213-
OnInitCallback onInit,
214-
bool rebuildOnChange = true,
215-
IgnoreChangeTest ignoreChange,
216-
}) {
217-
return new _StoreStreamListener._(
218-
builder: builder,
219-
converter: converter,
220-
store: store,
221-
key: key,
222-
rebuildOnChange: rebuildOnChange,
223-
onInit: onInit,
224-
distinct: distinct,
225-
ignoreChange: ignoreChange,
226-
);
227-
}
228-
229246
@override
230247
State<StatefulWidget> createState() {
231248
return new _StoreStreamListenerState();
@@ -242,6 +259,15 @@ class _StoreStreamListenerState<ViewModel> extends State<_StoreStreamListener> {
242259
super.initState();
243260
}
244261

262+
@override
263+
void dispose() {
264+
if (widget.onDispose != null) {
265+
widget.onDispose(widget.store);
266+
}
267+
268+
super.dispose();
269+
}
270+
245271
@override
246272
void didUpdateWidget(_StoreStreamListener oldWidget) {
247273
if (widget.store != oldWidget.store) {

pubspec.yaml

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
name: flutter_redux
22
description: A library that connects Widgets to a Redux Store
3-
version: 0.3.2
3+
version: 0.3.3
44
author: Brian Egan <[email protected]>
5-
homepage: https://gitlab.com/brianegan/flutter_redux
5+
homepage: https://github.com/brianegan/flutter_redux
66

77
dependencies:
88
meta: ">=1.1.1 <2.0.0"

test/flutter_redux_test.dart

+67-27
Original file line numberDiff line numberDiff line change
@@ -46,27 +46,6 @@ void main() {
4646

4747
expect(captor.store.state, newState);
4848
});
49-
50-
testWidgets('can run a function on Init', (WidgetTester tester) async {
51-
final defaultState = "test";
52-
final newState = "new";
53-
Widget widget([String state]) {
54-
return new StoreProvider(
55-
store: new Store(
56-
new IdentityReducer(),
57-
initialState: state,
58-
),
59-
child: new StoreCaptor(),
60-
);
61-
}
62-
63-
await tester.pumpWidget(widget(defaultState));
64-
await tester.pumpWidget(widget(newState));
65-
66-
StoreCaptor captor = tester.firstWidget(find.byType(StoreCaptor));
67-
68-
expect(captor.store.state, newState);
69-
});
7049
});
7150

7251
group('StoreConnector', () {
@@ -120,9 +99,9 @@ void main() {
12099
child: new StoreBuilder(
121100
builder: (context, store) {
122101
return new Text(
123-
store.state,
124-
textDirection: TextDirection.ltr,
125-
);
102+
store.state,
103+
textDirection: TextDirection.ltr,
104+
);
126105
},
127106
),
128107
);
@@ -250,7 +229,7 @@ void main() {
250229
(WidgetTester tester) async {
251230
var numBuilds = 0;
252231
final action = "action";
253-
final onInit = new OnInitCounter();
232+
final onInit = new StoreCounter();
254233
final store = new Store(
255234
new IdentityReducer(),
256235
initialState: action,
@@ -295,11 +274,41 @@ void main() {
295274
expect(onInit.callCount, 1);
296275
});
297276

277+
testWidgets('StoreBuilder also runs a function when disposed',
278+
(WidgetTester tester) async {
279+
final action = "action";
280+
final onDispose = new StoreCounter();
281+
final store = new Store(
282+
new IdentityReducer(),
283+
initialState: action,
284+
);
285+
final widget = () => new StoreProvider(
286+
store: store,
287+
child: new StoreBuilder(
288+
onDispose: onDispose,
289+
builder: (context, store) => new Container(),
290+
),
291+
);
292+
293+
// Build the widget with the initial state
294+
await tester.pumpWidget(widget());
295+
296+
expect(onDispose.callCount, 0);
297+
298+
store.dispatch(action);
299+
300+
// Rebuild a different widget, should trigger a dispose as the
301+
// StoreBuilder has been removed from the Widget tree.
302+
await tester.pumpWidget(new Container());
303+
304+
expect(onDispose.callCount, 1);
305+
});
306+
298307
testWidgets('optionally runs a function when the State is initialized',
299308
(WidgetTester tester) async {
300309
var numBuilds = 0;
301310
final action = "action";
302-
final onInit = new OnInitCounter();
311+
final onInit = new StoreCounter();
303312
final store = new Store(
304313
new IdentityReducer(),
305314
initialState: action,
@@ -345,6 +354,37 @@ void main() {
345354
expect(onInit.callCount, 1);
346355
});
347356

357+
testWidgets('optionally runs a function when the State is disposed',
358+
(WidgetTester tester) async {
359+
final action = "action";
360+
final onDispose = new StoreCounter();
361+
final store = new Store(
362+
new IdentityReducer(),
363+
initialState: action,
364+
);
365+
final widget = () => new StoreProvider(
366+
store: store,
367+
child: new StoreConnector(
368+
onDispose: onDispose,
369+
converter: (store) => store.state,
370+
builder: (context, latest) => new Container(),
371+
),
372+
);
373+
374+
// Build the widget with the initial state
375+
await tester.pumpWidget(widget());
376+
377+
// onDispose should not be called yet.
378+
expect(onDispose.callCount, 0);
379+
380+
store.dispatch(action);
381+
382+
// Rebuild a different widget tree. Expect this to trigger `onDispose`.
383+
await tester.pumpWidget(new Container());
384+
385+
expect(onDispose.callCount, 1);
386+
});
387+
348388
testWidgets(
349389
'avoids rebuilds when distinct is used with an object that implements ==',
350390
(WidgetTester tester) async {
@@ -412,7 +452,7 @@ class IdentityReducer extends ReducerClass {
412452
}
413453
}
414454

415-
class OnInitCounter {
455+
class StoreCounter {
416456
final List<Store> stores = [];
417457

418458
int get callCount => stores.length;

0 commit comments

Comments
 (0)