Bloc: Delay when dispatching event

Created on 28 Jan 2019  路  15Comments  路  Source: felangel/bloc

i wrote an example of github search api (similar to your example provided) just to get familiar with the idea of bloc and i found that when events are sent in delays which leads to inconstant behaviour.
here is my implementation of the bloc class


class SearchEvent {}

class SearchState {}

class SubmitSearchEvent implements SearchEvent {
  String query;
  SubmitSearchEvent({this.query});
}

class SearchEmptyState implements SearchState {}

class SearchLoadingState implements SearchState {}

class SearchSuccessState implements SearchState {
  final SearchResult result;
  SearchSuccessState(this.result);
}

class SearchErrorState implements SearchState {
  final Error error;

  SearchErrorState(this.error);
}

class SearchBloc extends Bloc<SearchEvent, SearchState> {
  final SearchApi _api;
  SearchBloc(this._api);
  @override
  SearchState get initialState => SearchEmptyState();
  @override
  void onTransition(Transition<SearchEvent, SearchState> transition) {
    super.onTransition(transition);
    print("${DateTime.now()} ");
    print("event: ${transition.event}");
    print("current state: ${transition.currentState}");
    print("next state: ${transition.nextState}");
  }

  @override
  Stream<SearchState> mapEventToState(
      SearchState currentState, SearchEvent event) async* {
    if (event is SubmitSearchEvent) {
      var query = event.query;
      if (query.isEmpty)
        yield SearchEmptyState();
      else {
        yield SearchLoadingState();
        try {
          var response = await _api.queryRepositores(query);
          yield SearchSuccessState(response);
        } catch (error) {
          yield SearchErrorState(error);
        }
      }
    }
  }

  @override
  void dispose() {
    super.dispose();
  }
}

here the ui implementation

void main() => runApp(MaterialApp(
      home: Main(),
      theme: ThemeData(),
    ));

class Main extends StatefulWidget {
  @override
  _MainState createState() => _MainState();
}

class _MainState extends State<Main> {
  String value = "";

  SearchBloc _searchBloc;
  @override
  void initState() {
    _searchBloc = SearchBloc(SearchApi());
    super.initState();
  }

  @override
  void dispose() {
    _searchBloc.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('text'),
      ),
      body: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          Padding(
            padding: const EdgeInsets.all(16.0),
            child: Column(
              children: <Widget>[
                TextField(
                  onChanged: (newValue) {
                    _searchBloc.dispatch(SubmitSearchEvent(query: newValue));
                  },
                  decoration: InputDecoration(
                      border: UnderlineInputBorder(
                          borderRadius: BorderRadius.all(Radius.circular(10))),
                      prefixIcon: Icon(Icons.search),
                      filled: true,
                      hintText: 'Search for Repositories'),
                ),
              ],
            ),
          ),
          BlocBuilder(
            bloc: _searchBloc,
            builder: (context, event) {
              if (event is SearchLoadingState) {
                return Center(
                  child: CircularProgressIndicator(),
                );
              } else if (event is SearchEmptyState) {
                return Center(child: Text('Search for data'));
              } else if (event is SearchSuccessState) {
                return Center(
                  child: Text("Success ${event.result.totalCount}"),
                );
              } else if (event is SearchErrorState) {
                return Center(child: Text(event.error.toString()));
              } else {
                return Center(child: Text('Search for data'));
              }
            },
          )
        ],
      ),
    );
  }
}

and here is the log with timestamp between each event

I/flutter ( 1846): 2019-01-28 17:24:35.854326
I/flutter ( 1846): event: SubmitSearchEvent - current state: SearchEmptyState - next state: SearchLoadingState
I/flutter ( 1846): sending request to https://api.github.com/search/repositories?q=s
I/flutter ( 1846): 2019-01-28 17:24:38.570055
I/flutter ( 1846): event: SubmitSearchEvent - current state: SearchLoadingState - next state: SearchSuccessState
I/flutter ( 1846): 2019-01-28 17:24:38.570929
I/flutter ( 1846): event: SubmitSearchEvent - current state: SearchSuccessState - next state: SearchLoadingState
I/flutter ( 1846): sending request to https://api.github.com/search/repositories?q=se
I/flutter ( 1846): 2019-01-28 17:24:39.593691
I/flutter ( 1846): event: SubmitSearchEvent - current state: SearchLoadingState - next state: SearchSuccessState
I/flutter ( 1846): 2019-01-28 17:24:39.594166
I/flutter ( 1846): event: SubmitSearchEvent - current state: SearchSuccessState - next state: SearchLoadingState
I/flutter ( 1846): sending request to https://api.github.com/search/repositories?q=sea
I/flutter ( 1846): 2019-01-28 17:24:40.484570
I/flutter ( 1846): event: SubmitSearchEvent - current state: SearchLoadingState - next state: SearchSuccessState
I/flutter ( 1846): 2019-01-28 17:24:40.485276
I/flutter ( 1846): event: SubmitSearchEvent - current state: SearchSuccessState - next state: SearchLoadingState
I/flutter ( 1846): sending request to https://api.github.com/search/repositories?q=sear
I/flutter ( 1846): 2019-01-28 17:24:41.964403
I/flutter ( 1846): event: SubmitSearchEvent - current state: SearchLoadingState - next state: SearchSuccessState
I/flutter ( 1846): 2019-01-28 17:24:41.964978
I/flutter ( 1846): event: SubmitSearchEvent - current state: SearchSuccessState - next state: SearchLoadingState
I/flutter ( 1846): sending request to https://api.github.com/search/repositories?q=searc
I/flutter ( 1846): 2019-01-28 17:24:43.177989
I/flutter ( 1846): event: SubmitSearchEvent - current state: SearchLoadingState - next state: SearchSuccessState
I/flutter ( 1846): 2019-01-28 17:24:43.179184
I/flutter ( 1846): event: SubmitSearchEvent - current state: SearchSuccessState - next state: SearchLoadingState
I/flutter ( 1846): sending request to https://api.github.com/search/repositories?q=search
I/flutter ( 1846): 2019-01-28 17:24:44.129876
I/flutter ( 1846): event: SubmitSearchEvent - current state: SearchLoadingState - next state: SearchSuccessState

idk if im doing something wrong but everything seems to be ok.
thanks for the library and your time.

question

Most helpful comment

@danielfpedro check out #524 (comment)

Appreciate!

All 15 comments

Thanks for opening an issue. Can you please be a bit more specific about what you expected as opposed to what you observed?

Transitions occur after a state change but before the bloc鈥檚 state is updated. The reason there is a delay is because your code is dependent on a network request so the transition won鈥檛 occur until after the github api responds with data for the success state.

Does that help?

@felangel
sorry if i made myself unclear since english is not native tongue.
perhaps this video might explain what im getting at: link

@m7mdra thanks for the video! Have you tried debouncing because it seems like the problem is your app is spamming the api on every text change.

// Add this to top with imports
import 'package:rxdart/rxdart.dart';


// Add this to your Bloc
@override
Stream<SearchEvent> transform(Stream<SearchEvent> events) {
  return (events as Observable<SearchEvent>)
      .debounce(Duration(milliseconds: 500));
}

Let me know if that helps.

@felangel yes its working now. thanks for your time.

No problem :) Glad I could help!

hello,
can you provide updated example on how to debounce event?
because I can't seem to find this override function in the latest flutter bloc

@override Stream<SearchEvent> transform(Stream<SearchEvent> events) { return (events as Observable<SearchEvent>) .debounce(Duration(milliseconds: 500)); }

thank you

@m7mdra thanks for the video! Have you tried debouncing because it seems like the problem is your app is spamming the api on every text change.

// Add this to top with imports
import 'package:rxdart/rxdart.dart';


// Add this to your Bloc
@override
Stream<SearchEvent> transform(Stream<SearchEvent> events) {
  return (events as Observable<SearchEvent>)
      .debounce(Duration(milliseconds: 500));
}

Let me know if that helps.

What if I have several events on the bloc, all events will be debounced? How to debounce just the event that fetch data from server?

@danielfpedro check out #524 (comment)

Appreciate!

@m7mdra thanks for the video! Have you tried debouncing because it seems like the problem is your app is spamming the api on every text change.

// Add this to top with imports
import 'package:rxdart/rxdart.dart';


// Add this to your Bloc
@override
Stream<SearchEvent> transform(Stream<SearchEvent> events) {
  return (events as Observable<SearchEvent>)
      .debounce(Duration(milliseconds: 500));
}

Let me know if that helps.

this doesn't work anymore. Please how do I do it now with the latest library?

Hi @Greatcallie 馃憢

Have a look at an example here https://github.com/felangel/bloc/issues/1107#issuecomment-621176290

Hi @Greatcallie 馃憢

Have a look at an example here #1107 (comment)

Thanks. debounceTime wasn't recognised until i added rxdart

The Observable is replaced by Stream in the latest RXDart version.

@override
  Stream<GithubSearchState> transformEvents(
    Stream<GithubSearchEvent> events,
    Stream<GithubSearchState> Function(GithubSearchEvent event) next,
  ) {
    return (events as Observable<GithubSearchEvent>)
        .debounceTime(
          Duration(milliseconds: 300),
        )
        .switchMap(next);
  }

Doesnt work as it doesnt override transformEvents().

Any input about how it can be done now?

The Observable is replaced by Stream in the latest RXDart version.

@override Stream<GithubSearchState> transformEvents( Stream<GithubSearchEvent> events, Stream<GithubSearchState> Function(GithubSearchEvent event) next, ) { return (events as Observable<GithubSearchEvent>) .debounceTime( Duration(milliseconds: 300), ) .switchMap(next); }

Doesnt work as it doesnt override transformEvents().

Any input about how it can be done now?

Never mind, understood it now.

If any one comes looking for it, here's how its done now:

@override
  Stream<Transition< GithubSearchEvent, GithubSearchState >> transformEvents(
      Stream< GithubSearchEvent > events, transitionFn) {
    return events
        .debounceTime(const Duration(milliseconds: 500))
        .switchMap((transitionFn));
  }
Was this page helpful?
0 / 5 - 0 ratings

Related issues

zs-dima picture zs-dima  路  34Comments

felangel picture felangel  路  32Comments

felangel picture felangel  路  27Comments

anderscheow picture anderscheow  路  75Comments

sawankumarbundelkhandi picture sawankumarbundelkhandi  路  108Comments