FutureBuilder не ждет завершения будущего

Я использую вызов API для получения данных и отображения в ListView.

Вот класс будущего строителя:

Widget futureBuilder() {
    return FutureBuilder<List<Project>>(
      future: _getProjects(),
      builder: (context, snapshot) {
        debugPrint('Builder');
        switch (snapshot.connectionState) {
          case ConnectionState.none:
          case ConnectionState.waiting:
          default:
            if (snapshot.hasError)
              return new Text('Error: ${snapshot.error}');
            else
              return listWidget(snapshot);
        }
      },
    );
  }
}

Проблема в том, что debugPrint ('Builder') вызывается еще до того, как _getProjects () возвращает результат, поэтому снимок передается как null в моем виджете ListView.

Вот класс _getProjects ():

Future<List<Project>> _getProjects() async {
    List<Project> projects = [];
    String getProjects = "https://api.myjson.com/bins/1g3xpe";
    var response = await http.get(getProjects);
    Iterable list = json.decode(response.body);
    projects = list.map((model) => Project.fromJson(model)).toList();
    debugPrint('Size ' + projects.length.toString());
    return projects;
  }

Вот полный код:

class Projects extends StatefulWidget {
  @override
  ProjectState createState() => new ProjectState();
}

class ProjectState extends State {

   Future<List<Project>> _getProjects() async {
    List<Project> projects = [];
    String getProjects = "https://api.myjson.com/bins/1g3xpe";
    var response = await http.get(getProjects);
    Iterable list = json.decode(response.body);
    projects = list.map((model) => Project.fromJson(model)).toList();
    debugPrint('Size ' + projects.length.toString());
    return projects;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      primary: true,
      appBar: EmptyAppBar(),
      body: Column(
        children: <Widget>[
          headerWidget(),
          futureBuilder()
        ],
      ),
    );
  }

  Widget futureBuilder() {
    return FutureBuilder<List<Project>>(
      future: _getProjects(),
      builder: (context, snapshot) {
        debugPrint('Builder');
        switch (snapshot.connectionState) {
          case ConnectionState.none:
          case ConnectionState.waiting:
          default:
            if (snapshot.hasError)
              return new Text('Error: ${snapshot.error}');
            else
              return listWidget(snapshot);
        }
      },
    );
  }
}



Widget headerWidget() {
  return Container(
    padding: EdgeInsets.all(16.0),
    color: Colors.blueAccent,
    child: Container(
        decoration: ShapeDecoration(
            color: Colors.white,
            shape: RoundedRectangleBorder(
                borderRadius: BorderRadius.all(Radius.circular(25.0)),
                side: BorderSide(color: Colors.white))),
        child: Row(
          children: <Widget>[
            Padding(padding: EdgeInsets.only(left: 12)),
            Icon(Icons.arrow_back, color: Colors.black54),
            Padding(padding: EdgeInsets.only(left: 12)),
            Flexible(
              fit: FlexFit.loose,
              child: searchBar(),
            ),
            Icon(
              Icons.search,
              color: Colors.black54,
            ),
            Padding(padding: EdgeInsets.only(right: 12)),
          ],
        )),
  );
}

Widget searchBar() {
  return Container(
    height: 52,
    alignment: Alignment.centerLeft,
    padding: EdgeInsets.only(left: 12, right: 12),
    child: TextField(
      decoration: new InputDecoration.collapsed(
        border: InputBorder.none,
        filled: false,
        hasFloatingPlaceholder: false,
        hintText: 'Search here',
        hintStyle: TextStyle(color: Colors.black54),
      ),
    ),
  );
}

Widget listWidget(AsyncSnapshot<List<Project>> snapshot) {
  return Scaffold(
      body: ListView.builder(
          itemBuilder: (BuildContext context, int index) {
            listItem(snapshot.data[index]);
          },
          itemCount: snapshot.data.length));
}

Widget listItem(Project project) {
  return Card(
    elevation: 6.0,
    child: Column(
      children: <Widget>[Text('Project ID'), Text('Project Name')],
    ),
  );
}

class EmptyAppBar extends StatelessWidget implements PreferredSizeWidget {
  @override
  Widget build(BuildContext context) {
    return Container();
  }

  @override
  Size get preferredSize => Size(0.0, 0.0);
}

ОБНОВЛЕНИЕ: я внес некоторые изменения в FutureBuilder:

Widget futureBuilder() {
    return FutureBuilder<List<Project>>(
      future: _getProjects(),
      builder: (context, snapshot) {
        debugPrint('Builder');
        switch (snapshot.connectionState) {
          case ConnectionState.done:
            if (snapshot.hasError)
              return new Text('Error: ${snapshot.error}');
            else
              return listWidget(snapshot);
            break;

          default:
            debugPrint("Snapshot " + snapshot.toString());
        }
      },
    );
  }

Сейчас я получаю эту ошибку:

I/flutter ( 4054): _FutureBuilderState<List<Project>>#67dc4):
I/flutter ( 4054): A build function returned null.
I/flutter ( 4054): The offending widget is: FutureBuilder<List<Project>>
I/flutter ( 4054): Build functions must never return null. To return an empty space that causes the building widget to
I/flutter ( 4054): fill available room, return "new Container()". To return an empty space that takes as little room as
I/flutter ( 4054): possible, return "new Container(width: 0.0, height: 0.0)".

И SnapShot по умолчанию возвращает этот снимок AsyncSnapshot> (ConnectionState.waiting, null, null)


person Hirak Chhatbar    schedule 22.05.2019    source источник
comment
в вашем коммутаторе, почему вы хотите использовать состояние none и waiting?   -  person pskink    schedule 22.05.2019
comment
Просто для отладки. И что еще более важно, они были в коде, который я получил из Интернета.   -  person Hirak Chhatbar    schedule 22.05.2019
comment
ты знаешь, как switch работает? если вы не знаете, как использовать FutureBuilder, прочтите его официальную документацию - у него есть рабочий пример кода   -  person pskink    schedule 22.05.2019
comment
да. Думаю, что debugPrint ('Builder') вызывается до того, как Future вернет результат.   -  person Hirak Chhatbar    schedule 22.05.2019
comment
вы проверили ссылку на опубликованную мною документацию? если да, что неясно в предоставленном ими примере кода?   -  person pskink    schedule 22.05.2019
comment
@pskink внес несколько изменений. И добавлена ​​ошибка, которую я получаю в своем исходном сообщении   -  person Hirak Chhatbar    schedule 22.05.2019
comment
default case возвращает null: "I/flutter ( 4054): A build function returned null."   -  person pskink    schedule 22.05.2019
comment
@pskink Спасибо за помощь. После нескольких тренировок я понял ошибку и заставил ее работать. Спасибо за то, что вы помогли вместо кормления с ложечки. Действительно ценю это. Ваше здоровье!!   -  person Hirak Chhatbar    schedule 22.05.2019
comment
конечно, добро пожаловать, поэтому я не даю рыбу - вместо этого даю удочку ;-)   -  person pskink    schedule 22.05.2019


Ответы (2)


I / flutter (4054): функция построения вернула нуль.

Вам всегда нужно вернуть виджет из конструктора, несмотря ни на что:

Widget futureBuilder() {
        return FutureBuilder<List<Project>>(
          future: _getProjects(),
          builder: (context, snapshot) {
            debugPrint('Builder');
            switch (snapshot.connectionState) {
              case ConnectionState.done:
                if (snapshot.hasError)
                  return new Text('Error: ${snapshot.error}');
                else
                  return listWidget(snapshot);
                break;

              default:
                debugPrint("Snapshot " + snapshot.toString());
              return Container() // also check your listWidget(snapshot) as it may return null.
            }
          },
        );
      }

Проблема в том, что debugPrint ('Builder') вызывается еще до того, как _getProjects () возвращает результат, поэтому снимок передается как null в моем виджете ListView.

Это нормально, подумайте, например, если у вас есть длинная задача (запрос или какое-то действительно долгое вычисление), вам нужно будет отображать загрузку, пока это будущее не будет «выполнено». Кроме того, если что-то должно быть выполнено в «будущем», это означает, что вы можете выполнить это «позже», в противном случае вам нужно использовать await.

person Durdu    schedule 22.05.2019

Вам необходимо проверить, есть ли какие-либо данные, возвращаемые AsyncSnapshot (снимок). Добавьте такое ifstatement:

if(snapshot.hasData && !snapshot.hasError) {
//rest of your code
}
else {
//show progress indicator or error...
}
person pso    schedule 22.05.2019