Skip to content
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

Expose ImmutableProvider and ImmutableProviderTree #364

Merged
merged 1 commit into from
Jun 23, 2019
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
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 ([#364](https://github.com/felangel/bloc/pull/364)) 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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good job on this error message. This will help a lot to troubleshoot issues ;)

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