Refactor tracking of multiple stream subscriptions (dart-lang/stream_transform#92)

Instead of using a variable with a bigger scope than the `onListen`
handler, and assigning to null in between calls to the callback, scope
the variable down. Remove the subscriptions when they are no longer
needed and use the size of the list as the subscription count rather
than tracking in a separate int variable.

Refactor a `..addAll` and a `.map` to use a list literal with a for-loop
element.

Make more local variables final since the details of which are changed
is very relevant to behavior in this code.
diff --git a/pkgs/stream_transform/lib/src/combine_latest.dart b/pkgs/stream_transform/lib/src/combine_latest.dart
index bacc7d6..7fee05d 100644
--- a/pkgs/stream_transform/lib/src/combine_latest.dart
+++ b/pkgs/stream_transform/lib/src/combine_latest.dart
@@ -192,22 +192,21 @@
   _CombineLatestAll(this._others);
 
   @override
-  Stream<List<T>> bind(Stream<T> source) {
-    final controller = source.isBroadcast
+  Stream<List<T>> bind(Stream<T> first) {
+    final controller = first.isBroadcast
         ? StreamController<List<T>>.broadcast(sync: true)
         : StreamController<List<T>>(sync: true);
 
-    var allStreams = [source]..addAll(_others);
-    if (source.isBroadcast) {
-      allStreams = allStreams
-          .map((s) => s.isBroadcast ? s : s.asBroadcastStream())
-          .toList();
-    }
-
-    List<StreamSubscription<T>> subscriptions;
+    final allStreams = [
+      first,
+      for (final other in _others)
+        !first.isBroadcast || other.isBroadcast
+            ? other
+            : other.asBroadcastStream(),
+    ];
 
     controller.onListen = () {
-      assert(subscriptions == null);
+      final subscriptions = <StreamSubscription<T>>[];
 
       final latestData = List<T>(allStreams.length);
       final hasEmitted = <int>{};
@@ -219,35 +218,39 @@
         }
       }
 
-      var activeStreamCount = 0;
-      subscriptions = allStreams.map((stream) {
-        final index = activeStreamCount;
-        activeStreamCount++;
-        return stream.listen((data) => handleData(index, data),
-            onError: controller.addError, onDone: () {
-          if (--activeStreamCount <= 0 || !hasEmitted.contains(index)) {
+      var streamId = 0;
+      for (final stream in allStreams) {
+        final index = streamId;
+
+        final subscription = stream.listen((data) => handleData(index, data),
+            onError: controller.addError);
+        subscription.onDone(() {
+          assert(subscriptions.contains(subscription));
+          subscriptions.remove(subscription);
+          if (subscriptions.isEmpty || !hasEmitted.contains(index)) {
             controller.close();
           }
         });
-      }).toList();
-      if (!source.isBroadcast) {
+        subscriptions.add(subscription);
+
+        streamId++;
+      }
+      if (!first.isBroadcast) {
         controller
           ..onPause = () {
-            for (var subscription in subscriptions) {
+            for (final subscription in subscriptions) {
               subscription.pause();
             }
           }
           ..onResume = () {
-            for (var subscription in subscriptions) {
+            for (final subscription in subscriptions) {
               subscription.resume();
             }
           };
       }
       controller.onCancel = () {
-        final toCancel = subscriptions;
-        subscriptions = null;
-        if (activeStreamCount <= 0) return null;
-        return Future.wait(toCancel.map((s) => s.cancel()));
+        if (subscriptions.isEmpty) return null;
+        return Future.wait(subscriptions.map((s) => s.cancel()));
       };
     };
     return controller.stream;
diff --git a/pkgs/stream_transform/lib/src/merge.dart b/pkgs/stream_transform/lib/src/merge.dart
index 5d2c864..99d19c9 100644
--- a/pkgs/stream_transform/lib/src/merge.dart
+++ b/pkgs/stream_transform/lib/src/merge.dart
@@ -87,47 +87,45 @@
 
   @override
   Stream<T> bind(Stream<T> first) {
-    var controller = first.isBroadcast
+    final controller = first.isBroadcast
         ? StreamController<T>.broadcast(sync: true)
         : StreamController<T>(sync: true);
 
-    var allStreams = [first]..addAll(_others);
-    if (first.isBroadcast) {
-      allStreams = allStreams
-          .map((s) => s.isBroadcast ? s : s.asBroadcastStream())
-          .toList();
-    }
-
-    List<StreamSubscription<T>> subscriptions;
+    final allStreams = [
+      first,
+      for (final other in _others)
+        !first.isBroadcast || other.isBroadcast
+            ? other
+            : other.asBroadcastStream(),
+    ];
 
     controller.onListen = () {
-      assert(subscriptions == null);
-      var activeStreamCount = 0;
-      subscriptions = allStreams.map((stream) {
-        activeStreamCount++;
-        return stream.listen(controller.add, onError: controller.addError,
-            onDone: () {
-          if (--activeStreamCount <= 0) controller.close();
+      final subscriptions = <StreamSubscription<T>>[];
+      for (final stream in allStreams) {
+        final subscription =
+            stream.listen(controller.add, onError: controller.addError);
+        subscription.onDone(() {
+          subscriptions.remove(subscription);
+          if (subscriptions.isEmpty) controller.close();
         });
-      }).toList();
+        subscriptions.add(subscription);
+      }
       if (!first.isBroadcast) {
         controller
           ..onPause = () {
-            for (var subscription in subscriptions) {
+            for (final subscription in subscriptions) {
               subscription.pause();
             }
           }
           ..onResume = () {
-            for (var subscription in subscriptions) {
+            for (final subscription in subscriptions) {
               subscription.resume();
             }
           };
       }
       controller.onCancel = () {
-        var toCancel = subscriptions;
-        subscriptions = null;
-        if (activeStreamCount <= 0) return null;
-        return Future.wait(toCancel.map((s) => s.cancel()));
+        if (subscriptions.isEmpty) return null;
+        return Future.wait(subscriptions.map((s) => s.cancel()));
       };
     };
     return controller.stream;
@@ -150,14 +148,12 @@
         final subscription =
             inner.listen(controller.add, onError: controller.addError);
         subscription.onDone(() {
-          assert(subscriptions.contains(subscription));
           subscriptions.remove(subscription);
           if (subscriptions.isEmpty) controller.close();
         });
         subscriptions.add(subscription);
       }, onError: controller.addError);
       outerSubscription.onDone(() {
-        assert(subscriptions.contains(outerSubscription));
         subscriptions.remove(outerSubscription);
         if (subscriptions.isEmpty) controller.close();
       });