Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions packages/bloc_concurrency/lib/src/droppable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class _ExhaustMapStreamTransformer<T> extends StreamTransformerBase<T, T> {
Stream<T> bind(Stream<T> stream) {
late StreamSubscription<T> subscription;
StreamSubscription<T>? mappedSubscription;
var isSourceDone = false;

final controller = StreamController<T>(
onCancel: () async {
Expand All @@ -30,6 +31,12 @@ class _ExhaustMapStreamTransformer<T> extends StreamTransformerBase<T, T> {
sync: true,
);

// Close the output stream once the source has completed and there is no
// event currently in flight.
void maybeClose() {
if (isSourceDone && mappedSubscription == null) controller.close();
}

subscription = stream.listen(
(data) {
if (mappedSubscription != null) return;
Expand All @@ -39,11 +46,17 @@ class _ExhaustMapStreamTransformer<T> extends StreamTransformerBase<T, T> {
mappedSubscription = mappedStream.listen(
controller.add,
onError: controller.addError,
onDone: () => mappedSubscription = null,
onDone: () {
mappedSubscription = null;
maybeClose();
},
);
},
onError: controller.addError,
onDone: () => mappedSubscription ?? controller.close(),
onDone: () {
isSourceDone = true;
maybeClose();
},
);

return controller.stream;
Expand Down
34 changes: 34 additions & 0 deletions packages/bloc_concurrency/test/src/droppable_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -105,5 +105,39 @@ void main() {

expect(states, isEmpty);
});

test('closes the output stream when the source ends mid-event', () async {
final states = <int>[];
var isDone = false;
final controller = StreamController<int>();
final stream = droppable<int>()(controller.stream, (x) async* {
await wait();
yield x;
});

final subscription = stream.listen(
states.add,
onDone: () => isDone = true,
);

controller.add(0);

await tick();

// The event handler is in flight when the source stream completes.
await controller.close();

expect(isDone, isFalse);
expect(states, isEmpty);

await wait();
await tick();

// The in-flight event is still delivered and the output stream closes.
expect(states, equals([0]));
expect(isDone, isTrue);

await subscription.cancel();
});
});
}
Loading