felangel / bloc

A predictable state management library that helps implement the BLoC design pattern

Home Page:https://bloclibrary.dev

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

fix: transformer works incorrectly while registering each event separately

II11II opened this issue · comments

Description
V1PushNotificationBloc jumps to another event processing while the first is awaited

Steps To Reproduce

  1. Go to the Gist
  2. Copy the code and run it locally
  3. Compare the outputs

Expected Behavior
V1PushNotificationBloc has to proceed with each event sequentially

Actual Behavior
V1PushNotificationBloc does not proceed with each event sequentially

Screenshots
image

import 'dart:async';

import 'package:bloc/bloc.dart';

void main() async{
  final pushNotification = PushNotification();
  final v1 = V1PushNotificationBloc(pushNotification: pushNotification);
  v1.add(PushNotificationEvent.started());
  v1.add(PushNotificationEvent.subscribe("V1PushNotificationBlocTopic"));
  await Future.delayed(Duration(seconds: 1), (){
    print("\n\n\n");
  });
  final v2 = V2PushNotificationBloc(pushNotification: pushNotification);
  v2.add(PushNotificationEvent.started());
  v2.add(PushNotificationEvent.subscribe("V2PushNotificationBlocTopic"));
}

class PushNotification {
  Future<String> initNotification() async {
    await Future.delayed(Duration(milliseconds: 200));
    return "token";
  }

  Future<void> subscribeToTopic(String topic) async {
    await Future.delayed(Duration(milliseconds: 200));
    print("PushNotification.subscribeToTopic($topic) is called");
  }

  Future<void> unsubscribeFromTopic(String topic) async {
    await Future.delayed(Duration(milliseconds: 200));
    print("PushNotification.unsubscribeFromTopic($topic) is called");
  }
}

EventTransformer<Event> sequential<Event>() {
  return (events, mapper) => events.asyncExpand(mapper);
}

sealed class PushNotificationEvent {
  const factory PushNotificationEvent.started() = _Started;
  const factory PushNotificationEvent.subscribe(String topicType) = _Subscribe;
  const factory PushNotificationEvent.unsubscribe(String topicType) =
      _Unsubscribe;
}

class _Started implements PushNotificationEvent {
  const _Started();
}

class _Subscribe implements PushNotificationEvent {
  final String topicType;
  const _Subscribe(this.topicType);
}

class _Unsubscribe implements PushNotificationEvent {
  final String topicType;
  const _Unsubscribe(this.topicType);
}

sealed class PushNotificationState {
  const factory PushNotificationState.initial() = _Initial;
  const factory PushNotificationState.granted({required String token}) =
      _Granted;
  const factory PushNotificationState.denied() = _Denied;
}

class _Initial implements PushNotificationState {
  const _Initial();
}

class _Granted implements PushNotificationState {
  final String token;
  const _Granted({required this.token});
}

class _Denied implements PushNotificationState {
  const _Denied();
}

class V1PushNotificationBloc
    extends Bloc<PushNotificationEvent, PushNotificationState> {
  V1PushNotificationBloc({required PushNotification pushNotification})
      : _pushNotification = pushNotification,
        super(const PushNotificationState.initial()) {
    on<_Started>(_onStarted, transformer: sequential());
    on<_Subscribe>(_onSubscribe, transformer: sequential());
    on<_Unsubscribe>(_onUnsubscribe, transformer: sequential());
  }
  final PushNotification _pushNotification;
  FutureOr<void> _onStarted(
      _Started event, Emitter<PushNotificationState> emit) async {
    print(
        "V1PushNotificationBloc._onStarted is called-The current state is PushNotificationState.initial");
    String token = await _pushNotification.initNotification();
    emit(PushNotificationState.granted(token: token));
    print(
        "V1PushNotificationBloc._onStarted is called-The current state is PushNotificationState.granted");
  }

  FutureOr<void> _onSubscribe(
          _Subscribe event, Emitter<PushNotificationState> emit) =>
      switch (state) {
        final _Granted grantedState => (_Granted grantedState) async {
            await _pushNotification.subscribeToTopic(event.topicType);
            print(
                "V1PushNotificationBloc._onSubscribe is called-Topic type: ${event.topicType}-The current state is granted");
          }(grantedState),
        _ => () {
            print(
                "V1PushNotificationBloc._onSubscribe is called-Topic type: ${event.topicType}-The current state is initial");
          }()
      };

  FutureOr<void> _onUnsubscribe(
          _Unsubscribe event, Emitter<PushNotificationState> emit) =>
      switch (state) {
        final _Granted grantedState => (_Granted grantedState) async {
            await _pushNotification.unsubscribeFromTopic(event.topicType);
            print(
                "V1PushNotificationBloc._onUnsubscribe is called-Topic type: ${event.topicType}-The current state is granted");
          }(grantedState),
        _ => () {
            print(
                "V1PushNotificationBloc._onUnsubscribe is called-Topic type: ${event.topicType}-The current state is initial");
          }()
      };
}

class V2PushNotificationBloc
    extends Bloc<PushNotificationEvent, PushNotificationState> {
  V2PushNotificationBloc({required PushNotification pushNotification})
      : _pushNotification = pushNotification,
        super(const PushNotificationState.initial()) {
    on<PushNotificationEvent>(
        (event, emit) => switch (event) {
              _Started() => _onStarted(event, emit),
              _Subscribe() => _onSubscribe(event, emit),
              _Unsubscribe() => _onUnsubscribe(event, emit),
            },
        transformer: sequential());
  }
  final PushNotification _pushNotification;
  FutureOr<void> _onStarted(
      _Started event, Emitter<PushNotificationState> emit) async {
    print(
        "V2PushNotificationBloc._onStarted is called-The current state is PushNotificationState.initial");
    String token = await _pushNotification.initNotification();
    emit(PushNotificationState.granted(token: token));
    print(
        "V2PushNotificationBloc._onStarted is called-The current state is PushNotificationState.granted");
  }

  FutureOr<void> _onSubscribe(
          _Subscribe event, Emitter<PushNotificationState> emit) =>
      switch (state) {
        final _Granted grantedState => (_Granted grantedState) async {
            await _pushNotification.subscribeToTopic(event.topicType);
          print(
                "V2PushNotificationBloc._onSubscribe is called-Topic type: ${event.topicType}-The current state is granted");
          }(grantedState),
        _ => () {
          print(
                "V2PushNotificationBloc._onSubscribe is called-Topic type: ${event.topicType}-The current state is initial");
        }()
      };

  FutureOr<void> _onUnsubscribe(
          _Unsubscribe event, Emitter<PushNotificationState> emit) =>
      switch (state) {
        final _Granted grantedState => (_Granted grantedState) async {
            await _pushNotification.unsubscribeFromTopic(event.topicType);
           print(
                "V2PushNotificationBloc._onUnsubscribe is called-Topic type: ${event.topicType}-The current state is granted");
          }(grantedState),
        _ => () {
          print(
                "V2PushNotificationBloc._onUnsubscribe is called-Topic type: ${event.topicType}-The current state is initial");
        }()
      };
}

/// Output:
// V1PushNotificationBloc._onStarted is called-The current state is PushNotificationState.initial
// V1PushNotificationBloc._onSubscribe is called-Topic type: V1PushNotificationBlocTopic-The current state is initial
// V1PushNotificationBloc._onStarted is called-The current state is PushNotificationState.granted
//
//
//
//
// V2PushNotificationBloc._onStarted is called-The current state is PushNotificationState.initial
// V2PushNotificationBloc._onStarted is called-The current state is PushNotificationState.granted
// PushNotification.subscribeToTopic(V2PushNotificationBlocTopic) is called
// V2PushNotificationBloc._onSubscribe is called-Topic type: V2PushNotificationBlocTopic-The current state is granted

Hi @II11II 👋
Thanks for opening an issue!

This is working as expected. Transformers only apply to the event handler that they are applied to. If you want all events to be sequential, then you need to create a single event handler like so:

sealed class CounterEvent {}

final class Increment extends CounterEvent {}

final class Decrement extends CounterEvent {}

class CounterBloc extends Bloc<CounterEvent, int> {
  CounterBloc() : super(0) {
    on<CounterEvent>(
      (event, emit) {
        return switch (event) {
          Increment() => emit(state + 1),
          Decrement() => emit(state - 1),
        };
      },
      transformer: sequential(),
    );
  }
}

Hope that helps! Closing for now 👍