Skip to content

Add onDispose, move to github #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Nov 25, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
os:
- linux
sudo: false
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- libstdc++6
- fonts-droid
before_script:
- git clone https://github.com/flutter/flutter.git -b alpha --depth 1
- ./flutter/bin/flutter doctor
script:
- ./flutter/bin/flutter test --coverage --coverage-path=lcov.info
after_success:
- bash <(curl -s https://codecov.io/bash)
cache:
directories:
- $HOME/.pub-cache
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
# Changelog

## 0.3.3

* 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.
* Move to github

## 0.3.2

* 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.
* 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.
* `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.
* Documentation updates

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# flutter_redux

[![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/)
[![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)

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

Expand Down
102 changes: 64 additions & 38 deletions lib/flutter_redux.dart
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,17 @@ typedef OnInitCallback<S> = void Function(
Store<S> store,
);

/// A function that will be run when the StoreConnector is removed from the
/// Widget Tree.
///
/// It is run in the [State.dispose] method.
///
/// This can be useful for dispatching actions that remove stale data from
/// your State tree.
typedef OnDisposeCallback<S> = void Function(
Store<S> store,
);

/// A test of whether or not your `converter` function should run in response
/// to a State change. For advanced use only.
///
Expand Down Expand Up @@ -96,6 +107,15 @@ class StoreConnector<S, ViewModel> extends StatelessWidget {
/// when it is first displayed.
final OnInitCallback onInit;

/// A function that will be run when the StoreConnector is removed from the
/// Widget Tree.
///
/// It is run in the [State.dispose] method.
///
/// This can be useful for dispatching actions that remove stale data from
/// your State tree.
final OnDisposeCallback onDispose;

/// Determines whether the Widget should be rebuilt when the Store emits an
/// onChange event.
final bool rebuildOnChange;
Expand All @@ -122,6 +142,7 @@ class StoreConnector<S, ViewModel> extends StatelessWidget {
@required this.converter,
this.distinct = false,
this.onInit,
this.onDispose,
this.rebuildOnChange = true,
this.ignoreChange,
})
Expand All @@ -130,15 +151,18 @@ class StoreConnector<S, ViewModel> extends StatelessWidget {
super(key: key);

@override
Widget build(BuildContext context) => new _StoreStreamListener<S, ViewModel>(
store: new StoreProvider.of(context).store,
builder: builder,
converter: converter,
distinct: distinct,
onInit: onInit,
rebuildOnChange: rebuildOnChange,
ignoreChange: ignoreChange,
);
Widget build(BuildContext context) {
return new _StoreStreamListener<S, ViewModel>(
store: new StoreProvider.of(context).store,
builder: builder,
converter: converter,
distinct: distinct,
onInit: onInit,
onDispose: onDispose,
rebuildOnChange: rebuildOnChange,
ignoreChange: ignoreChange,
);
}
}

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

/// A function that will be run when the StoreBuilder is removed from the
/// Widget Tree.
///
/// It is run in the [State.dispose] method.
///
/// This can be useful for dispatching actions that remove stale data from
/// your State tree.
final OnDisposeCallback onDispose;

StoreBuilder({
Key key,
@required this.builder,
this.onInit,
this.onDispose,
this.rebuildOnChange = true,
})
: assert(builder != null),
super(key: key);

@override
Widget build(BuildContext context) => new StoreConnector<S, Store<S>>(
builder: builder,
converter: _identity,
rebuildOnChange: rebuildOnChange,
onInit: onInit,
);
Widget build(BuildContext context) {
return new StoreConnector<S, Store<S>>(
builder: builder,
converter: _identity,
rebuildOnChange: rebuildOnChange,
onInit: onInit,
onDispose: onDispose,
);
}
}

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

_StoreStreamListener._({
_StoreStreamListener({
Key key,
@required this.builder,
@required this.store,
@required this.converter,
this.distinct = false,
this.onInit,
this.onDispose,
this.rebuildOnChange = true,
this.ignoreChange,
})
: super(key: key);

factory _StoreStreamListener({
Key key,
@required Store<S> store,
@required StoreConverter<S, ViewModel> converter,
@required ViewModelBuilder<ViewModel> builder,
bool distinct = false,
OnInitCallback onInit,
bool rebuildOnChange = true,
IgnoreChangeTest ignoreChange,
}) {
return new _StoreStreamListener._(
builder: builder,
converter: converter,
store: store,
key: key,
rebuildOnChange: rebuildOnChange,
onInit: onInit,
distinct: distinct,
ignoreChange: ignoreChange,
);
}

@override
State<StatefulWidget> createState() {
return new _StoreStreamListenerState();
Expand All @@ -242,6 +259,15 @@ class _StoreStreamListenerState<ViewModel> extends State<_StoreStreamListener> {
super.initState();
}

@override
void dispose() {
if (widget.onDispose != null) {
widget.onDispose(widget.store);
}

super.dispose();
}

@override
void didUpdateWidget(_StoreStreamListener oldWidget) {
if (widget.store != oldWidget.store) {
Expand Down
4 changes: 2 additions & 2 deletions pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
name: flutter_redux
description: A library that connects Widgets to a Redux Store
version: 0.3.2
version: 0.3.3
author: Brian Egan <[email protected]>
homepage: https://gitlab.com/brianegan/flutter_redux
homepage: https://github.com/brianegan/flutter_redux

dependencies:
meta: ">=1.1.1 <2.0.0"
Expand Down
94 changes: 67 additions & 27 deletions test/flutter_redux_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -46,27 +46,6 @@ void main() {

expect(captor.store.state, newState);
});

testWidgets('can run a function on Init', (WidgetTester tester) async {
final defaultState = "test";
final newState = "new";
Widget widget([String state]) {
return new StoreProvider(
store: new Store(
new IdentityReducer(),
initialState: state,
),
child: new StoreCaptor(),
);
}

await tester.pumpWidget(widget(defaultState));
await tester.pumpWidget(widget(newState));

StoreCaptor captor = tester.firstWidget(find.byType(StoreCaptor));

expect(captor.store.state, newState);
});
});

group('StoreConnector', () {
Expand Down Expand Up @@ -120,9 +99,9 @@ void main() {
child: new StoreBuilder(
builder: (context, store) {
return new Text(
store.state,
textDirection: TextDirection.ltr,
);
store.state,
textDirection: TextDirection.ltr,
);
},
),
);
Expand Down Expand Up @@ -250,7 +229,7 @@ void main() {
(WidgetTester tester) async {
var numBuilds = 0;
final action = "action";
final onInit = new OnInitCounter();
final onInit = new StoreCounter();
final store = new Store(
new IdentityReducer(),
initialState: action,
Expand Down Expand Up @@ -295,11 +274,41 @@ void main() {
expect(onInit.callCount, 1);
});

testWidgets('StoreBuilder also runs a function when disposed',
(WidgetTester tester) async {
final action = "action";
final onDispose = new StoreCounter();
final store = new Store(
new IdentityReducer(),
initialState: action,
);
final widget = () => new StoreProvider(
store: store,
child: new StoreBuilder(
onDispose: onDispose,
builder: (context, store) => new Container(),
),
);

// Build the widget with the initial state
await tester.pumpWidget(widget());

expect(onDispose.callCount, 0);

store.dispatch(action);

// Rebuild a different widget, should trigger a dispose as the
// StoreBuilder has been removed from the Widget tree.
await tester.pumpWidget(new Container());

expect(onDispose.callCount, 1);
});

testWidgets('optionally runs a function when the State is initialized',
(WidgetTester tester) async {
var numBuilds = 0;
final action = "action";
final onInit = new OnInitCounter();
final onInit = new StoreCounter();
final store = new Store(
new IdentityReducer(),
initialState: action,
Expand Down Expand Up @@ -345,6 +354,37 @@ void main() {
expect(onInit.callCount, 1);
});

testWidgets('optionally runs a function when the State is disposed',
(WidgetTester tester) async {
final action = "action";
final onDispose = new StoreCounter();
final store = new Store(
new IdentityReducer(),
initialState: action,
);
final widget = () => new StoreProvider(
store: store,
child: new StoreConnector(
onDispose: onDispose,
converter: (store) => store.state,
builder: (context, latest) => new Container(),
),
);

// Build the widget with the initial state
await tester.pumpWidget(widget());

// onDispose should not be called yet.
expect(onDispose.callCount, 0);

store.dispatch(action);

// Rebuild a different widget tree. Expect this to trigger `onDispose`.
await tester.pumpWidget(new Container());

expect(onDispose.callCount, 1);
});

testWidgets(
'avoids rebuilds when distinct is used with an object that implements ==',
(WidgetTester tester) async {
Expand Down Expand Up @@ -412,7 +452,7 @@ class IdentityReducer extends ReducerClass {
}
}

class OnInitCounter {
class StoreCounter {
final List<Store> stores = [];

int get callCount => stores.length;
Expand Down