Skip to content

Commit

Permalink
Add Automatic Bloc Lookup to BlocBuilder and BlocListener
Browse files Browse the repository at this point in the history
  • Loading branch information
felangel committed Jul 20, 2019
1 parent ebdde1e commit 6821b59
Show file tree
Hide file tree
Showing 9 changed files with 115 additions and 78 deletions.
40 changes: 16 additions & 24 deletions packages/flutter_bloc/example/lib/main.dart
Expand Up @@ -27,33 +27,26 @@ class SimpleBlocDelegate extends BlocDelegate {

void main() {
BlocSupervisor.delegate = SimpleBlocDelegate();
runApp(
MultiBlocProvider(
providers: [
BlocProvider<CounterBloc>(
builder: (context) => CounterBloc(),
),
BlocProvider<ThemeBloc>(
builder: (context) => ThemeBloc(),
),
],
child: App(),
),
);
runApp(App());
}

class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder(
bloc: BlocProvider.of<ThemeBloc>(context),
builder: (_, ThemeData theme) {
return MaterialApp(
title: 'Flutter Demo',
home: CounterPage(),
theme: theme,
);
},
return BlocProvider<ThemeBloc>(
builder: (context) => ThemeBloc(),
child: BlocBuilder<ThemeBloc, ThemeData>(
builder: (_, ThemeData theme) {
return MaterialApp(
title: 'Flutter Demo',
home: BlocProvider(
builder: (context) => CounterBloc(),
child: CounterPage(),
),
theme: theme,
);
},
),
);
}
}
Expand All @@ -66,8 +59,7 @@ class CounterPage extends StatelessWidget {

return Scaffold(
appBar: AppBar(title: Text('Counter')),
body: BlocBuilder<CounterEvent, int>(
bloc: counterBloc,
body: BlocBuilder<CounterBloc, int>(
builder: (BuildContext context, int count) {
return Center(
child: Text(
Expand Down
42 changes: 25 additions & 17 deletions packages/flutter_bloc/lib/src/bloc_builder.dart
Expand Up @@ -3,6 +3,7 @@ import 'dart:async';
import 'package:flutter/widgets.dart';

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

/// Signature for the builder function which takes the [BuildContext] and state
/// and is responsible for returning a [Widget] which is to be rendered.
Expand All @@ -14,9 +15,9 @@ typedef BlocWidgetBuilder<S> = Widget Function(BuildContext context, S state);
/// [BlocBuilder] with the current state.
typedef BlocBuilderCondition<S> = bool Function(S previous, S current);

class BlocBuilder<E, S> extends BlocBuilderBase<E, S> {
class BlocBuilder<B extends Bloc<dynamic, S>, S> extends BlocBuilderBase<B, S> {
/// The [Bloc] that the [BlocBuilder] will interact with.
final Bloc<E, S> bloc;
final B bloc;

/// The `builder` function which will be invoked on each widget build.
/// The `builder` takes the [BuildContext] and current bloc state and
Expand Down Expand Up @@ -58,18 +59,18 @@ class BlocBuilder<E, S> extends BlocBuilderBase<E, S> {
/// ```
const BlocBuilder({
Key key,
@required this.bloc,
this.bloc,
@required this.builder,
this.condition,
}) : assert(bloc != null),
assert(builder != null),
}) : assert(builder != null),
super(key: key, bloc: bloc);

@override
Widget build(BuildContext context, S state) => builder(context, state);
}

abstract class BlocBuilderBase<E, S> extends StatefulWidget {
abstract class BlocBuilderBase<B extends Bloc<dynamic, S>, S>
extends StatefulWidget {
/// Base class for widgets that build themselves based on interaction with
/// a specified [Bloc].
///
Expand All @@ -79,7 +80,7 @@ abstract class BlocBuilderBase<E, S> extends StatefulWidget {
const BlocBuilderBase({Key key, this.bloc, this.condition}) : super(key: key);

/// The [Bloc] that the [BlocBuilderBase] will interact with.
final Bloc<E, S> bloc;
final B bloc;

/// The [BlocBuilderCondition] that the [BlocBuilderBase] will invoke.
final BlocBuilderCondition<S> condition;
Expand All @@ -88,30 +89,37 @@ abstract class BlocBuilderBase<E, S> extends StatefulWidget {
Widget build(BuildContext context, S state);

@override
State<BlocBuilderBase<E, S>> createState() => _BlocBuilderBaseState<E, S>();
State<BlocBuilderBase<B, S>> createState() => _BlocBuilderBaseState<B, S>();
}

class _BlocBuilderBaseState<E, S> extends State<BlocBuilderBase<E, S>> {
class _BlocBuilderBaseState<B extends Bloc<dynamic, S>, S>
extends State<BlocBuilderBase<B, S>> {
StreamSubscription<S> _subscription;
S _previousState;
S _state;
B _bloc;

@override
void initState() {
super.initState();
_previousState = widget.bloc.currentState;
_state = widget.bloc.currentState;
_bloc = widget.bloc ?? BlocProvider.of<B>(context);
_previousState = _bloc.currentState;
_state = _bloc.currentState;
_subscribe();
}

@override
void didUpdateWidget(BlocBuilderBase<E, S> oldWidget) {
void didUpdateWidget(BlocBuilderBase<B, S> oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.bloc.state != widget.bloc.state) {
final Stream<S> oldState =
oldWidget.bloc?.state ?? BlocProvider.of<B>(context).state;
final Stream<S> currentState = widget.bloc?.state ?? oldState;
if (oldState != currentState) {
if (_subscription != null) {
_unsubscribe();
_previousState = widget.bloc.currentState;
_state = widget.bloc.currentState;
_bloc = widget.bloc ?? BlocProvider.of<B>(context);
_previousState = _bloc.currentState;
_state = _bloc.currentState;
}
_subscribe();
}
Expand All @@ -127,8 +135,8 @@ class _BlocBuilderBaseState<E, S> extends State<BlocBuilderBase<E, S>> {
}

void _subscribe() {
if (widget.bloc.state != null) {
_subscription = widget.bloc.state.skip(1).listen((S state) {
if (_bloc.state != null) {
_subscription = _bloc.state.skip(1).listen((S state) {
if (widget.condition?.call(_previousState, state) ?? true) {
setState(() {
_state = state;
Expand Down
42 changes: 25 additions & 17 deletions packages/flutter_bloc/lib/src/bloc_listener.dart
Expand Up @@ -2,6 +2,7 @@ import 'dart:async';

import 'package:bloc/bloc.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:provider/provider.dart';

/// Signature for the listener function which takes the [BuildContext] along with the bloc state
Expand All @@ -13,11 +14,11 @@ typedef BlocWidgetListener<S> = void Function(BuildContext context, S state);
/// of [BlocListener] with the current state.
typedef BlocListenerCondition<S> = bool Function(S previous, S current);

class BlocListener<E, S> extends BlocListenerBase<E, S>
class BlocListener<B extends Bloc<dynamic, S>, S> extends BlocListenerBase<B, S>
with SingleChildCloneableWidget {
/// The [Bloc] whose state will be listened to.
/// Whenever the bloc's state changes, `listener` will be invoked.
final Bloc<E, S> bloc;
final B bloc;

/// The [BlocWidgetListener] which will be called on every state change (including the `initialState`).
/// This listener should be used for any code which needs to execute
Expand Down Expand Up @@ -56,12 +57,11 @@ class BlocListener<E, S> extends BlocListenerBase<E, S>
/// `builder` in [BlocBuilder].
const BlocListener({
Key key,
@required this.bloc,
this.bloc,
@required this.listener,
this.condition,
this.child,
}) : assert(bloc != null),
assert(listener != null),
}) : assert(listener != null),
super(
key: key,
bloc: bloc,
Expand All @@ -73,8 +73,8 @@ class BlocListener<E, S> extends BlocListenerBase<E, S>
/// All other values, including `key`, `bloc` and `listener` are preserved.
/// preserved.
@override
BlocListener<E, S> cloneWithChild(Widget child) {
return BlocListener<E, S>(
BlocListener<B, S> cloneWithChild(Widget child) {
return BlocListener<B, S>(
key: key,
bloc: bloc,
listener: listener,
Expand All @@ -87,10 +87,11 @@ class BlocListener<E, S> extends BlocListenerBase<E, S>
Widget build(BuildContext context) => child;
}

abstract class BlocListenerBase<E, S> extends StatefulWidget {
abstract class BlocListenerBase<B extends Bloc<dynamic, S>, S>
extends StatefulWidget {
/// The [Bloc] whose state will be listened to.
/// Whenever the bloc's state changes, `listener` will be invoked.
final Bloc<E, S> bloc;
final B bloc;

/// The [BlocWidgetListener] which will be called on every state change.
/// This listener should be used for any code which needs to execute
Expand All @@ -113,30 +114,37 @@ abstract class BlocListenerBase<E, S> extends StatefulWidget {
@required this.condition,
}) : super(key: key);

State<BlocListenerBase<E, S>> createState() => _BlocListenerBaseState<E, S>();
State<BlocListenerBase<B, S>> createState() => _BlocListenerBaseState<B, S>();

/// Returns a [Widget] based on the [BuildContext].
Widget build(BuildContext context);
}

class _BlocListenerBaseState<E, S> extends State<BlocListenerBase<E, S>> {
class _BlocListenerBaseState<B extends Bloc<dynamic, S>, S>
extends State<BlocListenerBase<B, S>> {
StreamSubscription<S> _subscription;
S _previousState;
B _bloc;

@override
void initState() {
super.initState();
_previousState = widget.bloc.currentState;
_bloc = widget.bloc ?? BlocProvider.of<B>(context);
_previousState = _bloc.currentState;
_subscribe();
}

@override
void didUpdateWidget(BlocListenerBase<E, S> oldWidget) {
void didUpdateWidget(BlocListenerBase<B, S> oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.bloc.state != widget.bloc.state) {
final Stream<S> oldState =
oldWidget.bloc?.state ?? BlocProvider.of<B>(context).state;
final Stream<S> currentState = widget.bloc.state ?? oldState;
if (oldState != currentState) {
if (_subscription != null) {
_unsubscribe();
_previousState = widget.bloc.currentState;
_bloc = widget.bloc ?? BlocProvider.of<B>(context);
_previousState = _bloc.currentState;
}
_subscribe();
}
Expand All @@ -152,8 +160,8 @@ class _BlocListenerBaseState<E, S> extends State<BlocListenerBase<E, S>> {
}

void _subscribe() {
if (widget.bloc.state != null) {
_subscription = widget.bloc.state.skip(1).listen((S state) {
if (_bloc.state != null) {
_subscription = _bloc.state.skip(1).listen((S state) {
if (widget.condition?.call(_previousState, state) ?? true) {
widget.listener(context, state);
}
Expand Down
49 changes: 39 additions & 10 deletions packages/flutter_bloc/lib/src/bloc_provider.dart
Expand Up @@ -3,7 +3,11 @@ import 'package:meta/meta.dart';
import 'package:provider/provider.dart';
import 'package:bloc/bloc.dart';

class BlocProvider<T extends Bloc<dynamic, dynamic>> extends Provider<T> {
class BlocProvider<T extends Bloc<dynamic, dynamic>>
extends ValueDelegateWidget<T> implements SingleChildCloneableWidget {
/// The [Widget] and its descendants which will have access to the [Bloc].
final Widget child;

/// Takes a [ValueBuilder] that is responsible for
/// building the bloc and a child which will have access to the bloc via `BlocProvider.of(context)`.
/// It is used as a dependency injection (DI) widget so that a single instance of a bloc can be provided
Expand All @@ -19,13 +23,14 @@ class BlocProvider<T extends Bloc<dynamic, dynamic>> extends Provider<T> {
/// ```
BlocProvider({
Key key,
@required ValueBuilder<T> builder,
Widget child,
ValueBuilder<T> builder,
this.child,
}) : super(
key: key,
builder: builder,
dispose: (_, bloc) => bloc?.dispose(),
child: child,
delegate: BuilderStateDelegate<T>(
builder,
dispose: (_, bloc) => bloc?.dispose(),
),
);

/// Takes a `Bloc` and a child which will have access to the bloc via `BlocProvider.of(context)`.
Expand All @@ -44,13 +49,18 @@ class BlocProvider<T extends Bloc<dynamic, dynamic>> extends Provider<T> {
BlocProvider.value({
Key key,
@required T value,
Widget child,
}) : super.value(
this.child,
}) : super(
key: key,
value: value,
child: child,
delegate: SingleValueDelegate<T>(value),
);
BlocProvider._({
Key key,
@required ValueStateDelegate<T> delegate,
this.child,
}) : super(key: key, delegate: delegate);
/// Method that allows widgets to access a bloc instance as long as their `BuildContext`
/// contains a [BlocProvider] instance.
///
Expand Down Expand Up @@ -81,4 +91,23 @@ class BlocProvider<T extends Bloc<dynamic, dynamic>> extends Provider<T> {
);
}
}
@override
Widget build(BuildContext context) {
return Builder(
builder: (context) => InheritedProvider<T>(
value: delegate.value,
child: child,
),
);
}
@override
BlocProvider<T> cloneWithChild(Widget child) {
return BlocProvider<T>._(
key: key,
delegate: delegate,
child: child,
);
}
}

0 comments on commit 6821b59

Please sign in to comment.