Flutter - Page Does Not Appear to Push Until FutureBuilder is Done
Try this out to get proper state of your data.
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'); }
You stated that the initial value of your stream is an empty list. Therefore, StreamBuilder
will display the empty list right away, so you can't see CircularProgressIndicator
.
Try the code below, please:
@overrideWidget 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, ), ); } }, ), );}
Don't forget that this code will show the CircularProgressIndicator
even when there is no item added to the list after your future method completed. If you don't want this to happen, leave the initial value of your stream as null and create a new list when you call getListItems
. So, you can just check the data for null like !snapshot.hasData
to show the loading state.
Alternative Approach
There is another way to display data from a stream besides StreamBuilder
. You can listen to the stream inside initState
and change the state of your screen when there is data.
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, ), ); } }}
I think the problem should be inside your myListStream
. Please share what it doing inside if this is not working.
getListItems()
is a Future
function (data is null or a list of items)
myListStream
is a Stream
function
If you want to concatenate the Future result to Stream, you should return a Stream immediately and yield
the updated data if the data is changed.
DO NOT USE
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(...)}
USE
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;}
For the best use of Stream, my suggestion is to implement something like getListItemsStream()
that can be used directly by StreamBuilder. In your case, I think you can just use FutureBuilder to achieve the same result.