Skip to content

Commit

Permalink
Expose ImmutableProvider and ImmutableProviderTree
Browse files Browse the repository at this point in the history
  • Loading branch information
felangel committed Jun 21, 2019
1 parent 6eeb358 commit 70611f2
Show file tree
Hide file tree
Showing 16 changed files with 630 additions and 118 deletions.
6 changes: 5 additions & 1 deletion packages/flutter_bloc/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# 0.18.0

Expose `ImmutableProvider` & `ImmutableProviderTree` to enable developers to provide immutable values, such as repositories, throughout the widget tree ([#354](https://github.com/felangel/bloc/pull/354)) and Documentation Updates

# 0.17.0

Update `BlocProvider` to automatically `dispose` the provided bloc ([#349](https://github.com/felangel/bloc/pull/349)) and Documentation Updates.
Update `BlocProvider` to automatically `dispose` the provided bloc ([#349](https://github.com/felangel/bloc/pull/349)) and Documentation Updates

# 0.16.0

Expand Down
51 changes: 51 additions & 0 deletions packages/flutter_bloc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,57 @@ BlocListenerTree(
)
```

**ImmutableProvider** is a Flutter widget which provides a value to its children via `ImmutableProvider.of<T>(context)`. It is used as a DI widget so that a single instance of an immutable value can be provided to multiple widgets within a subtree. `BlocProvider` is built on top of `ImmutableProvider` and should be used to provide blocs whereas `ImmutableProvider` should be used for other values such as repositories.

```dart
ImmutableProvider(
value: Repository(),
child: ChildA(),
);
```

then from `ChildA` we can retrieve the `Repository` instance with:

```dart
ImmutableProvider.of<Repository>(context)
```

**ImmutableProviderTree** is a Flutter widget that merges multiple `ImmutableProvider` widgets into one.
`ImmutableProviderTree` improves the readability and eliminates the need to nest multiple `ImmutableProviders`.
By using `ImmutableProviderTree` we can go from:

```dart
ImmutableProvider<ValueA>(
value: ValueA(),
child: ImmutableProvider<ValueB>(
value: ValueB(),
child: ImmutableProvider<ValueC>(
value: ValueC(),
child: ChildA(),
)
)
)
```

to:

```dart
ImmutableProviderTree(
immutableProviders: [
ImmutableProvider<ValueA>(
value: ValueA(),
),
ImmutableProvider<ValueB>(
value: ValueB(),
),
ImmutableProvider<ValueC>(
value: ValueC(),
),
],
child: ChildA(),
)
```

## Usage

Lets take a look at how to use `BlocBuilder` to hook up a `CounterPage` widget to a `CounterBloc`.
Expand Down
2 changes: 2 additions & 0 deletions packages/flutter_bloc/lib/flutter_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ export './src/bloc_listener.dart';
export './src/bloc_listener_tree.dart';
export './src/bloc_provider.dart';
export './src/bloc_provider_tree.dart';
export './src/immutable_provider.dart';
export './src/immutable_provider_tree.dart';
7 changes: 5 additions & 2 deletions packages/flutter_bloc/lib/src/bloc_listener.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:flutter/widgets.dart';

import 'copyable.dart';

/// Signature for the listener function which takes the [BuildContext] along with the bloc state
/// and is responsible for executing in response to state changes.
typedef BlocWidgetListener<S> = void Function(BuildContext context, S state);
Expand All @@ -13,7 +15,7 @@ typedef BlocWidgetListener<S> = void Function(BuildContext context, S state);
/// such as navigation, showing a [SnackBar], showing a [Dialog], etc...
/// The `listener` is guaranteed to only be called once for each state change unlike the
/// `builder` in [BlocBuilder].
class BlocListener<E, S> extends BlocListenerBase<E, S> {
class BlocListener<E, S> extends BlocListenerBase<E, S> with Copyable {
/// The [Bloc] whose state will be listened to.
/// Whenever the bloc's state changes, `listener` will be invoked.
final Bloc<E, S> bloc;
Expand All @@ -36,9 +38,10 @@ class BlocListener<E, S> extends BlocListenerBase<E, S> {
assert(listener != null),
super(key: key, bloc: bloc, listener: listener);

/// Clone the current [BlocListener] with a new child [Widget].
/// Clones the current [BlocListener] with a new child [Widget].
/// All other values, including [Key], [Bloc] and [BlocWidgetListener] are
/// preserved.
@override
BlocListener<E, S> copyWith(Widget child) {
return BlocListener<E, S>(
key: key,
Expand Down
17 changes: 4 additions & 13 deletions packages/flutter_bloc/lib/src/bloc_listener_tree.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

import 'tree_buildable.dart';

/// A Flutter [Widget] that merges multiple [BlocListener] widgets into one widget tree.
///
/// [BlocListenerTree] improves the readability and eliminates the need
Expand Down Expand Up @@ -50,7 +52,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
/// into a tree of nested [BlocListener] widgets.
/// As a result, the only advantage of using [BlocListenerTree] is improved
/// readability due to the reduction in nesting and boilerplate.
class BlocListenerTree extends StatelessWidget {
class BlocListenerTree extends TreeBuildable<BlocListener> {
/// The [BlocListener] list which is converted into a tree of [BlocListener] widgets.
/// The tree of [BlocListener] widgets is created in order meaning the first [BlocListener]
/// will be the top-most [BlocListener] and the last [BlocListener] will be a direct ancestor
Expand All @@ -64,16 +66,5 @@ class BlocListenerTree extends StatelessWidget {
Key key,
@required this.blocListeners,
@required this.child,
}) : assert(blocListeners != null),
assert(child != null),
super(key: key);

@override
Widget build(BuildContext context) {
Widget tree = child;
for (final blocListener in blocListeners.reversed) {
tree = blocListener.copyWith(tree);
}
return tree;
}
}) : super(key: key, copyables: blocListeners, child: child);
}
45 changes: 13 additions & 32 deletions packages/flutter_bloc/lib/src/bloc_provider.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import 'package:flutter/widgets.dart';

import 'package:bloc/bloc.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

import 'copyable.dart';

/// Signature for the builder function which takes the [BuildContext]
/// and is responsible for returning a [Bloc] which is to be provided to the subtree.
Expand All @@ -16,7 +19,8 @@ typedef BlocProviderBuilder<T extends Bloc<dynamic, dynamic>> = T Function(
/// widget is disposed. In a few edge cases, such as when using `BlocProvider` to provide an
/// existing bloc to another route, it might be necessary to prevent automatic disposal of the bloc.
/// In those cases, the `dispose` property can be set to `false`.
class BlocProvider<T extends Bloc<dynamic, dynamic>> extends StatefulWidget {
class BlocProvider<T extends Bloc<dynamic, dynamic>> extends StatefulWidget
with Copyable {
/// The [BlocProviderBuilder] which creates the [Bloc]
/// that will be made available throughout the subtree.
final BlocProviderBuilder<T> builder;
Expand All @@ -34,8 +38,8 @@ class BlocProvider<T extends Bloc<dynamic, dynamic>> extends StatefulWidget {
BlocProvider({
Key key,
@required this.builder,
this.dispose = true,
this.child,
this.dispose = true,
}) : assert(builder != null),
super(key: key);

Expand All @@ -45,12 +49,9 @@ class BlocProvider<T extends Bloc<dynamic, dynamic>> extends StatefulWidget {
/// Method that allows widgets to access the bloc as long as their `BuildContext`
/// contains a `BlocProvider` instance.
static T of<T extends Bloc<dynamic, dynamic>>(BuildContext context) {
final type = _typeOf<_InheritedBlocProvider<T>>();
final _InheritedBlocProvider<T> provider = context
.ancestorInheritedElementForWidgetOfExactType(type)
?.widget as _InheritedBlocProvider<T>;

if (provider == null) {
try {
return ImmutableProvider.of<T>(context);
} on FlutterError catch (_) {
throw FlutterError(
"""
BlocProvider.of() called with a context that does not contain a Bloc of type $T.
Expand All @@ -62,15 +63,11 @@ class BlocProvider<T extends Bloc<dynamic, dynamic>> extends StatefulWidget {
""",
);
}
return provider.bloc;
}

/// Necessary to obtain generic [Type]
/// https://github.com/dart-lang/sdk/issues/11923
static Type _typeOf<T>() => T;

/// Clone the current [BlocProvider] with a new child [Widget].
/// Clones the current [BlocProvider] with a new child [Widget].
/// All other values, including [Key] and [Bloc] are preserved.
@override
BlocProvider<T> copyWith(Widget child) {
return BlocProvider<T>(
key: key,
Expand All @@ -97,8 +94,8 @@ class _BlocProviderState<T extends Bloc<dynamic, dynamic>>

@override
Widget build(BuildContext context) {
return _InheritedBlocProvider(
bloc: _bloc,
return ImmutableProvider<T>(
value: _bloc,
child: widget.child,
);
}
Expand All @@ -111,19 +108,3 @@ class _BlocProviderState<T extends Bloc<dynamic, dynamic>>
super.dispose();
}
}

class _InheritedBlocProvider<T extends Bloc<dynamic, dynamic>>
extends InheritedWidget {
final T bloc;

final Widget child;

_InheritedBlocProvider({
Key key,
@required this.bloc,
this.child,
}) : super(key: key, child: child);

@override
bool updateShouldNotify(_InheritedBlocProvider oldWidget) => false;
}
35 changes: 16 additions & 19 deletions packages/flutter_bloc/lib/src/bloc_provider_tree.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

import 'tree_buildable.dart';

/// A Flutter [Widget] that merges multiple [BlocProvider] widgets into one widget tree.
///
/// [BlocProviderTree] improves the readability and eliminates the need
Expand All @@ -10,11 +12,11 @@ import 'package:flutter_bloc/flutter_bloc.dart';
///
/// ```dart
/// BlocProvider<BlocA>(
/// bloc: BlocA(),
/// builder: (BuildContext context) => BlocA(),
/// child: BlocProvider<BlocB>(
/// bloc: BlocB(),
/// builder: (BuildContext context) => BlocB(),
/// child: BlocProvider<BlocC>(
/// value: BlocC(),
/// builder: (BuildContext context) => BlocC(),
/// child: ChildA(),
/// )
/// )
Expand All @@ -26,9 +28,15 @@ import 'package:flutter_bloc/flutter_bloc.dart';
/// ```dart
/// BlocProviderTree(
/// blocProviders: [
/// BlocProvider<BlocA>(bloc: BlocA()),
/// BlocProvider<BlocB>(bloc: BlocB()),
/// BlocProvider<BlocC>(bloc: BlocC()),
/// BlocProvider<BlocA>(
/// builder: (BuildContext context) => BlocA(),
/// ),
/// BlocProvider<BlocB>(
/// builder: (BuildContext context) => BlocB(),
/// ),
/// BlocProvider<BlocC>(
/// builder: (BuildContext context) => BlocC(),
/// ),
/// ],
/// child: ChildA(),
/// )
Expand All @@ -38,7 +46,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
/// into a tree of nested [BlocProvider] widgets.
/// As a result, the only advantage of using [BlocProviderTree] is improved
/// readability due to the reduction in nesting and boilerplate.
class BlocProviderTree extends StatelessWidget {
class BlocProviderTree extends TreeBuildable<BlocProvider> {
/// The [BlocProvider] list which is converted into a tree of [BlocProvider] widgets.
/// The tree of [BlocProvider] widgets is created in order meaning the first [BlocProvider]
/// will be the top-most [BlocProvider] and the last [BlocProvider] will be a direct ancestor
Expand All @@ -53,16 +61,5 @@ class BlocProviderTree extends StatelessWidget {
Key key,
@required this.blocProviders,
@required this.child,
}) : assert(blocProviders != null),
assert(child != null),
super(key: key);

@override
Widget build(BuildContext context) {
Widget tree = child;
for (final blocProvider in blocProviders.reversed) {
tree = blocProvider.copyWith(tree);
}
return tree;
}
}) : super(key: key, copyables: blocProviders, child: child);
}
8 changes: 8 additions & 0 deletions packages/flutter_bloc/lib/src/copyable.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import 'package:flutter/widgets.dart';

/// A mixin on `Widget` which exposes a `copyWith` method.
mixin Copyable on Widget {
/// `copyWith` takes a child `Widget` and must create a copy of itself with the new child.
/// All values except child (including [Key]) should be preserved.
Widget copyWith(Widget child);
}
59 changes: 59 additions & 0 deletions packages/flutter_bloc/lib/src/immutable_provider.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import 'package:flutter/material.dart';

import 'copyable.dart';

/// A Flutter widget which provides an immutable value to its children via `ImmutableProvider.of(context)`.
/// It is used as a DI widget so that a single instance of an immutable value can be provided
/// to multiple widgets within a subtree.
class ImmutableProvider<T> extends InheritedWidget with Copyable {
/// The immutable value which will be made availablee to the subtree
final T value;

const ImmutableProvider({
Key key,
@required this.value,
Widget child,
}) : super(key: key, child: child);

@override
bool updateShouldNotify(InheritedWidget _) => false;

/// Necessary to obtain generic [Type]
/// https://github.com/dart-lang/sdk/issues/11923
static Type _typeOf<T>() => T;

/// Method that allows widgets to access the value as long as their `BuildContext`
/// contains an `ImmutableProvider` instance.
static T of<T>(BuildContext context) {
final type = _typeOf<ImmutableProvider<T>>();
final provider = context
.ancestorInheritedElementForWidgetOfExactType(type)
?.widget as ImmutableProvider<T>;

if (provider == null) {
throw FlutterError(
"""
ImmutableProvider.of() called with a context that does not contain a value of type $T.
No ancestor could be found starting from the context that was passed to ImmutableProvider.of<$T>().
This can happen if the context you use comes from a widget above the ImmutableProvider.
This can also happen if you used ImmutableProviderTree and didn\'t explicity provide
the ImmutableProvider types: ImmutableProvider(value: $T()) instead of ImmutableProvider<$T>(value: $T()).
The context used was: $context
""",
);
}

return provider.value;
}

/// Clones the current [ImmutableProvider] with a new child [Widget].
/// All other values, including `key` and `value` are preserved.
@override
ImmutableProvider<T> copyWith(Widget child) {
return ImmutableProvider<T>(
key: key,
value: value,
child: child,
);
}
}
Loading

0 comments on commit 70611f2

Please sign in to comment.