【问题标题】:Flutter - Page Does Not Appear to Push Until FutureBuilder is DoneFlutter - 在 FutureBuilder 完成之前,页面似乎不会推送
【发布时间】:2021-02-22 19:04:02
【问题描述】:

我有一个显示项目列表的页面。通过单击上一页的按钮可以到达此页面。检索完成后,我使用 FutureBuilder 填充项目列表。当 ConnectionState 处于“等待”状态时,我会显示一个圆形进度指示器。但是,在 FutureBuilder 完成之前,进度指示器永远不会出现,并且列表页面不会出现推送。如果我在“if (snapshot.connectionState == ConnectionState.waiting)”块中放置一个断点,它会命中该断点,但屏幕在返回时不会改变。屏幕确实按预期显示,但随着项目列表的增加,屏幕出现的时间越来越长。我在这里错过了什么吗:

class MyListScreen extends StatelessWidget {
   RelevantObject relevantObject;

   MyListScreen(this.relevantObject, {Key key}) : super(key: key);

   @override
   Widget build(BuildContext context) {
   final bloc = MyListBloc(relevantObject);

   return FutureBuilder<Widget>(
     future: _buildList(bloc, context),
     builder: (BuildContext context, AsyncSnapshot<Widget> snapshot) {
       if (snapshot.connectionState == ConnectionState.waiting) {
         return Scaffold(
           appBar: AppBar(
             centerTitle: false,
             title: Text(
               "My Title",
             ),
           ),
           body: Center(child: CircularProgressIndicator()),
         );
       } else {
         return BlocProvider<MyListBloc>(
             bloc: bloc, child: snapshot.data);
       }
     },
   );
 } 

 Future<Widget> _buildList(
     MyListBloc bloc, BuildContext myContext) async {
   //await bloc.init();
   List<Widget> listView = await bloc.getListItems(myContext);
   return StreamBuilder<List<MyListItem>>(
       stream: bloc.drawingCanvasListStream,
       builder: (context, snapshot) {
         return Scaffold(
           appBar: AppBar(
             title: Text('My Title'),
             centerTitle: false,
           ),
           body: SingleChildScrollView(
             child: Column(mainAxisSize: MainAxisSize.min, children: listView),
           ),
         );
       });
 }
}

更新:

我改变了我的方法,并试图让它更简单一些。这是我更新的“列表屏幕”。

class MyListScreen extends StatefulWidget{

 final bloc = MyListBloc();
 @override
 _MyListState createState() => _MyListState();

}

class _MyListState extends State<MyListScreen>{
 @override
 void initState() {
   widget.bloc.getListItems(context);
   super.initState();
 }

 @override
 Widget build(BuildContext context) {
   // TODO: implement build
   return StreamBuilder<List<Widget>>(
       stream: widget.bloc.myListStream,
       builder: (context, snapshot) {
         return Scaffold(
           appBar: AppBar(
             title: Text('My App'),
             centerTitle: false,
           ),
           body:
           SingleChildScrollView(
             child: Column(
                 mainAxisSize: MainAxisSize.min, children: snapshot.data == null ? [Center(child: CircularProgressIndicator())] :
          snapshot.data
          )
          ,
        ),
      );

    });
 }
}

MyListBloc 类有一个 List 的 Stream,它最初是一个空列表。当 getListItems 完成时,它会将这些小部件添加到流中。在异步 getListItems 完成之前,MyListScreen 仍然没有显示。希望这个新代码能让我更容易指出我的误解。

更新 2:

我也试过了,没用:

import 'package:flutter/material.dart';

class MyListScreen extends StatefulWidget{

 final bloc = MyListBloc();
 @override
 _MyListState createState() => _MyListState();

}

class _MyListState extends State<MyListScreen>{
 @override
 void initState() {
   widget.bloc.getListItems(context);
   super.initState();
 }

 @override
 Widget build(BuildContext context) {
   return Scaffold(
     appBar: AppBar(
       title: Text('My App'),
       centerTitle: false,
     ),
     body: StreamBuilder<List<Widget>>(
         stream: widget.bloc.myListStream,
         builder: (context, snapshot) {
           if(snapshot.connectionState == ConnectionState.waiting){
             return Center(child: CircularProgressIndicator());
           }
           else {
             return SingleChildScrollView(
               child: Column(
                   mainAxisSize: MainAxisSize.min, children: snapshot.data
               )
               ,
             );
           }
         })
   );
 }
}

widget.bloc.getListItems(context) 是我不等待的异步方法。然而,屏幕仍然没有出现,直到 snapshot.data != null。

【问题讨论】:

  • 可能是因为它无法区分 2 个脚手架。在上面放一个 ValueKey,看看是否有帮助。理想情况下,您不会在未来构建小部件。只需拥有 bloc.drawingCanvasListStreamFutureBuilder 的未来并在构建器中渲染所有小部件

标签: flutter dart asynchronous flutter-futurebuilder


【解决方案1】:

试试这个以获得正确的数据状态。

  switch(snapshot.connectionState){
                  case ConnectionState.none:
                    return Text('none');
                  case ConnectionState.active:
                  case ConnectionState.waiting:
                    return Text('Active and may be waiting');
                  case ConnectionState.done:
                    return Text('Done');
                  default:
                    return Text('Default');
                }

【讨论】:

  • 它仅有的两个连接状态是 ConnectionState.waiting 和 ConnectionState.active(在构建器的第一行设置断点,这是我得到的唯一两个)。
【解决方案2】:

您说您的流的初始值是一个空列表。所以StreamBuilder会马上显示空列表,所以你看不到CircularProgressIndicator

请试试下面的代码:

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: Text('My App'),
      centerTitle: false,
    ),
    body: StreamBuilder<List<Widget>>(
      stream: widget.bloc.myListStream,
      builder: (context, snapshot) {
        if (!snapshot.hasData || snapshot.data.length == 0) {
          return Center(
            child: CircularProgressIndicator(),
          );
        } else {
          return SingleChildScrollView(
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: snapshot.data,
            ),
          );
        }
      },
    ),
  );
}

不要忘记,即使在您的未来方法完成后没有任何项目添加到列表中时,此代码也会显示 CircularProgressIndicator。如果您不希望发生这种情况,请将流的初始值保留为 null,并在调用 getListItems 时创建一个新列表。所以,你可以像!snapshot.hasData这样检查数据是否为空来显示加载状态。

替代方法

除了StreamBuilder,还有另一种显示流数据的方法。你可以在initState里面收听流,当有数据的时候改变你的屏幕状态。

class MyListScreen extends StatefulWidget {
  MyListScreen();

  @override
  _MyListScreenState createState() => _MyListScreenState();
}

class _MyListScreenState extends State<MyListScreen> {
  final bloc = MyListBloc();
  List<Widget> myList;

  @override
  void initState() {
    super.initState();

    bloc.getListItems(context).whenComplete(() {
      bloc.myListStream.listen((List<Widget> listData) {
        setState(() {
          myList = listData;
        });
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('My App'),
        centerTitle: false,
      ),
      body: _buildBody(),
    );
  }

  Widget _buildBody() {
    if (myList == null) {
      return Center(
        child: CircularProgressIndicator(),
      );
    } else {
      return SingleChildScrollView(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: myList,
        ),
      );
    }
  }
}

【讨论】:

  • 我已经做到了。但是,在返回 SingleChildScrollView 之前,上一页仍在视图中。 CircularProgressIndicator 从不显示。也尝试将其保留为空。断点会命中 if(snapshot.data == null){ return CircularProgressInidcator();},但直到 widget.bloc.getListItems 返回后页面仍然不会显示
  • 在 iPad pro 11 上运行此程序“如果这意味着什么。这可能是线程问题吗?
  • 这很奇怪。如果没有看到您在前一个屏幕中的内容以及您的 bloc 类,很难说些什么。我为您的情况添加了替代方法。你可以试试,然后告诉我它是否有效?
【解决方案3】:

我认为问题应该出在您的myListStream 内部。如果这不起作用,请分享它在里面做什么。

getListItems() 是一个Future 函数(数据为空或项目列表)

myListStream是一个Stream函数

如果要将 Future 结果连接到 Stream,则应立即返回 Stream,如果数据发生更改,则应返回 yield 更新的数据。

请勿使用

StreamBuilder<List<Widget>>(
  stream: myListStream(),  // myListStream will be null until the data is ready
  builder:
  ...

Future getListItems() async {
  List<Widget> listView = await bloc.getListItems(myContext);
  myListStream = Stream(...)
}

使用

StreamBuilder<List<Widget>>(
  stream: myListStream(), // myListStream is a Stream but the data is null
  builder:
  ...

Stream<List<Widget>> myListStream() async* {
  List<Widget> listView = await bloc.getListItems(myContext);
  yield listView;
}
  

为了最好地使用 Stream,我的建议是实现类似 getListItemsStream() 的东西,它可以被 StreamBuilder 直接使用。在您的情况下,我认为您可以使用 FutureBuilder 来实现相同的结果。

【讨论】:

    【解决方案4】:

    这就是我最终修复它的方式。这对我来说没有多大意义,但它确实有效。当 StreamBuilder 连接状态正在等待时,该页面将永远不会显示。在我的 widget.bloc.getListItems(context) 代码中,我之前完成了所有工作,然后在最后添加到流中。

    上一个:

    import 'dart:async';
    
    import 'package:flutter/material.dart';
    
    import 'bloc.dart';
    
    class MyListBloc implements Bloc {
      List<Widget> listItems = <Widget>[];
    
      final _myListController = StreamController<List<Widget>>();
    
      Stream<List<Widget>> get myListStream =>
          _myListController.stream;
    
      MyListBloc();
    
      Future<void> getListItems(BuildContext context) async {
        var widgets = <Widget>[];
        
        ///Do all the async work which may take a while
    
        listItems = widgets;
        update();
      }
    
    
    
      void update() {
        this._myListController.sink.add(listItems);
      }
    
      @override
      void dispose() {
        // TODO: implement dispose
      }
    }
    

    所以我实际上最终做的是更像这样的事情并且它起作用了:

    import 'dart:async';
    
    import 'package:flutter/material.dart';
    
    import 'bloc.dart';
    
    class MyListBloc implements Bloc {
      List<Widget> listItems = <Widget>[];
    
      final _myListController = StreamController<List<Widget>>();
    
      Stream<List<Widget>> get myListStream =>
          _myListController.stream;
    
      MyListBloc();
    
      Future<void> getListItems(BuildContext context) async {
        var widgets = <Widget>[];
    
        ///Go ahead and set the content to be the indicator and add it to the stream now.
        widgets.add(Center(child:CircularProgressIndicator()));
        update();
    
        ///NOW set the content back to empty then Do all the async work which may take a while. 
        
        widgets = <Widget>[];
    
        ///await async stuff here (data retrieval...etc)
    
        ///Then set the listItems to the resulting content and add it to the stream.
        listItems = widgets;
        update();
      }
    
    
    
      void update() {
        this._myListController.sink.add(listItems);
      }
    
      @override
      void dispose() {
        // TODO: implement dispose
      }
    }
    

    这样做,我得到了页面以显示循环进度指示器,直到列表构建完成。然后显示列表内容。

    【讨论】:

      【解决方案5】:
      return Scaffold(
         appBar:AppBar(),
         body: FutureBuilder<Widget>(
            future:() => _buildList(bloc,context),// correct way to pass args to function
            builder:(context,snapshot){
                if(snapshot.connectionState == ConnectionState.waiting)
                    return  Center(child: CircularProgressIndicator());
      
                else 
                     return FlutterLogo();
           }
         ),
      );
      

      【讨论】:

      • 在 _buildList 完成之前,当前页面仍然可见。它不会推送此列表页面并显示 CircularProgressIndicator
      • 将else下的return BlocProvider部分替换为Text("done");这会在加载指示器后显示吗?如果是,那么您正在使用的 BlocProvider 小部件存在一些问题。如果不是,那么您的未来可能会出现一些问题。
      • 感谢您查看此内容。查看我更新的代码,看看它是否更有意义。
      • 除非流中有一些数据,否则您仍然不会显示任何内容。将代码更改为让StreamBuilder 绘制主体而不是整个屏幕。这样AppBar 和其他东西仍然会在没有流数据的情况下显示。检查我的代码大纲。
      • 抱歉,但这仍然无法按预期工作。请参阅更新 2。 widget.bloc.getListItems(context) 是我不等待的异步方法。但是,该页面直到 snapshot.connectionState != waiting 才显示。我在 StreamBuilder 内的两个返回上都放置了一个断点。我看到它在 connectionState == ConnectionState.waiting 时第一次返回。我继续,屏幕没有出现。当它击中另一个返回并继续时 snapshot.connectionState != ConnectionState.waiting .....屏幕终于出现了。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-09-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-09-14
      • 2021-02-15
      • 2021-10-19
      相关资源
      最近更新 更多