Cancel stream onData

Just declare the variable beforehand, then you can access it from within the callback:

StreamSubscription subscription;
subscription = _controller.stream.listen((Map<PlaceParam, dynamic> params) {
  if(params.containsKey(param)) {
    executeOnce();
    subscription.cancel(); 
  }
});

The issue is that I can't access the variable subscription in the body of my callback, since it is still not created at the time

That's correct - you cannot access is the subscription variable, even if you know that the subscription itself would exist. Dart doesn't allow variables declarations to refer to themselves. That's occasionally annoying when the variable it's only referenced inside a closure that won't be executed yet.

The solution is to either pre-declare the variable or to update the onData-listener after doing the listening:

    // Pre-declare variable (can't be final, and with null safety
    // it has to be late).
    late StreamSubscription<Map<PlaceParam, dynamic>> subscription;
    subscription = stream.listen((event) {
      .... subscription.cancel();
    });

or

    final subscription = stream.listen(null);
    subscription.onData((event) {  // Update onData after listening.
      .... subscription.cancel(); ....
    });

There can be cases where you can't access the subscription object yet, but that's only possible if the stream breaks the Stream contract and starts sending events immediately when it's listened to. Streams must not do that, they must wait until a later microtask before delivering the first event, so that the code that called listen has time to, say, receive the subscription and assign it to a variable. It's possible to violate the contract using a synchronous stream controller (which is one reason why synchronous stream controllers should be used judiciously).

(This answer provided was updated for null safety. Prior to Dart 2.12, the former proposal didn't need the late, and was generally the preferred approach. With Dart 2.12 and null safety, the latter approach will likely be better.)


Callbacks that are only called once are gross. I think it would be more Dart-y to return a Future:

Future<Null> consumeOnce(PlaceParam param) async {
  await _controller.stream.singleWhere((params) => params.containsKey(param));
}

Given that this is a one-liner I'm not sure it's even really necessary, unless it happens a lot. Clients of the EventBus could call getBus().singleWhere just as easily.

If you're really attached to the idea of using callbacks you can keep the executeOnce argument and invoke it after awaiting the call to singleWhere, but I can't really think of a situation where this would be better.

Tags:

Dart