Issue
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?
Solution
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.
Answered By – Er1
Answer Checked By – Marie Seifert (FlutterFixes Admin)