flutterstreamblocflutter-widgetprogress-indicator

How to show progress indicator when fetching data from API?


The problem is very simple but I am unable to find a proper solution. I have a search screen and here is what I want to do.

How can I achieve this behavior using Stream? Following is my code so far:

Widgets

StreamBuilder<List<Model>>(
  stream: bloc.searchStream,
  builder: (context, snapshot) {

    if (snapshot.hasError) {
      return Center(
        child: Text(
          'Error occurred'
        ),
      );
    }

    if (!snapshot.hasData) {
      return Center(
        child: Text(
          'Your results will appear here'
        ),
      );
    }

    if (snapshot.hasData && snapshot.data.length < 1) {
      return Center(
        child: Text(
          'Found nothing'
        ),
      );
    }

    // Where should I put my CircularProgressIndicator?

    return ListView.separated(
      itemCount: snapshot.data.length,
      separatorBuilder: (context, index) => Divider(height: 1),
      itemBuilder: (context, index) {
        final item = snapshot.data[index];
        return ItemWidget(item);
      },
    );
  },
)

BLoC

final _searchStreamController = StreamController<List<Model>>();
Stream<List<Model>> get searchStream => _searchStreamController.stream;

void search(String searchTerm) async {

  if (searchTerm.isEmpty) {
    _searchStreamController.add(null);
    return;
  }

  final client = HttpClient();
  client.method = HttpMethod.POST;
  client.endPoint = 'search';
  client.addData('query', searchTerm);

  try {
    final responseStr = await client.execute();
    final response = SearchResponse.fromJson(responseStr);
    _searchStreamController.add(response.data);
  } catch (e) {
    _searchStreamController.addError(e);
  }
}

I want to display CircularProgressIndicator every time search(String searchedTerm) function is called.

Thank you.


Solution

  • Here is what hack I had to do to achieve my requirements. If there is a better way to achieve this, please post an answer and I will mark it as correct.

    Hack

    class JugarModel<T> {
      bool isProcessing;
      T data;
    
      JugarModel({this.isProcessing, this.data});
    }
    

    And then use this isProcessing property to check whether to show CircularProgressIndicator or ListView. The rest of the code became:

    Widget

    StreamBuilder<JugarModel>(
      stream: bloc.searchStream,
      builder: (context, snapshot) {
    
        if (snapshot.hasError) {
          return Center(
            child: Text(
              'Error occurred'
            ),
          );
        }
    
        if (!snapshot.hasData) {
          return Center(
            child: Text(
              'Your results will appear here'
            ),
          );
        }
    
        if (snapshot.hasData && snapshot.data.data.isEmpty) {
    
          if (snapshot.data.isProcessing) {
            return Center(
              child: CircularProgressIndicator(),
            );
          } else {
            return Center(
              child: Text(
                'Found nothing'
              ),
            );
          }
        }
    
        return ListView.separated(
          itemCount: snapshot.data.data.length,
          separatorBuilder: (context, index) => Divider(height: 1),
          itemBuilder: (context, index) {
            final item = snapshot.data.data[index];
            return ItemWidget(item);
          },
        );
      },
    )
    

    BLoC

    final _searchStreamController = StreamController<JugarModel<List<Data>>>();
    Stream<JugarModel<List<Data>>> get searchStream => _searchStreamController.stream;
    
    void search(String searchTerm) async {
    
      if (searchTerm.isEmpty) {
        _searchStreamController.add(null);
        return;
      }
    
      final client = HttpClient();
      client.method = HttpMethod.POST;
      client.endPoint = 'search';
      client.addData('query', searchTerm);
    
      // This MAGIC line will call StreamBuilder callback with isProcessing set to true.
      _searchStreamController.add(JugarModel<List<Data>>(isProcessing: true, data: List()));
      try {
        final responseStr = await client.execute();
        final response = SearchResponse.fromJson(responseStr);
    
        // And after we've received response from API and parsed it, we're calling StreamBuilder
        // callback again with isProcessing set to false.
        _searchStreamController.add(JugarModel<List<Data>>(isProcessing: false, data: response.data));
      } catch (e) {
        _searchStreamController.addError(e);
      }
    }