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

Issue

I have a page that shows a list of items. This page is reached by button click on a previous page. I use a FutureBuilder to populate the list of items when the retrieval is complete. While the ConnectionState is "waiting," I show a circular progress indicator. However, the progress indicator never appears and the list page does not appear to push until the FutureBuilder completes. If I place a breakpoint in the "if (snapshot.connectionState == ConnectionState.waiting)" block, it hits that breakpoint, but the screen doesn’t change on return. The screen does appear as expected, but as the list of items grows it takes longer and longer for the screen to appear. Am I missing something here:

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

UPDATE:

I altered my approach and tried to make it a bit simpler. Here is my updated "List Screen.":

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

    });
 }
}

The MyListBloc class has a Stream of List<Widget> that is initially an empty list. When the getListItems finishes, it adds those widgets to the stream. The MyListScreen still is not showing up until that async getListItems has finished. Hopes this new code makes it a little easier to point out my misunderstanding.

UPDATE 2:

I tried this as well to no avail:

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) is an async method that I do not await. Yet, the screen still does not appear until snapshot.data != null.

Solution

This is how I ended up fixing it. It doesn’t make a whole lot of sense to me, but it worked. The page would never show while the StreamBuilder connection state was waiting. In my widget.bloc.getListItems(context) code, I was previously doing all the work then adding to the stream at the end.

PREVIOUS:

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
  }
}

So what I actually ended up doing was something more like this and it worked:

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
  }
}

Doing it this way I got the page to show with the circular progress indicator until the list building was complete. It then shows the list content.

Answered By – mac

Answer Checked By – Candace Johnson (FlutterFixes Volunteer)

Leave a Reply

Your email address will not be published. Required fields are marked *