Flutter - 在 FutureBuilder 完成之前,页面似乎不会推送

Flutter - Page Does Not Appear to Push Until FutureBuilder is Done

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

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 class 有一个 List 流,它最初是一个空列表。当 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.

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();
     }
   ),
);

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

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

您声明您的流的初始值为空列表。因此,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,
        ),
      );
    }
  }
}

我觉得问题应该出在你的myListStream里面。如果这不起作用,请分享它在里面做了什么。

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

myListStream是一个Stream函数

如果您想将 Future 结果连接到 Stream,您应该立即 return 一个 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 来获得相同的结果。

这就是我最终修复它的方式。这对我来说没有多大意义,但它奏效了。当 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
  }
}

通过这种方式,我得到了显示循环进度指示器的页面,直到列表构建完成。然后显示列表内容。