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: add EventTransformer for all incoming events #3623

Closed
maRci002 opened this issue Nov 10, 2022 · 4 comments
Closed

feat: add EventTransformer for all incoming events #3623

maRci002 opened this issue Nov 10, 2022 · 4 comments
Assignees
Labels
question Further information is requested

Comments

@maRci002
Copy link

maRci002 commented Nov 10, 2022

Description

Via Bloc.on<E extends Event>(EventHandler<E, State> handler, {EventTransformer<E>? transformer}) an EventTransformer's can be registered to Bloc. The registered transformer gets only the specific E events.

Problem is: multiple EventHandler cannot share the same transformer.

For instance I've a PaginatingBloc which has two handlers with restartable transformer from bloc_concurrency:

  • _onTestEventLoadMoreItem: load more items
  • _onTestEventRefresh: reloads the first 10 items

if _onTestEventLoadMoreItem is called which takes 2 seconds then user also pulls for _onTestEventRefresh which takes 1 second then the _onTestEventLoadMoreItem.emit should be discarded.

import 'package:bloc/bloc.dart';
import 'package:bloc_concurrency/bloc_concurrency.dart';

abstract class TestEvent {}

class TestEventRefresh extends TestEvent {}

class TestEventLoadMoreItems extends TestEvent {}

class TestBloc extends Bloc<TestEvent, List<int>> {
  TestBloc.withItems(List<int> items) : super(items) {
    on<TestEventRefresh>(
      _onTestEventRefresh,
      transformer: restartable(),
    );
    on<TestEventLoadMoreItems>(
      _onTestEventLoadMoreItems,
      transformer: restartable(),
    );
  }

  Future<void> _onTestEventRefresh(
    TestEventRefresh event,
    Emitter<List<int>> emit,
  ) async {
    await Future.delayed(const Duration(seconds: 1));
    emit(List.generate(10, (index) => index));
  }

  Future<void> _onTestEventLoadMoreItems(
    TestEventLoadMoreItems event,
    Emitter<List<int>> emit,
  ) async {
    await Future.delayed(const Duration(seconds: 2));

    final last = state.last + 1;
    emit(state + List.generate(10, (index) => last + index));
  }
}

Future<void> main() async {
  final bloc = TestBloc.withItems(List.generate(10, (index) => index));
  bloc.stream.listen(print);

  // user reaches end of the ListView
  bloc.add(TestEventLoadMoreItems());

  // user scrolls up to trigger a refresh
  await Future.delayed(const Duration(milliseconds: 200));
  bloc.add(TestEventRefresh());
}

Output:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]

Desired Output:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
@jacobtipp
Copy link

jacobtipp commented Nov 10, 2022

@maRci002 in your example

class TestEventRefresh extends TestEvent {}

class TestEventLoadMoreItems extends TestEvent {}

these are both derived events of the base TestEvent

The functionality you desire is to have a single event handler for the base event and handle all derived events inside of it.

Like so:

abstract class TestEvent {}

class TestEventRefresh extends TestEvent {}

class TestEventLoadMoreItems extends TestEvent {}

class TestBloc extends Bloc<TestEvent, List<int>> {
  TestBloc.withItems(List<int> items) : super(items) {
    on<TestEvent>( // subscribe to base event instead of derived events
      _onTestEvent,
      transformer: restartable(),
    );
  }

 Future<void> _onTestEvent(
    TestEvent event,
    Emitter<List<int>> emit,
  ) async {
    // handle derived events by narrowing them down individually and invoking their handlers
    if (event is TestEventRefresh) _onTestEventRefresh(event, emit)
    if (event is TestEventLoadMoreItems) _onTestEventLoadMoreItems(event, emit)
  }

  Future<void> _onTestEventRefresh(
    TestEventRefresh event,
    Emitter<List<int>> emit,
  ) async {
    await Future.delayed(const Duration(seconds: 1));
    emit(List.generate(10, (index) => index));
  }

  Future<void> _onTestEventLoadMoreItems(
    TestEventLoadMoreItems event,
    Emitter<List<int>> emit,
  ) async {
    await Future.delayed(const Duration(seconds: 2));

    final last = state.last + 1;
    emit(state + List.generate(10, (index) => last + index));
  }
}

If either of the derived events are being handled and another derived event comes in, it will invoke the new handler and the pervious event handler is discarded and will no longer emit with restartable() transform

@felangel felangel added question Further information is requested waiting for response Waiting for follow up labels Nov 10, 2022
@maRci002
Copy link
Author

@jacobtipp thanks for advice, I'm wondering if this is the way, I already thought about it.

Unfortunatelly the output is the same this might be another issue ( #3349 #3586 ). I am advised to check if (emit.isDone) return; but in this case both _onTestEventRefresh and _onTestEventLoadMoreItems will evaluate true especially if these handlers emits multiple states I cannot rely on isDone flag.

import 'package:bloc/bloc.dart';
import 'package:bloc_concurrency/bloc_concurrency.dart';

abstract class TestEvent {}

class TestEventRefresh extends TestEvent {}

class TestEventLoadMoreItems extends TestEvent {}

class TestBloc extends Bloc<TestEvent, List<int>> {
  TestBloc.withItems(List<int> items) : super(items) {
    on<TestEvent>(
      // subscribe to base event instead of derived events
      _onTestEvent,
      transformer: restartable(),
    );
  }

  Future<void> _onTestEvent(
    TestEvent event,
    Emitter<List<int>> emit,
  ) async {
    // handle derived events by narrowing them down individually and invoking their handlers
    if (event is TestEventRefresh) _onTestEventRefresh(event, emit);
    if (event is TestEventLoadMoreItems) _onTestEventLoadMoreItems(event, emit);
  }

  Future<void> _onTestEventRefresh(
    TestEventRefresh event,
    Emitter<List<int>> emit,
  ) async {
    print('_onTestEventRefresh (1) active: ${!emit.isDone}');

    await Future.delayed(const Duration(seconds: 1));
    print('_onTestEventRefresh (2) active: ${!emit.isDone}');
    emit(List.generate(10, (index) => index));
  }

  Future<void> _onTestEventLoadMoreItems(
    TestEventLoadMoreItems event,
    Emitter<List<int>> emit,
  ) async {
    print('_onTestEventLoadMoreItems (1) active: ${!emit.isDone}');

    await Future.delayed(const Duration(seconds: 2));
    print('_onTestEventLoadMoreItems (2) active: ${!emit.isDone}');
    final last = state.last + 1;
    emit(state + List.generate(10, (index) => last + index));
  }
}

Future<void> main() async {
  final bloc = TestBloc.withItems(List.generate(10, (index) => index));
  bloc.stream.listen(print);

  // user reaches end of the ListView
  bloc.add(TestEventLoadMoreItems());

  // user scrolls up to trigger a refresh
  await Future.delayed(const Duration(milliseconds: 200));
  bloc.add(TestEventRefresh());
}

Output:

_onTestEventLoadMoreItems (1) active: true
_onTestEventRefresh (1) active: true
_onTestEventRefresh (2) active: false
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
_onTestEventLoadMoreItems (2) active: false
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]

_onTestEventRefresh (2) active: false this is the problem.

@jacobtipp
Copy link

jacobtipp commented Nov 10, 2022

@maRci002 I apologize, I forgot that the handler is async so you would need to await both derived event handlers

change this:

  Future<void> _onTestEvent(
    TestEvent event,
    Emitter<List<int>> emit,
  ) async {
    // handle derived events by narrowing them down individually and invoking their handlers
    if (event is TestEventRefresh) _onTestEventRefresh(event, emit);
    if (event is TestEventLoadMoreItems) _onTestEventLoadMoreItems(event, emit);
  }

to this:

  Future<void> _onTestEvent(
    TestEvent event,
    Emitter<List<int>> emit,
  ) async {
    // handle derived events by narrowing them down individually and invoking their handlers
    if (event is TestEventRefresh) await _onTestEventRefresh(event, emit); // must await here in order for it to resolve properly
    if (event is TestEventLoadMoreItems) await _onTestEventLoadMoreItems(event, emit); // same as above ^
  }

let me know if this is your desired outcome

@maRci002
Copy link
Author

@jacobtipp thank you very much your effort it works like a charm.

Usually unawaited_futures is enabled on my projects so I would be forced to await derived event handlers.

Output:

_onTestEventLoadMoreItems (1) active: true
_onTestEventRefresh (1) active: true
_onTestEventRefresh (2) active: true
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
_onTestEventLoadMoreItems (2) active: false

@felangel felangel removed the waiting for response Waiting for follow up label Nov 11, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

3 participants