I have a search bar, when characters are entered it sends a request to a BLoC, which then requests data from a Future.
Here is the BLoC
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:stockrails/models/semantics/error-type-model.dart';
import 'package:stockrails/models/user-interface/search-query-model.dart';
import 'package:stockrails/services/apis/stock-data.dart';
import 'package:stockrails/shared/constants/messages.dart';
part 'search_event.dart';
part 'search_state.dart';
class SearchBloc extends Bloc<SearchEvent, SearchState> {
SearchBloc() : super(SearchInitial());
// Connection to api service
StockData _data = StockData();
@override
Stream<SearchState> mapEventToState(
SearchEvent event,
) async* {
// : Initial search state - no text has been entered, no loading
if (event is SearchIntializeEvent) {
yield SearchInitial();
}
// : Querying search state - text is being entered, loading next
if (event is SearchQueryingEvent) {
// Yields call
yield* _mapAPICallToEvent(event.query);
}
}
// + ----------- Functions -------------
Stream<SearchState> _mapAPICallToEvent(String query) async* {
// : Yield loading screen based on prior state
if (state is SearchErrorState)
yield SearchErrorLoadingState();
else
yield SearchLoadingState(query: query);
// : Attempts to call method
List<SearchQueryModel> _result;
try {
_result = await _data.searchQuery(query);
} on TimeoutException {
_result = null;
} catch (err) {
_result = [];
}
// : If it can't then it defaults to using the unknownErrorMessage
if (_result == null)
yield SearchErrorState(errorMessage: Messages.unknownErrorMessage, errorType: ErrorType.errorTypes[0]);
// : Else if there's no content in the response
else if (_result.length == 0)
yield SearchErrorState(errorMessage: query, errorType: ErrorType.errorTypes[1]);
// : If there's no errors return loaded!
else {
yield SearchLoadedState(searchQueryModelList: _result, query: query);
}
}
}
And here is the Future that grabs data
// Grabs data from endpoint and returns or errors
// If it times out it will throw a TimeoutException
Future _dataFetch(String url, String query, String token) async {
try {
// Build out auth header
final authHeader = 'Bearer $token';
final response = await http
.get(url, headers: <String, String>{
'query': query,
'authorization': authHeader,
})
.timeout(Duration(seconds: 10))
.catchError((error) {
throw error;
});
// Makes sure response is okay
if (response.statusCode == 200) {
return json.decode(response.body);
}
// If status code is unknown, prints an error
else {
throw Exception(Messages.unknownErrorMessage);
}
} on TimeoutException catch (error) {
throw error;
} on SocketException catch (error) {
throw error;
} catch (error) {
throw error;
}
}
I've been banging my head against a wall trying to figure out a solution to make the BLoC await
for the Future but also to allow SearchInitializeEvent
to cancel any requests being made.
Because what's happening is on a bad network, the request is trying to go through no matter what and won't stop until it gets a response.
I know you can't cancel Futures, I tried both CancelableOperation
and 'CancelableCompletion` or whatever it's called.
I'm guessing I have to setup my Future as a Stream instead so I can close it. I'm open to any suggestions. Thank you in advance!
There's a limitation in dart for breaking Streams. It's something that can't be done outside the "loop". This is issue was discussed in detail here:
https://github.com/dart-lang/sdk/issues/42717
https://github.com/felangel/bloc/issues/1472
You may want to look into this from a different perspective, like overriding the changes made by the recent request. Or if the request results are something that's displayed on screen, then consider not displaying the results instead.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With