Skip to content

Instantly share code, notes, and snippets.

@felangel
Created February 27, 2019 04:02
Show Gist options
  • Save felangel/6614b9ce0a536ef462138a0ba698053a to your computer and use it in GitHub Desktop.
Save felangel/6614b9ce0a536ef462138a0ba698053a to your computer and use it in GitHub Desktop.
Bloc Exception Handling
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
abstract class AuthenticationEvent extends Equatable {
AuthenticationEvent([List props = const []]) : super(props);
}
class LoginEvent extends AuthenticationEvent {
final String loginRequest;
LoginEvent(this.loginRequest) : super([loginRequest]);
@override
String toString() {
return 'LoginEvent{loginRequest: $loginRequest}';
}
}
abstract class AuthenticationState extends Equatable {
AuthenticationState([List props = const []]) : super(props);
}
class AuthenticationStateUnInitialized extends AuthenticationState {
@override
String toString() => 'AuthenticationStateUnInitialized';
}
class AuthenticationStateLoading extends AuthenticationState {
@override
String toString() => 'AuthenticationStateLoading';
}
class AuthenticationStateSuccess extends AuthenticationState {
String user;
AuthenticationStateSuccess(this.user) : super([user]);
@override
String toString() => 'AuthenticationStateSuccess{ user: $user }';
}
class AuthenticationStateError extends AuthenticationState {
final String error;
AuthenticationStateError(this.error) : super([error]);
@override
String toString() => 'AuthenticationStateError { error: $error }';
}
class AuthenticationBloc
extends Bloc<AuthenticationEvent, AuthenticationState> {
AuthenticationBloc();
@override
AuthenticationState get initialState => AuthenticationStateUnInitialized();
@override
Stream<AuthenticationState> mapEventToState(
AuthenticationState currentState,
AuthenticationEvent event,
) async* {
if (event is LoginEvent) {
yield AuthenticationStateLoading();
try {
final result = await _login(event.loginRequest);
yield AuthenticationStateSuccess(result);
} catch (e) {
yield AuthenticationStateError("error processing login request");
}
} else {
print("unknown");
}
}
Future<String> _login(dynamic request) async {
throw Exception();
}
@override
void onTransition(
Transition<AuthenticationEvent, AuthenticationState> transition,
) {
print(transition);
}
@override
void onError(Object error, StackTrace stacktrace) {
print('error in bloc $error $stacktrace');
}
}
void main() async {
AuthenticationBloc authBloc = AuthenticationBloc();
authBloc.state.listen((authState) {
print(authState);
});
authBloc.dispatch(LoginEvent('blah'));
await Future.delayed(Duration(seconds: 3));
authBloc.dispatch(LoginEvent('blah'));
}
@fnicastri
Copy link

@astubenbord @felangel

I'm experimenting with this idea, I have a proof of concept implementation of this second stream in a Cubit, it works well but, again, is a very experimental and rough implementation.
Never had the time to do it in a proper way.

This is the implementation, again it's just a fast and dirty impl and tbh it's not something
I really worked on, just an experiment. Much of the stuff I just copy/pasted from the original classes to check if it was a good idea or not.
I'm sure it can be done in a much much better way.

import 'dart:async';

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

typedef BlocWidgetErrorListener<E> = void Function(
  BuildContext context,
  E error,
);

abstract class BlocBaseWithError<State, ErrorEvent> extends BlocBase<State>
    implements
        EmittableErrorEvent<ErrorEvent>,
        ErrorEventStreamable<ErrorEvent> {
  final StreamController<ErrorEvent?> _errorController =
      StreamController<ErrorEvent?>.broadcast();

  ErrorEvent? _errorEvent;

  BlocBaseWithError(super.initialState);

  @override
  ErrorEvent? get error => _errorEvent;

  @override
  Stream<ErrorEvent?> get errorStream => _errorController.stream;

  bool get isErrorClosed => _errorController.isClosed;

  @override
  Future<void> close() async {
    await _errorController.close();
    await super.close();
  }

  @override
  void emit(State state) {
    super.emit(state);
    // _errorController.sink.add(null);
  }

  @override
  void emitErrorEvent(ErrorEvent error) {
    try {
      if (isErrorClosed) {
        throw StateError('Cannot emit new errors after calling close');
      }
      _errorEvent = error;
      _errorController.add(error);
    } catch (error, stackTrace) {
      super.onError(error, stackTrace);
      rethrow;
    }
  }
}

abstract class CubitWithError<State, ErrorEvent>
    extends BlocBaseWithError<State, ErrorEvent>
    implements ErrorEventStreamableSource<ErrorEvent> {
  CubitWithError(State initialState) : super(initialState);

  @override
  Stream<ErrorEvent?> get errorStream {
    return super.errorStream;
  }
}

abstract class EEStreamable<E extends Object?> {
  /// The current [errorStream] of errors.
  Stream<E?> get errorStream;
}

/// An object that can emit new states.
abstract class EmittableErrorEvent<ErrorEvent extends Object?> {
  /// Emits a new [state].
  void emitErrorEvent(ErrorEvent state);
}

abstract class ErrorEventSink<ErrorEvent extends Object?> {
  /// Adds an [event] to the sink.
  ///
  /// Must not be called on a closed sink.
  void addErrorEvent(ErrorEvent event);
}

abstract class ErrorEventStreamable<ErrorEvent>
    implements EEStreamable<ErrorEvent> {
  /// The current [error].
  ErrorEvent? get error;
}

abstract class ErrorEventStreamableSource<ErrorEvent>
    implements ErrorEventStreamable<ErrorEvent>, Closable {}

class ErrorListener<B extends ErrorEventStreamable<E>, E>
    extends StatefulWidget {
  final BlocWidgetErrorListener<E?> errorListener;

  final E? error;
  final BlocListenerCondition<E?>? listenWhen;
  final B? bloc;
  final Widget? child;
  const ErrorListener({
    Key? key,
    required this.errorListener,
    this.error,
    this.listenWhen,
    this.child,
    this.bloc,
  }) : super(key: key);
  @override
  State<ErrorListener<B, E>> createState() => _ErrorListenerState<B, E>();
}

class _ErrorListenerState<B extends ErrorEventStreamable<E>, E>
    extends State<ErrorListener<B, E>> {
  late B _bloc;
  StreamSubscription<E?>? _subscription;
  late E? _previousError;
  @override
  Widget build(BuildContext context) {
    return widget.child ?? Container();
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    final bloc = context.read<B>();
    if (_bloc != bloc) {
      if (_subscription != null) {
        _unsubscribe();
        _bloc = bloc;
        _previousError = _bloc.error;
      }
      _subscribe();
    }
  }

  @override
  void didUpdateWidget(ErrorListener<B, E> oldWidget) {
    super.didUpdateWidget(oldWidget);
    final oldBloc = oldWidget.bloc ?? context.read<B>();
    final currentBloc = widget.bloc ?? oldBloc;
    if (oldBloc != currentBloc) {
      if (_subscription != null) {
        _unsubscribe();
        _bloc = currentBloc;
        _previousError = _bloc.error;
      }
      _subscribe();
    }
  }

  @override
  void dispose() {
    _unsubscribe();
    super.dispose();
  }

  @override
  void initState() {
    super.initState();
    _bloc = context.read<B>();
    _subscribe();
  }

  void _subscribe() {
    _subscription = _bloc.errorStream.listen((errorEvent) {
      if (widget.listenWhen?.call(_previousError, errorEvent) ?? true) {
        widget.errorListener(context, errorEvent);
      }
      _previousError = errorEvent;
    });
  }

  void _unsubscribe() {
    _subscription?.cancel();
    _subscription = null;
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment