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
}
}
通过这种方式,我得到了显示循环进度指示器的页面,直到列表构建完成。然后显示列表内容。
我有一个显示项目列表的页面。单击上一页的按钮可到达此页面。检索完成后,我使用 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
更新 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
}
}
通过这种方式,我得到了显示循环进度指示器的页面,直到列表构建完成。然后显示列表内容。