Skip to content

Commit

Permalink
minor updates
Browse files Browse the repository at this point in the history
  • Loading branch information
felangel committed Nov 3, 2020
1 parent 8516819 commit 61e5dc5
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 71 deletions.
57 changes: 23 additions & 34 deletions packages/flutter_bloc/lib/src/bloc_provider.dart
@@ -1,3 +1,5 @@
import 'dart:async';

import 'package:flutter/widgets.dart';

import 'package:bloc/bloc.dart';
Expand All @@ -11,31 +13,18 @@ typedef CreateBloc<T extends Cubit<dynamic>> = T Function(BuildContext context);
/// of multiple [BlocProvider]s.
mixin BlocProviderSingleChildWidget on SingleChildWidget {}

/// Extends the `BuildContext` class with the ability
/// to perform a lookup based on a `Bloc` type.
/// Extends the [BuildContext] class with the ability
/// to perform a lookup based on a [Cubit] type.
extension BlocProviderExtension on BuildContext {
/// Performs a lookup using the `BuildContext` to obtain
/// the nearest ancestor `Cubit` of type [C].
/// Performs a lookup using the [BuildContext] to obtain
/// the nearest ancestor [Cubit] of type [C].
///
/// Calling this method is equivalent to calling:
///
/// ```dart
/// BlocProvider.of<C>(context);
/// ```
C bloc<C extends Cubit<Object>>() => BlocProvider.of<C>(this);

/// Performs a lookup using the `BuildContext` to obtain
/// the nearest ancestor `Cubit` of type [C] and registers the current
/// [BuildContext] as a dependant.
///
/// Calling this method is equivalent to calling:
///
/// ```dart
/// BlocProvider.of<C>(context, listen: true);
/// ```
S listen<C extends Cubit<S>, S>() {
return BlocProvider.of<C>(this, listen: true).state;
}
}

/// {@template bloc_provider}
Expand Down Expand Up @@ -109,7 +98,7 @@ class BlocProvider<B extends Cubit<dynamic>> extends SingleChildStatefulWidget
/// in the widget tree we can do so via:
///
/// ```dart
/// BlocProvider.of<BlocA>(context)
/// BlocProvider.of<BlocA>(context);
/// ```
static T of<T extends Cubit<dynamic>>(
BuildContext context, {
Expand All @@ -134,19 +123,21 @@ class BlocProvider<B extends Cubit<dynamic>> extends SingleChildStatefulWidget
''',
);
}
return provider.create();
return provider.value();
}
}

class _BlocProviderState<B extends Cubit<dynamic>>
extends SingleChildState<BlocProvider<B>> {
B _bloc;
final _completer = Completer<B>();

@override
void initState() {
super.initState();
if (!widget.lazy) {
_bloc = widget.create(context);
_completer.complete(_bloc);
}
}

Expand All @@ -156,32 +147,30 @@ class _BlocProviderState<B extends Cubit<dynamic>>
super.dispose();
}

B _create() {
if (_bloc == null) {
_bloc = widget.create(context);
WidgetsBinding.instance.addPostFrameCallback((_) => setState(() {}));
}
return _bloc;
}

@override
Widget buildWithChild(BuildContext context, Widget child) {
return _InheritedBlocProvider(
child: child ?? widget.child,
bloc: _bloc,
create: _create,
deferredBloc: _completer.future,
value: () {
if (!_completer.isCompleted) {
_bloc = widget.create(context);
_completer.complete(_bloc);
}
return _bloc;
},
);
}
}

class _InheritedBlocProvider<B extends Cubit<dynamic>>
extends InheritedStream<B> {
extends DeferredInheritedStream<B> {
_InheritedBlocProvider({
Key key,
@required B bloc,
@required this.create,
@required Future<B> deferredBloc,
@required this.value,
Widget child,
}) : super(key: key, stream: bloc, child: child);
}) : super(key: key, deferredStream: deferredBloc, child: child);

final B Function() create;
final ValueGetter<B> value;
}
70 changes: 59 additions & 11 deletions packages/flutter_bloc/lib/src/repository_provider.dart
@@ -1,3 +1,5 @@
import 'dart:async';

import 'package:flutter/widgets.dart';
import 'package:nested/nested.dart';

Expand Down Expand Up @@ -75,7 +77,7 @@ class RepositoryProvider<T> extends SingleChildStatefulWidget
''',
);
}
return provider.repository ?? provider.create();
return provider.value();
}

@override
Expand All @@ -85,23 +87,28 @@ class RepositoryProvider<T> extends SingleChildStatefulWidget
class _RepositoryProviderState<T>
extends SingleChildState<RepositoryProvider<T>> {
T _repository;
final _completer = Completer<T>();

@override
void initState() {
super.initState();
if (!widget.lazy) {
_repository = widget.create(context);
_completer.complete(_repository);
}
}

@override
Widget buildWithChild(BuildContext context, Widget child) {
return _InheritedRepository(
create: () {
_repository ??= widget.create(context);
value: () {
if (!_completer.isCompleted) {
_repository = widget.create(context);
_completer.complete(_repository);
}
return _repository;
},
repository: _repository,
future: _completer.future,
child: child ?? widget.child,
);
}
Expand All @@ -122,18 +129,59 @@ extension RepositoryProviderExtension on BuildContext {
}

class _InheritedRepository<T> extends InheritedWidget {
_InheritedRepository({
const _InheritedRepository({
Key key,
@required this.create,
@required this.repository,
this.future,
this.value,
@required Widget child,
}) : super(key: key, child: child);
}) : assert(child != null),
super(key: key, child: child);

final T Function() create;
final T repository;
final Future<void> future;
final ValueGetter<T> value;

@override
bool updateShouldNotify(_InheritedRepository<T> oldWidget) {
return oldWidget.repository != repository;
return oldWidget.future != future;
}

@override
_InheritedRepositoryElement<T> createElement() =>
_InheritedRepositoryElement<T>(this);
}

class _InheritedRepositoryElement<T> extends InheritedElement {
_InheritedRepositoryElement(_InheritedRepository<T> widget) : super(widget) {
widget.future?.then((_) => _handleUpdate());
}

@override
_InheritedRepository<T> get widget => super.widget as _InheritedRepository<T>;

bool _dirty = false;

@override
void update(_InheritedRepository<T> newWidget) {
final oldFuture = widget.future;
final newFuture = newWidget.future;
if (oldFuture != newFuture) newFuture?.then((_) => _handleUpdate());
super.update(newWidget);
}

@override
Widget build() {
if (_dirty) notifyClients(widget);
return super.build();
}

void _handleUpdate() {
_dirty = true;
markNeedsBuild();
}

@override
void notifyClients(_InheritedRepository<T> oldWidget) {
super.notifyClients(oldWidget);
_dirty = false;
}
}
2 changes: 1 addition & 1 deletion packages/flutter_bloc/pubspec.yaml
Expand Up @@ -13,7 +13,7 @@ dependencies:
flutter:
sdk: flutter
bloc: ^6.0.0
inherited_stream: ^1.0.0
inherited_stream: ^1.1.0
nested: ^0.0.4

dev_dependencies:
Expand Down
47 changes: 22 additions & 25 deletions packages/flutter_bloc/test/bloc_provider_test.dart
Expand Up @@ -245,11 +245,10 @@ void main() {
child: const CounterPage(),
));

final _counterFinder = find.byKey((const Key('counter_text')));
expect(_counterFinder, findsOneWidget);

final _counterText = _counterFinder.evaluate().first.widget as Text;
expect(_counterText.data, '0');
final counterText = tester.widget<Text>(
find.byKey((const Key('counter_text'))),
);
expect(counterText.data, '0');
});

testWidgets(
Expand Down Expand Up @@ -312,11 +311,11 @@ void main() {
child: RoutePage(),
));

final _routeButtonFinder = find.byKey((const Key('route_button')));
expect(_routeButtonFinder, findsOneWidget);
final routeButtonFinder = find.byKey((const Key('route_button')));
expect(routeButtonFinder, findsOneWidget);
expect(closeCalled, false);

await tester.tap(_routeButtonFinder);
await tester.tap(routeButtonFinder);
await tester.pumpAndSettle();

expect(closeCalled, false);
Expand Down Expand Up @@ -348,11 +347,11 @@ void main() {
final Widget _child = RoutePage();
await tester.pumpWidget(MyApp(value: value, child: _child));

final _routeButtonFinder = find.byKey((const Key('route_button')));
expect(_routeButtonFinder, findsOneWidget);
final routeButtonFinder = find.byKey((const Key('route_button')));
expect(routeButtonFinder, findsOneWidget);
expect(closeCalled, false);

await tester.tap(_routeButtonFinder);
await tester.tap(routeButtonFinder);
await tester.pumpAndSettle();

expect(closeCalled, false);
Expand Down Expand Up @@ -403,9 +402,9 @@ void main() {
expect(numBuilds, 1);
});

testWidgets(
'should access cubit instance'
'via BlocProviderExtension', (tester) async {
testWidgets('should access cubit instance via context.bloc',
(tester) async {
const textKey = Key('__text__');
await tester.pumpWidget(
BlocProvider(
create: (_) => CounterCubit(),
Expand All @@ -414,22 +413,19 @@ void main() {
body: Builder(
builder: (context) => Text(
'${context.bloc<CounterCubit>().state}',
key: const Key('value_data'),
key: textKey,
),
),
),
),
),
);
final _counterFinder = find.byKey((const Key('value_data')));
expect(_counterFinder, findsOneWidget);

final _counterText = _counterFinder.evaluate().first.widget as Text;
expect(_counterText.data, '0');
final counterText = tester.widget<Text>(find.byKey(textKey));
expect(counterText.data, '0');
});

testWidgets('context.listen registers context as dependant',
(tester) async {
testWidgets('listen: true registers context as dependant', (tester) async {
const textKey = Key('__text__');
const buttonKey = Key('__button__');
var counterCubitCreateCount = 0;
Expand All @@ -449,10 +445,11 @@ void main() {
body: Builder(
builder: (context) {
textBuildCount++;
return Text(
'${context.listen<CounterCubit, int>()}',
key: textKey,
);
final count = BlocProvider.of<CounterCubit>(
context,
listen: true,
).state;
return Text('$count', key: textKey);
},
),
floatingActionButton: FloatingActionButton(
Expand Down

0 comments on commit 61e5dc5

Please sign in to comment.