从 API 获取数据时如何显示进度指示器?

Posted

技术标签:

【中文标题】从 API 获取数据时如何显示进度指示器?【英文标题】:How to show progress indicator when fetching data from API? 【发布时间】:2020-12-13 02:08:14 【问题描述】:

问题很简单,但我找不到合适的解决方案。我有一个搜索屏幕,这就是我想要做的。

第一次启动屏幕时,我想在Text 小部件中显示一条消息“您的结果将出现在此处”。 如果用户在搜索框中输入内容并按搜索,我想显示CircularProgressIndicator。 然后,当收到 API 响应时,我想显示 ListViewText 说“什么也没找到”。 如果用户再次搜索,我想再次显示CircularProgressIndicator

如何使用Stream 实现此行为?以下是我目前的代码:

小部件

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);
  

我想在每次调用search(String searchedTerm) 函数时显示CircularProgressIndicator

谢谢。

【问题讨论】:

【参考方案1】:

你可以试试:

StreamBuilder<List<Model>>(
          builder: (context, snapshot) 
            switch (snapshot.connectionState) 
              case ConnectionState.none:
                return Center(child: Text('Your results will appear here'));

              case ConnectionState.active:
              case ConnectionState.waiting:
                return Center(child: CircularProgressIndicator());

              case ConnectionState.done:
                if (snapshot.hasError)
                  return Center(child: Text('Error occurred'));

                if (snapshot.data.isEmpty) 
                  return Container(child: Center(child: Text('Found nothing')));
                 else 
                  return ListView.separated(
                    itemCount: snapshot.data.length,
                    separatorBuilder: (context, index) => Divider(height: 1),
                    itemBuilder: (context, index) 
                      final item = snapshot.data[index];
                      return ItemWidget(item);
                    ,
                  );
                
            
            return Center(child: Text('Your results will appear here'));
          ,
        )

【讨论】:

感谢您的回答,但它并没有解决我的问题。从未收到ConnectionState.none。此外,这种方法不会在下次搜索时显示进度条。 谢谢,但它不起作用。 snapshot.connectionState 总是有一个值,它从它的 case 返回。【参考方案2】:
    StreamBuilder<List<Model>>(
      stream: bloc.searchStream,
      builder: (context, snapshot) 
 
        if (snapshot.hasData) 
         if (snapshot.data.length ==0) 
              return  Center(
                 child: Text(
                      'Found nothing'
                    ),
              );
    
             
        return ListView.separated(
          itemCount: snapshot.data.length,
          separatorBuilder: (context, index) => Divider(height: 1),
          itemBuilder: (context, index) 
            final item = snapshot.data[index];
            return ItemWidget(item);
          ,
        );
        else if (snapshot.hasError) 
          return Center(
            child: Text(
              'Error occurred'
            ),
          );
      else 
         return Center(
            child:CircularProgressIndicator()
          );
       
      ,
    )

给一个初始的 Text() 小部件,其值为“结果将出现在此处”, 然后 onClick 按钮,将初始 Text() 小部件替换为 StreamBuilder

【讨论】:

这不起作用。因为它开始在开始时显示CircularProgressIndicator。我想在开始时显示Text 小部件,说“您的结果将出现在此处”。我只想在用户开始搜索时显示CircularProgressIndicator【参考方案3】:

这是我为了满足我的要求而必须做的事情。如果有更好的方法来实现这一点,请发布答案,我会将其标记为正确。

破解

class JugarModel<T> 
  bool isProcessing;
  T data;

  JugarModel(this.isProcessing, this.data);

然后使用这个isProcessing 属性来检查是显示CircularProgressIndicator 还是ListView。剩下的代码变成了:

小部件

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);
  

【讨论】:

很多逻辑不在 UI 中,但理想情况下应该在业务逻辑中。看看flutter_bloc 包,它有一个非常好的标准处理方式。 我已经检查过了。在这种情况下,它似乎对我没有帮助。如果你能想出一个代码示例,你可以在这里发布,我会标记为正确的。

以上是关于从 API 获取数据时如何显示进度指示器?的主要内容,如果未能解决你的问题,请参考以下文章

如果 API 无法获取文件,则显示不同的小部件

获取数据时如何在 React Redux 应用程序中显示加载指示器? [关闭]

从服务器获取数据时如何显示活动指示器

进度指示器的状态没有变化[重复]

从firebase数据库获取数据到android应用程序时使用进度条

如何检测 ListView 或任何控件何时完成渲染?