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.
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
Hi @antonygunawan94 馃憢
Check out https://github.com/felangel/bloc/blob/926029cae2d7614d38b5a9a8952e36bb59054b02/examples/github_search/common_github_search/lib/src/github_search_bloc/github_search_bloc.dart#L15 for an example.
@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 https://github.com/felangel/bloc/issues/524#issuecomment-573978217
@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));
}
Most helpful comment
Appreciate!