Repository: ReactiveX/rxdart

Issue:
Rx.using: clean up order
https://github.com/ReactiveX/rxdart/blob/18fd718bad56ff5407b68fc3aac96ba27a38d46a/lib/src/streams/using.dart#L95-L98



Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0d58be5b..b99e05ad 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,7 +2,10 @@
 
 ### Changed
 
-- **Breaking**: Rename `ForkJoinStream.combine2`..`combine9` to `ForkJoinStream.join2`..`join9`.
+* **Breaking**: Rename `ForkJoinStream.combine2`..`combine9` to `ForkJoinStream.join2`..`join9`.
+* **Breaking**: `Rx.using`/`UsingStream`
+  * Convert all _required positional_ parameters to _required named_ parameters.
+  * The `disposer` is now called after the future returned from `StreamSubscription.cancel` completes.
 
 ## 0.28.0-dev.1 (2024-01-27)
 
diff --git a/lib/src/rx.dart b/lib/src/rx.dart
index a87921fa..eacc5d20 100644
--- a/lib/src/rx.dart
+++ b/lib/src/rx.dart
@@ -969,12 +969,16 @@ abstract class Rx {
   ///       (r) => Stream.fromIterable(r),
   ///       (r) => r.clear(),
   ///     ).listen(print); // prints 1, 2, 3
-  static Stream<T> using<T, R>(
-    FutureOr<R> Function() resourceFactory,
-    Stream<T> Function(R) streamFactory,
-    FutureOr<void> Function(R) disposer,
-  ) =>
-      UsingStream(resourceFactory, streamFactory, disposer);
+  static Stream<T> using<T, R>({
+    required FutureOr<R> Function() resourceFactory,
+    required Stream<T> Function(R) streamFactory,
+    required FutureOr<void> Function(R) disposer,
+  }) =>
+      UsingStream(
+        resourceFactory: resourceFactory,
+        streamFactory: streamFactory,
+        disposer: disposer,
+      );
 
   /// Merges the specified streams into one stream sequence using the given
   /// zipper function whenever all of the stream sequences have produced
diff --git a/lib/src/streams/using.dart b/lib/src/streams/using.dart
index 75bf6c1d..3bc25259 100644
--- a/lib/src/streams/using.dart
+++ b/lib/src/streams/using.dart
@@ -1,12 +1,11 @@
 import 'dart:async';
 
-import 'package:rxdart/src/utils/future.dart';
-
 /// When listener listens to it, creates a resource object from resource factory function,
 /// and creates a [Stream] from the given factory function and resource as argument.
 /// Finally when the stream finishes emitting items or stream subscription
 /// is cancelled (call [StreamSubscription.cancel] or `Stream.listen(cancelOnError: true)`),
 /// call the disposer function on resource object.
+/// The disposer is called after the future returned from [StreamSubscription.cancel] completes.
 ///
 /// The [UsingStream] is a way you can instruct a Stream to create
 /// a resource that exists only during the lifespan of the Stream
@@ -25,11 +24,11 @@ class UsingStream<T, R> extends StreamView<T> {
   /// Construct a [UsingStream] that creates a resource object from [resourceFactory],
   /// and then creates a [Stream] from [streamFactory] and resource as argument.
   /// When the Stream terminates, call [disposer] on resource object.
-  UsingStream(
-    FutureOr<R> Function() resourceFactory,
-    Stream<T> Function(R) streamFactory,
-    FutureOr<void> Function(R) disposer,
-  ) : super(_buildStream(resourceFactory, streamFactory, disposer));
+  UsingStream({
+    required FutureOr<R> Function() resourceFactory,
+    required Stream<T> Function(R) streamFactory,
+    required FutureOr<void> Function(R) disposer,
+  }) : super(_buildStream(resourceFactory, streamFactory, disposer));
 
   static Stream<T> _buildStream<T, R>(
     FutureOr<R> Function() resourceFactory,
@@ -93,9 +92,13 @@ class UsingStream<T, R> extends StreamView<T> {
       onPause: () => subscription?.pause(),
       onResume: () => subscription?.resume(),
       onCancel: () {
-        final futureOr = resourceCreated ? disposer(resource) : null;
         final cancelFuture = subscription?.cancel();
-        return waitTwoFutures(cancelFuture, futureOr);
+        subscription = null;
+
+        return cancelFuture == null
+            ? (resourceCreated ? disposer(resource) : null)
+            : cancelFuture
+                .then((_) => resourceCreated ? disposer(resource) : null);
       },
     );
 


Test patch:
diff --git a/test/streams/using_test.dart b/test/streams/using_test.dart
index 508e095d..a51e39f7 100644
--- a/test/streams/using_test.dart
+++ b/test/streams/using_test.dart
@@ -110,10 +110,10 @@ void main() async {
 
         test('$groupPrefix.done', () async {
           final stream = Rx.using<int, MockResource>(
-            resourceFactory,
-            (resource) => Stream.value(resource)
+            resourceFactory: resourceFactory,
+            streamFactory: (resource) => Stream.value(resource)
                 .flatMap((_) => Stream.fromIterable([1, 2, 3])),
-            disposer,
+            disposer: disposer,
           );
 
           await expectLater(
@@ -135,12 +135,12 @@ void main() async {
           var callDisposer = false;
 
           final stream = Rx.using<int, MockResource>(
-            resourceFactoryThrows,
-            (resource) {
+            resourceFactory: resourceFactoryThrows,
+            streamFactory: (resource) {
               calledStreamFactory = true;
               return Rx.range(0, 3);
             },
-            (resource) {
+            disposer: (resource) {
               callDisposer = true;
               return disposer(resource);
             },
@@ -158,9 +158,9 @@ void main() async {
 
         test('$groupPrefix.disposer.throws', () async {
           final subscription = Rx.using<int, MockResource>(
-            resourceFactory,
-            (resource) => Rx.timer(0, resourceDuration),
-            disposerThrows,
+            resourceFactory: resourceFactory,
+            streamFactory: (resource) => Rx.timer(0, resourceDuration),
+            disposer: disposerThrows,
           ).listen(null);
 
           if (create == Create.async) {
@@ -175,9 +175,9 @@ void main() async {
 
         test('$groupPrefix.streamFactory.throws', () async {
           final stream = Rx.using<int, MockResource>(
-            resourceFactory,
-            (resource) => throw Exception(),
-            disposer,
+            resourceFactory: resourceFactory,
+            streamFactory: (resource) => throw Exception(),
+            disposer: disposer,
           );
 
           await expectLater(
@@ -191,9 +191,9 @@ void main() async {
 
         test('$groupPrefix.streamFactory.errors', () async {
           final stream = Rx.using<int, MockResource>(
-            resourceFactory,
-            (resource) => Stream.error(Exception()),
-            disposer,
+            resourceFactory: resourceFactory,
+            streamFactory: (resource) => Stream.error(Exception()),
+            disposer: disposer,
           );
 
           await expectLater(
@@ -209,12 +209,12 @@ void main() async {
           const duration = Duration(milliseconds: 200);
 
           final subscription = Rx.using<int, MockResource>(
-            resourceFactory,
-            (resource) => Rx.concat([
+            resourceFactory: resourceFactory,
+            streamFactory: (resource) => Rx.concat([
               Rx.timer(0, duration),
               Stream.error(Exception()),
             ]),
-            disposer,
+            disposer: disposer,
           ).listen(
             null,
             cancelOnError: false,
@@ -231,12 +231,12 @@ void main() async {
 
         test('$groupPrefix.cancel.immediately', () async {
           final subscription = Rx.using<int, MockResource>(
-            resourceFactory,
-            (resource) => Rx.concat([
+            resourceFactory: resourceFactory,
+            streamFactory: (resource) => Rx.concat([
               Rx.timer(0, const Duration(milliseconds: 10)),
               Stream.error(Exception()),
             ]),
-            disposer,
+            disposer: disposer,
           ).listen(
             expectAsync1((v) => expect(true, false), count: 0),
             onError: expectAsync2(
@@ -255,12 +255,12 @@ void main() async {
 
         test('$groupPrefix.errors.continueOnError', () async {
           Rx.using<int, MockResource>(
-            resourceFactory,
-            (resource) => Rx.concat([
+            resourceFactory: resourceFactory,
+            streamFactory: (resource) => Rx.concat([
               Rx.timer(0, resourceDuration * 2),
               Stream<int>.error(Exception())
             ]),
-            disposer,
+            disposer: disposer,
           ).listen(
             null,
             onError: (Object e, StackTrace s) {},
@@ -274,9 +274,9 @@ void main() async {
 
         test('$groupPrefix.errors.cancelOnError', () async {
           Rx.using<int, MockResource>(
-            resourceFactory,
-            (resource) => Stream.error(Exception()),
-            disposer,
+            resourceFactory: resourceFactory,
+            streamFactory: (resource) => Stream.error(Exception()),
+            disposer: disposer,
           ).listen(
             null,
             onError: (Object e, StackTrace s) {},
@@ -290,9 +290,9 @@ void main() async {
 
         test('$groupPrefix.single.subscription', () async {
           final stream = Rx.using<int, MockResource>(
-            resourceFactory,
-            (resource) => Rx.range(0, 3),
-            disposer,
+            resourceFactory: resourceFactory,
+            streamFactory: (resource) => Rx.range(0, 3),
+            disposer: disposer,
           );
           stream.listen(null);
           expect(() => stream.listen(null), throwsStateError);
@@ -300,12 +300,12 @@ void main() async {
 
         test('$groupPrefix.asBroadcastStream', () async {
           final stream = Rx.using<int, MockResource>(
-            resourceFactory,
-            (resource) => Stream.periodic(
+            resourceFactory: resourceFactory,
+            streamFactory: (resource) => Stream.periodic(
               const Duration(milliseconds: 50),
               (i) => i,
             ),
-            disposer,
+            disposer: disposer,
           ).asBroadcastStream(onCancel: (s) => s.cancel());
 
           final s1 = stream.listen(null);
@@ -324,12 +324,12 @@ void main() async {
           late StreamSubscription<int> subscription;
 
           subscription = Rx.using<int, MockResource>(
-            resourceFactory,
-            (resource) => Stream.periodic(
+            resourceFactory: resourceFactory,
+            streamFactory: (resource) => Stream.periodic(
               const Duration(milliseconds: 20),
               (i) => i,
             ),
-            disposer,
+            disposer: disposer,
           ).listen(
             expectAsync1(
               (value) {
@@ -343,6 +343,35 @@ void main() async {
           subscription
               .pause(Future<void>.delayed(const Duration(milliseconds: 50)));
         });
+
+        test('$groupPrefix.disposer.order', () async {
+          final stream = Rx.using<int, MockResource>(
+            resourceFactory: resourceFactory,
+            streamFactory: (resource) {
+              final controller = StreamController<int>();
+
+              controller.onListen = () {
+                controller.add(1);
+                controller.add(2);
+                controller.close();
+              };
+
+              controller.onCancel = () async {
+                expect(resource.isClosed, false);
+                await Future<void>.delayed(resourceDuration * 10);
+                expect(resource.isClosed, false);
+              };
+
+              return controller.stream;
+            },
+            disposer: disposer,
+          ).take(1);
+
+          await expectLater(
+            stream,
+            emitsInOrder(<Object>[1, emitsDone]),
+          );
+        });
       });
     }
   }