Skip to content

Commit

Permalink
Reintroduced StatefulProvider (#19)
Browse files Browse the repository at this point in the history
  • Loading branch information
rrousselGit committed Feb 4, 2019
1 parent 9507ef6 commit 3e5c725
Show file tree
Hide file tree
Showing 8 changed files with 204 additions and 297 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
@@ -1,3 +1,9 @@
# 1.4.0

- Reintroduced `StatefulProvider` with a modified prototype.
The second argument of `valueBuilder` and `didChangeDependencies` have been removed.
And `valueBuilder` is now called only once for the whole life-cycle of `StatefulProvider`.

# 1.3.0

- Added `Consumer`, useful when we need to both expose and consume a value simultaneously.
Expand Down
29 changes: 29 additions & 0 deletions README.md
Expand Up @@ -69,6 +69,35 @@ Provider<int>(
)
```

### StatefulProvider

A [Provider] that can also create and dispose an object.

It is usually used to avoid making a [StatefulWidget] for something trivial, such as instanciating a BLoC.

[StatefulBuilder] is the equivalent of a [State.initState] combined with [State.dispose].
As such, [valueBuilder] is called only once and is unable to use [InheritedWidget]; which makes it impossible to update the created value.

If this is too limiting, consider instead [HookProvider], which offer a much more advanced control over the created value.

The following example instanciate a `Model` once, and dispose it when [StatefulProvider] is removed from the tree.

```dart
class Model {
void dispose() {}
}
class Stateless extends StatelessWidget {
@override
Widget build(BuildContext context) {
return StatefulProvider<Model>(
valueBuilder: (context) => Model(),
dispose: (context, value) => value.dispose(),
child: ...,
);
}
}
```

### HookProvider

Expand Down
3 changes: 1 addition & 2 deletions example/stateful_provider.dart
Expand Up @@ -23,9 +23,8 @@ class Bloc {
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// ignore: deprecated_member_use
return StatefulProvider<Bloc>(
valueBuilder: (_, old) => old ?? Bloc(),
valueBuilder: (_) => Bloc(),
onDispose: (_, value) => value.dipose(),
child: Example(),
);
Expand Down
7 changes: 1 addition & 6 deletions lib/provider.dart
@@ -1,9 +1,4 @@
library provider;

export 'src/provider.dart'
show
Provider,
// ignore: deprecated_member_use, deprecated_member_use_from_same_package
StatefulProvider,
HookProvider,
Consumer;
show Provider, StatefulProvider, HookProvider, Consumer;
180 changes: 123 additions & 57 deletions lib/src/provider.dart
@@ -1,10 +1,6 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:functional_widget_annotation/functional_widget_annotation.dart'
hide widget;

part 'provider.g.dart';

/// Necessary to obtain generic [Type]
/// see https://stackoverflow.com/questions/52891537/how-to-get-generic-type
Expand Down Expand Up @@ -70,6 +66,12 @@ class Provider<T> extends InheritedWidget {
}
}

@visibleForTesting
// ignore: public_member_api_docs
UpdateShouldNotify<T> debugGetProviderUpdateShouldNotify<T>(
Provider<T> provider) =>
provider._updateShouldNotify;

/// Obtain [Provider<T>] from its ancestors and pass its value to [builder].
///
/// [builder] must not be null and may be called multiple times (such as when provided value change).
Expand All @@ -90,38 +92,40 @@ class Consumer<T> extends StatelessWidget {
}
}

/// A wrapper over [Provider] to make exposing complex objets
/// A [Provider] that can also create and dispose an object.
///
/// It is usually used to avoid making a [StatefulWidget] for something trivial, such as instanciating a BLoC.
///
/// [StatefulBuilder] is the equivalent of a [State.initState] combined with [State.dispose].
/// As such, [valueBuilder] is called only once and is unable to use [InheritedWidget]; which makes it impossible to update the created value.
///
/// If this is too limiting, consider instead [HookProvider], which offer a much more advanced control over the created value.
///
/// It is usuallt used to create once an object, to not recreate it on every [State.build] call
/// without having to manually create a [StatefulWidget]
/// The following example instanciate a `Model` once, and dispose it when [StatefulProvider] is removed from the tree.
///
/// ```
/// class Model {}
/// class Model {
/// void dispose() {}
/// }
///
/// class Stateless extends StatelessWidget {
/// @override
/// Widget build(BuildContext context) {
/// return StatefulProvider<Model>(
/// valueBuilder: (context, old) => old ?? Model(),
/// valueBuilder: (context) => Model(),
/// dispose: (context, value) => value.dispose(),
/// child: ...,
/// );
/// }
/// }
/// ```
@Deprecated('Prefer HookBuilder combined with useState hook instead')
class StatefulProvider<T> extends StatefulWidget {
/// [valueBuilder] is called on [State.initState] and [State.didUpdateWidget]
/// A function that creates the provided value.
///
/// The second argument of [valueBuilder] is the previous value returned by [valueBuilder].
/// This value will be `null` on the first call.
/// [valueBuilder] must not be null and is called only once for the life-cycle of [StatefulProvider].
///
/// It is not possible to obtain an [InheritedWidget] from [valueBuilder].
/// Use [didChangeDependencies] instead.
final T Function(BuildContext context, T previous) valueBuilder;

/// [didChangeDependencies] is a hook to [State.didChangeDependencies]
/// It can be used to build/update values depending on an [InheritedWidget]
final T Function(BuildContext context, T value) didChangeDependencies;
final T Function(BuildContext context) valueBuilder;

/// [onDispose] is a callback called when [StatefulProvider] is
/// removed for the widget tree, and pass the current value as parameter.
Expand All @@ -140,40 +144,24 @@ class StatefulProvider<T> extends StatefulWidget {
/// Allows to specify parameters to [StatefulProvider]
StatefulProvider({
Key key,
this.valueBuilder,
this.child,
@required this.valueBuilder,
@required this.child,
this.onDispose,
this.didChangeDependencies,
this.updateShouldNotify,
}) : assert(valueBuilder != null || didChangeDependencies != null),
}) : assert(valueBuilder != null),
super(key: key);

@override
_StatefulProviderState<T> createState() => _StatefulProviderState<T>();
}

@deprecated
class _StatefulProviderState<T> extends State<StatefulProvider<T>> {
T _value;

@override
void initState() {
super.initState();
_buildValue();
}

@override
void didUpdateWidget(StatefulProvider<T> oldWidget) {
super.didUpdateWidget(oldWidget);
_buildValue();
}

@override
void didChangeDependencies() {
super.didChangeDependencies();
if (widget.didChangeDependencies != null) {
_value = widget.didChangeDependencies(context, _value);
}
_value = widget.valueBuilder(context);
}

@override
Expand All @@ -192,25 +180,103 @@ class _StatefulProviderState<T> extends State<StatefulProvider<T>> {
child: widget.child,
);
}

void _buildValue() {
if (widget.valueBuilder != null) {
_value = widget.valueBuilder(context, _value);
}
}
}

/// A [Provider] that exposes a value obtained from a [Hook].
/// A provider which can use hooks from [flutter_hooks](https://github.com/rrousselGit/flutter_hooks)
///
/// [HookProvider] will rebuild and potentially expose a new value if the hooks used ask for it.
@hwidget
Widget hookProvider<T>(
{T hook(),
@required Widget child,
UpdateShouldNotify<T> updateShouldNotify}) {
return Provider<T>(
value: hook(),
child: child,
updateShouldNotify: updateShouldNotify,
);
/// This is especially useful to create complex providers, without having to make a `StatefulWidget`.
///
/// The following example uses BLoC pattern to create a BLoC, provide its value, and dispose it when the provider is removed from the tree.
///
/// ```dart
/// HookProvider<MyBloc>(
/// hook: () {
/// final bloc = useMemoized(() => MyBloc());
/// useEffect(() => bloc.dispose, [bloc]);
/// return bloc;
/// },
/// child: // ...
/// )
/// ```
class HookProvider<T> extends HookWidget {
/// A provider which can use hooks from [flutter_hooks](https://github.com/rrousselGit/flutter_hooks)
///
/// This is especially useful to create complex providers, without having to make a `StatefulWidget`.
///
/// The following example uses BLoC pattern to create a BLoC, provide its value, and dispose it when the provider is removed from the tree.
///
/// ```dart
/// HookProvider<MyBloc>(
/// hook: () {
/// final bloc = useMemoized(() => MyBloc());
/// useEffect(() => bloc.dispose, [bloc]);
/// return bloc;
/// },
/// child: // ...
/// )
/// ```
const HookProvider(
{Key key, this.hook, @required this.child, this.updateShouldNotify})
: super(key: key);

/// A provider which can use hooks from [flutter_hooks](https://github.com/rrousselGit/flutter_hooks)
///
/// This is especially useful to create complex providers, without having to make a `StatefulWidget`.
///
/// The following example uses BLoC pattern to create a BLoC, provide its value, and dispose it when the provider is removed from the tree.
///
/// ```dart
/// HookProvider<MyBloc>(
/// hook: () {
/// final bloc = useMemoized(() => MyBloc());
/// useEffect(() => bloc.dispose, [bloc]);
/// return bloc;
/// },
/// child: // ...
/// )
/// ```
final T Function() hook;

/// A provider which can use hooks from [flutter_hooks](https://github.com/rrousselGit/flutter_hooks)
///
/// This is especially useful to create complex providers, without having to make a `StatefulWidget`.
///
/// The following example uses BLoC pattern to create a BLoC, provide its value, and dispose it when the provider is removed from the tree.
///
/// ```dart
/// HookProvider<MyBloc>(
/// hook: () {
/// final bloc = useMemoized(() => MyBloc());
/// useEffect(() => bloc.dispose, [bloc]);
/// return bloc;
/// },
/// child: // ...
/// )
/// ```
final Widget child;

/// A provider which can use hooks from [flutter_hooks](https://github.com/rrousselGit/flutter_hooks)
///
/// This is especially useful to create complex providers, without having to make a `StatefulWidget`.
///
/// The following example uses BLoC pattern to create a BLoC, provide its value, and dispose it when the provider is removed from the tree.
///
/// ```dart
/// HookProvider<MyBloc>(
/// hook: () {
/// final bloc = useMemoized(() => MyBloc());
/// useEffect(() => bloc.dispose, [bloc]);
/// return bloc;
/// },
/// child: // ...
/// )
/// ```
final bool Function(T, T) updateShouldNotify;

@override
Widget build(BuildContext context) => Provider<T>(
value: hook(),
child: child,
updateShouldNotify: updateShouldNotify,
);
}
55 changes: 0 additions & 55 deletions lib/src/provider.g.dart

This file was deleted.

6 changes: 2 additions & 4 deletions pubspec.yaml
@@ -1,6 +1,6 @@
name: provider
description: An helper to easily exposes a value using InheritedWidget without having to write one.
version: 1.3.0
version: 1.4.0
homepage: https://github.com/rrousselGit/provider
author: Remi Rousselet <darky12s@gmail.com>

Expand All @@ -11,11 +11,9 @@ dependencies:
flutter:
sdk: flutter
flutter_hooks: '>=0.2.1 <1.0.0'
functional_widget_annotation: '>=0.3.0 <1.0.0'

dev_dependencies:
functional_widget: ^0.4.0
build_runner: ^1.1.2
pedantic: ^1.4.0
mockito: ^4.0.0
flutter_test:
sdk: flutter

0 comments on commit 3e5c725

Please sign in to comment.