8

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 set up my Future as a Stream instead so I can close it. I'm open to any suggestions. Thank you in advance!

tyirvine
  • 1,861
  • 1
  • 19
  • 29
  • 1
    You can cancel http requests with [dio](https://pub.dev/packages/dio) – Augustin R Jul 07 '20 at 14:21
  • I tried using Dio but I must've done it wrong. Whenever I'd cancel it the request seemed to keep trying to go through. As well, I found I had to re-define the `Dio()` object whenever I cancelled it because it sort of closed the object from making any further requests – tyirvine Jul 07 '20 at 16:27
  • I agree that there will be no trick to cancel a `Future`, but there is a way to cancel a StreamSubscription. [This SO post](https://stackoverflow.com/questions/17552757/is-there-any-way-to-cancel-a-dart-future) might help you with how it could be implemented. Have you checked it? – MαπμQμαπkγVπ.0 Jul 19 '21 at 20:07

1 Answers1

3

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.

Omatt
  • 8,564
  • 2
  • 42
  • 144