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

feat(bloc)!: BlocOverrides API #2932

Merged
merged 12 commits into from
Nov 14, 2021
10 changes: 6 additions & 4 deletions packages/bloc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,8 +166,9 @@ class MyBlocObserver extends BlocObserver {

```dart
void main() {
Bloc.observer = MyBlocObserver();
// Use cubits...
BlocOverrides.runZoned(() {
// Use cubits...
}, blocObserver: MyBlocObserver());
}
```

Expand Down Expand Up @@ -317,8 +318,9 @@ class MyBlocObserver extends BlocObserver {

```dart
void main() {
Bloc.observer = MyBlocObserver();
// Use blocs...
BlocOverrides.runZoned(() {
// Use blocs...
}, blocObserver: MyBlocObserver());
}
```

Expand Down
8 changes: 4 additions & 4 deletions packages/bloc/example/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ class SimpleBlocObserver extends BlocObserver {
}

void main() {
Bloc.observer = SimpleBlocObserver();

cubitMain();
blocMain();
BlocOverrides.runZoned(() {
cubitMain();
blocMain();
}, blocObserver: SimpleBlocObserver());
}

void cubitMain() {
Expand Down
130 changes: 108 additions & 22 deletions packages/bloc/lib/src/bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,100 @@ import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:meta/meta.dart';

const _asyncRunZoned = runZoned;

/// This class facilitates overriding [BlocObserver] and [EventTransformer].
/// It should be extended by another class in client code with overrides
/// that construct a custom implementation. The implementation in this class
/// defaults to the base [blocObserver] and [eventTransformer] implementation.
/// For example:
///
/// ```dart
/// class MyBlocObserver extends BlocObserver {
/// ...
/// // A custom BlocObserver implementation.
/// ...
/// }
///
/// void main() {
/// BlocOverrides.runZoned(() {
/// ...
/// // Bloc instances will use MyBlocObserver instead of the default BlocObserver.
/// ...
/// }, blocObserver: MyBlocObserver());
/// }
/// ```
abstract class BlocOverrides {
static final _token = Object();

/// Returns the current [BlocOverrides] instance.
///
/// This will return `null` if the current [Zone] does not contain
/// any [BlocOverrides].
///
/// See also:
/// * [BlocOverrides.runZoned] to provide [BlocOverrides] in a fresh [Zone].
///
static BlocOverrides? get current => Zone.current[_token] as BlocOverrides?;

/// Runs [body] in a fresh [Zone] using the provided overrides.
static R runZoned<R>(
R Function() body, {
BlocObserver? blocObserver,
EventTransformer? eventTransformer,
}) {
final overrides = _BlocOverridesScope(blocObserver, eventTransformer);
return _asyncRunZoned(body, zoneValues: {_token: overrides});
}

/// The default [BlocObserver] instance.
BlocObserver get blocObserver => Bloc._defaultBlocObserver;

/// The default [EventTransformer] used for all event handlers.
///
/// By default all events are processed concurrently.
///
/// If a custom transformer is specified for a particular event handler,
/// it will take precendence over the global transformer.
///
/// See also:
///
/// * [package:bloc_concurrency](https://pub.dev/packages/bloc_concurrency) for an
/// opinionated set of event transformers.
///
EventTransformer get eventTransformer => Bloc._defaultEventTransformer;
}

class _BlocOverridesScope extends BlocOverrides {
_BlocOverridesScope(this._blocObserver, this._eventTransformer);

final BlocOverrides? _previous = BlocOverrides.current;
final BlocObserver? _blocObserver;
final EventTransformer? _eventTransformer;

@override
BlocObserver get blocObserver {
final blocObserver = _blocObserver;
if (blocObserver != null) return blocObserver;

final previous = _previous;
if (previous != null) return previous.blocObserver;

return super.blocObserver;
}

@override
EventTransformer get eventTransformer {
final eventTransformer = _eventTransformer;
if (eventTransformer != null) return eventTransformer;

final previous = _previous;
if (previous != null) return previous.eventTransformer;

return super.eventTransformer;
}
}

/// {@template emitter}
/// An [Emitter] is a class which is capable of emitting new states.
///
Expand Down Expand Up @@ -220,21 +314,8 @@ abstract class Bloc<Event, State> extends BlocBase<State> {
/// {@macro bloc}
Bloc(State initialState) : super(initialState);

/// The current [BlocObserver] instance.
static BlocObserver observer = BlocObserver();

/// The default [EventTransformer] used for all event handlers.
/// By default all events are processed concurrently.
///
/// If a custom transformer is specified for a particular event handler,
/// it will take precendence over the global transformer.
///
/// See also:
///
/// * [package:bloc_concurrency](https://pub.dev/packages/bloc_concurrency) for an
/// opinionated set of event transformers.
///
static EventTransformer<dynamic> transformer = (events, mapper) {
static final _defaultBlocObserver = BlocObserver();
static final _defaultEventTransformer = (Stream events, EventMapper mapper) {
return events
.map(mapper)
.transform<dynamic>(const _FlatMapStreamTransformer<dynamic>());
Expand All @@ -244,6 +325,8 @@ abstract class Bloc<Event, State> extends BlocBase<State> {
final _subscriptions = <StreamSubscription<dynamic>>[];
final _handlers = <_Handler>[];
final _emitters = <_Emitter>[];
final _eventTransformer =
BlocOverrides.current?.eventTransformer ?? _defaultEventTransformer;

/// Notifies the [Bloc] of a new [event] which triggers
/// all corresponding [EventHandler] instances.
Expand Down Expand Up @@ -296,7 +379,7 @@ abstract class Bloc<Event, State> extends BlocBase<State> {
@mustCallSuper
void onEvent(Event event) {
// ignore: invalid_use_of_protected_member
observer.onEvent(this, event);
_blocObserver.onEvent(this, event);
}

/// {@template emit}
Expand Down Expand Up @@ -364,7 +447,7 @@ abstract class Bloc<Event, State> extends BlocBase<State> {
return true;
}());

final _transformer = transformer ?? Bloc.transformer;
final _transformer = transformer ?? _eventTransformer;
final subscription = _transformer(
_eventController.stream.where((event) => event is E).cast<E>(),
(dynamic event) {
Expand Down Expand Up @@ -436,7 +519,7 @@ abstract class Bloc<Event, State> extends BlocBase<State> {
@mustCallSuper
void onTransition(Transition<Event, State> transition) {
// ignore: invalid_use_of_protected_member
Bloc.observer.onTransition(this, transition);
_blocObserver.onTransition(this, transition);
}

/// Closes the `event` and `state` `Streams`.
Expand Down Expand Up @@ -487,9 +570,12 @@ abstract class BlocBase<State> {
/// {@macro bloc_stream}
BlocBase(this._state) {
// ignore: invalid_use_of_protected_member
Bloc.observer.onCreate(this);
_blocObserver.onCreate(this);
}

final _blocObserver =
BlocOverrides.current?.blocObserver ?? Bloc._defaultBlocObserver;

StreamController<State>? __stateController;
StreamController<State> get _stateController {
return __stateController ??= StreamController<State>.broadcast();
Expand Down Expand Up @@ -560,7 +646,7 @@ abstract class BlocBase<State> {
@mustCallSuper
void onChange(Change<State> change) {
// ignore: invalid_use_of_protected_member
Bloc.observer.onChange(this, change);
_blocObserver.onChange(this, change);
}

/// Reports an [error] which triggers [onError] with an optional [StackTrace].
Expand All @@ -586,7 +672,7 @@ abstract class BlocBase<State> {
@mustCallSuper
void onError(Object error, StackTrace stackTrace) {
// ignore: invalid_use_of_protected_member
Bloc.observer.onError(this, error, stackTrace);
_blocObserver.onError(this, error, stackTrace);
}

/// Closes the instance.
Expand All @@ -595,7 +681,7 @@ abstract class BlocBase<State> {
@mustCallSuper
Future<void> close() async {
// ignore: invalid_use_of_protected_member
Bloc.observer.onClose(this);
_blocObserver.onClose(this);
await _stateController.close();
}
}
Expand Down
122 changes: 58 additions & 64 deletions packages/bloc/test/bloc_event_transformer_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,6 @@ class CounterBloc extends Bloc<CounterEvent, int> {
}

void main() {
late EventTransformer transformer;

setUp(() {
transformer = Bloc.transformer;
});

tearDown(() {
Bloc.transformer = transformer;
});

test('processes events concurrently by default', () async {
final states = <int>[];
final bloc = CounterBloc()
Expand Down Expand Up @@ -196,77 +186,81 @@ void main() {
test(
'processes events sequentially when '
'Bloc.transformer is overridden.', () async {
Bloc.transformer = (events, mapper) => events.asyncExpand<dynamic>(mapper);
final states = <int>[];
final bloc = CounterBloc()
..stream.listen(states.add)
..add(Increment())
..add(Increment())
..add(Increment());
await BlocOverrides.runZoned(
() async {
final states = <int>[];
final bloc = CounterBloc()
..stream.listen(states.add)
..add(Increment())
..add(Increment())
..add(Increment());

await tick();
await tick();

expect(
bloc.onCalls,
equals([Increment()]),
);
expect(
bloc.onCalls,
equals([Increment()]),
);

await wait();
await wait();

expect(
bloc.onEmitCalls,
equals([Increment()]),
);
expect(states, equals([1]));
expect(
bloc.onEmitCalls,
equals([Increment()]),
);
expect(states, equals([1]));

await tick();
await tick();

expect(
bloc.onCalls,
equals([Increment(), Increment()]),
);
expect(
bloc.onCalls,
equals([Increment(), Increment()]),
);

await wait();
await wait();

expect(
bloc.onEmitCalls,
equals([Increment(), Increment()]),
);
expect(
bloc.onEmitCalls,
equals([Increment(), Increment()]),
);

expect(states, equals([1, 2]));
expect(states, equals([1, 2]));

await tick();
await tick();

expect(
bloc.onCalls,
equals([
Increment(),
Increment(),
Increment(),
]),
);
expect(
bloc.onCalls,
equals([
Increment(),
Increment(),
Increment(),
]),
);

await wait();
await wait();

expect(
bloc.onEmitCalls,
equals([Increment(), Increment(), Increment()]),
);
expect(
bloc.onEmitCalls,
equals([Increment(), Increment(), Increment()]),
);

expect(states, equals([1, 2, 3]));
expect(states, equals([1, 2, 3]));

await bloc.close();
await bloc.close();

expect(
bloc.onCalls,
equals([Increment(), Increment(), Increment()]),
);
expect(
bloc.onCalls,
equals([Increment(), Increment(), Increment()]),
);

expect(
bloc.onEmitCalls,
equals([Increment(), Increment(), Increment()]),
);
expect(
bloc.onEmitCalls,
equals([Increment(), Increment(), Increment()]),
);

expect(states, equals([1, 2, 3]));
expect(states, equals([1, 2, 3]));
},
eventTransformer: (events, mapper) => events.asyncExpand<dynamic>(mapper),
);
});
}
Loading