import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:provider/single_child_widget.dart'; mixin BlocLogger { String? get tag => null; bool Function(dynamic currentState, dynamic nextState)? get shouldLog => null; } class BlocListenerT<B extends StateStreamable<S>, S, T> extends SingleChildStatelessWidget { const BlocListenerT({ super.key, super.child, required this.selector, required this.listener, }); @override Widget buildWithChild(BuildContext context, Widget? child) { return BlocListener<B, S>( listenWhen: (previous, current) => selector(previous) != selector(current), listener: (context, state) => listener(context, selector(state)), child: child, ); } final BlocWidgetSelector<S, T> selector; final void Function(BuildContext context, T state) listener; } /// Wrap around a string such that two strings with the same value will fail /// the identical check class StateMessage { StateMessage(this.value); final String value; } extension EmitterExtension<State> on Emitter<State> { Future<void> forEachIgnoreError<T>( Stream<T> stream, { required State Function(T data) onData, }) => onEach<T>( stream, onData: (data) => call(onData(data)), onError: (_, __) {}, ); } extension BlocExtension<E, S> on Bloc<E, S> { void safeAdd(E event) { if (!isClosed) { add(event); } } } class _BlocForEachObj { const _BlocForEachObj(this.subscription, this.completer); final StreamSubscription subscription; final Completer completer; } mixin BlocForEachMixin<E, S> implements Bloc<E, S> { @override Future<void> close() async { for (final e in _forEaches) { unawaited(e.subscription.cancel()); e.completer.complete(); } } // The original emit.forEach is causing the internal eventController in Bloc // to deadlock when closing, use this instead Future<void> forEach<T>( Emitter<S> emit, Stream<T> stream, { required S Function(T data) onData, S Function(Object error, StackTrace stackTrace)? onError, }) async { final completer = Completer(); final subscription = stream.listen((event) { emit(onData(event)); }, onError: (e, stackTrace) { if (onError != null) { emit(onError(e, stackTrace)); } }); _forEaches.add(_BlocForEachObj(subscription, completer)); return completer.future; } final _forEaches = <_BlocForEachObj>[]; }