Rxdart: switchMap doesn't cancel previous http request

Created on 6 Aug 2021  路  3Comments  路  Source: ReactiveX/rxdart

switchMap doesn't cancel previous http request. And when the streamcontroller close is called, the internal stream does not cancel. help me, thanks

class AssetManagementViewModel implements ViewModel {
  // Inputs
  void refresh() => _refresh.add(null);

  // Outputs
  late final Stream<List<Asset>> assets;

  final _refresh = StreamController<void>.broadcast();

  AssetManagementViewModel({required this.api}) {
    _getAssets() => api
        .getAssets(null)
        .map((event) => event.map((json) => Asset.fromJson(json)).toList());

    assets = _getAssets().asBroadcastStream().concatWith([
      _refresh.stream.switchMap((value) {
        return _getAssets(); // 2.don鈥榯 cancel
      }).shareReplay(maxSize: 1)
    ]);
  }

  final AssetManagementAPI api;

  @override
  void dispose() {
    _refresh.close(); // 1.when call
  }
}

Here is the API stream generator锛孖 can't find a better way

  Stream<List> getAssets(params) {
    print("will send getAssets request");
    final controller = StreamController<List>();
    rootBundle.loadString("lib/resource/assetData.json").then((value) {
      final list = jsonDecode(value);
      Future.delayed(Duration(seconds: 3)).then((value) {
        print("response getAssets request");
        controller.add(list);
        controller.close();
      });
    });
    controller.onCancel = () {
      print("cancel getAssets request");
      // dioToken.cancel ...
    };

    return controller.stream;
  }
question waiting for response

Most helpful comment

Stream<List> getAssets(params) {
  return Rx.fromCallable(() => rootBundle.loadString("lib/resource/assetData.json"))
    .map((value) => jsonDecode(value) as List)
    .delay(const Duration(seconds: 3))
    .doOnListen(() => print('Listen'))
    .doOnCancel(() => print('Cancel'))
    .doOnData((d) => print('Data $d'));
}

and

class AssetManagementViewModel {
  ....

  final bag = CompositeSubscription():

  AssetManagementViewModel({required this.api}) {
    Stream<List<Asset>> _getAssets() => api
        .getAssets(null)
        .map((event) => event.map((json) => Asset.fromJson(json)).toList());

    assets = _refresh.stream
      .startWith(null)
      .switchMap((_) => _getAssets())
      .shareReplay(maxSize: 1)..listen(null).addTo(bag);
  }

  void dispose() {
    bag.dispose();
    ...
  }
}

Uses listen(null).addTo(bag) to keep the connection until ViewModel is disposed

All 3 comments

Stream<List> getAssets(params) {
  return Rx.fromCallable(() => rootBundle.loadString("lib/resource/assetData.json"))
    .map((value) => jsonDecode(value) as List)
    .delay(const Duration(seconds: 3))
    .doOnListen(() => print('Listen'))
    .doOnCancel(() => print('Cancel'))
    .doOnData((d) => print('Data $d'));
}

and

class AssetManagementViewModel {
  ....

  final bag = CompositeSubscription():

  AssetManagementViewModel({required this.api}) {
    Stream<List<Asset>> _getAssets() => api
        .getAssets(null)
        .map((event) => event.map((json) => Asset.fromJson(json)).toList());

    assets = _refresh.stream
      .startWith(null)
      .switchMap((_) => _getAssets())
      .shareReplay(maxSize: 1)..listen(null).addTo(bag);
  }

  void dispose() {
    bag.dispose();
    ...
  }
}

Uses listen(null).addTo(bag) to keep the connection until ViewModel is disposed

And remember, Future cannot be cancelled -> When cancelling a Stream created from Future, we only mark the Stream as cancelled, and listeners will be removed from Stream, if source Future has been already executed, we cannot cancel that

Stream<List> getAssets(params) {
  return Rx.fromCallable(() => rootBundle.loadString("lib/resource/assetData.json"))
    .map((value) => jsonDecode(value) as List)
    .delay(const Duration(seconds: 3))
    .doOnListen(() => print('Listen'))
    .doOnCancel(() => print('Cancel'))
    .doOnData((d) => print('Data $d'));
}

and

class AssetManagementViewModel {
  ....

  final bag = CompositeSubscription():

  AssetManagementViewModel({required this.api}) {
    Stream<List<Asset>> _getAssets() => api
        .getAssets(null)
        .map((event) => event.map((json) => Asset.fromJson(json)).toList());

    assets = _refresh.stream
      .startWith(null)
      .switchMap((_) => _getAssets())
      .shareReplay(maxSize: 1)..listen(null).addTo(bag);
  }

  void dispose() {
    bag.dispose();
    ...
  }
}

Uses listen(null).addTo(bag) to keep the connection until ViewModel is disposed

Thank you very much, it solved my headache. According to your suggestion, it works well now. Thanks again.

Was this page helpful?
0 / 5 - 0 ratings