2

I am trying to create a timer in Flutter Bloc to fetch data every 30 seconds after DataFetchRequested event is called.


class DataBloc extends Bloc<BlocEvent, BlocState> {
  final Api _api;
  Timer _timer;

  DataBloc(this._api);

  @override
  BlocState get initialState => StateInitial();

  @override
  Stream<BlocState> mapEventToState(BlocEvent event) async* {
    if (event is DataFetchRequested) {
      _resetTimer((timer) => _fetchData());
      yield* _fetchData();
    }
  }

  Stream<BlocState> _fetchData() async* {
    yield StateLoading();

    try {
      final result = await _api.fetch();
      yield StateSuccess(result);
    } catch (e) {
      _timer.cancel();
      yield StateFailure(e.toString());
    }
  }

  _resetTimer(void Function(Timer) onCallback) {
    _timer?.cancel();
    _timer = Timer.periodic(Duration(seconds: 1), onCallback);
  }

  @override
  Future<void> close() {
    _timer?.cancel();
    return super.close();
  }
}


However, timer does not do anything when called in this way.

Tried changing

_resetTimer((timer) => fetchFn);

to

yield* _resetTimer((timer) async* {
  yield* fetchFn;
});

which led to error:

Tried calling: listen(Closure: (Object) => void from Function '_add@4048458':., cancelOnError: false, onDone: Closure: () => void from Function '_close@4048458':., onError: Closure: (Object, StackTrace) => void from Function '_addError@4048458':.) occurred in bloc Instance of 'DataBloc'.
#0      Object.noSuchMethod  (dart:core-patch/object_patch.dart:53:5)
#1      new _AddStreamState  (dart:async/stream_controller.dart:900:34)
#2      new _StreamControllerAddStreamState  (dart:async/stream_controller.dart:952:9)
#3      _StreamController.addStream  (dart:async/stream_controller.dart:571:13)
#4      _AsyncStarStreamController.addStream  (dart:async-patch/async_patch.dart:206:37)
#5      DataBloc.mapEventToState 
package:app/…/data/DataBloc.dart:33
<asynchronous suspension>
#6      Bloc._bindEventsToStates.<anonymous closure> <…>

Is it possible to use Timer in Bloc to fetch data every 30 seconds?

Tried using Stream.periodic, but failed with it as well.

I was able to achieve what I wanted by using timer in presentation widget and calling the event every 30 seconds, but I would like to move this logic from presentation widget into DataBloc - is this possible?

zilijonas
  • 3,285
  • 3
  • 26
  • 49
  • Check my answer to a similar question here: https://stackoverflow.com/questions/56446763/how-to-get-data-from-api-every-x-seconds-in-flutter/56446982#56446982 – Er1 May 19 '20 at 12:29
  • @Er1 Sadly, this answer does not help, because you are calling a method that has a return type of `Future`. I need to call a method that has a return type of `Stream`. When calling a method with `Stream` return type - Timer callback fails. – zilijonas May 19 '20 at 12:32

1 Answers1

8

I think this should work;

class DataBloc extends Bloc<BlocEvent, BlocState> {
  final Api _api;
  StreamSubscription _periodicSubscription;

  DataBloc(this._api);

  @override
  BlocState get initialState => StateInitial();

  @override
  Stream<BlocState> mapEventToState(BlocEvent event) async* {
    if (event is DataFetchRequested) {
      yield StateLoading();
      if(_periodicSubscription == null) {
        _periodicSubscription ??=
            Stream.periodic(const Duration(seconds: 30), (x) => x).listen(
                    (_) => add(const FetchEvent()),
                onError: (error) =>
                    print("Do something with $error")
            );
      } else {
        _periodicSubscription.resume();
      }
    }
    if(event is FetchEvent){
      yield StateLoading();

      try {
        final result = await _api.fetch();
        yield StateSuccess(result);
      } catch (e) {
        _periodicSubscription.pause();
        yield StateFailure(e.toString());
      }
    }
  }

  @override
  Future<void> close() async {
    await _periodicSubscription?.cancel();
    _periodicSubscription = null;
    return super.close();
  }
}

FetchEvent is a new event class you have to create.

Er1
  • 2,559
  • 1
  • 12
  • 24