Флаттер: правильный подход, чтобы получить ценность из будущего - PullRequest
0 голосов
/ 21 сентября 2019

У меня есть функция, которая возвращает путь к каталогу изображений, она выполняет дополнительную проверку, например, существует ли каталог или нет, и ведет себя соответственно.

Вот мой код:

Future<String> getImagesPath() async {
   final Directory appDir = await getApplicationDocumentsDirectory();
   final String appDirPath = appDir.path;

   final String imgPath = appDirPath + '/data/images';

   final imgDir = new Directory(imgPath);

   bool dirExists = await imgDir.exists();

   if (!dirExists) {
        await new Directory(imgPath).create(recursive: true);
   }

   return imgPath;

}

Этот фрагмент кода работает, как и ожидалось, но у меня проблема с получением значения из Future.

Сценарий: У меня есть данные, хранящиеся в локальной базе данных, и я пытаюсь отобразить их внутри списка.Я использую FutureBuilder, как объяснено в этом ответе .Каждая строка данных имеет изображение, связанное с ней (означает, что имя изображения хранится в БД).

Внутри Widget build метода, у меня есть этот код:

@override
Widget build(BuildContext context) {
 getImagesPath().then((path){
     imagesPath = path;
     print(imagesPath); //prints correct path
   });
 print(imagesPath);   //prints null

return Scaffold(
    //removed
    body: FutureBuilder<List>(
        future: databaseHelper.getList(),
        initialData: List(),
        builder: (context, snapshot) {
          return snapshot.hasData
              ? ListView.builder(
                  itemCount: snapshot.data.length,
                  itemBuilder: (_, int position) {
                    final item = snapshot.data[position];
                    final image = "$imagesPath/${item.row[0]}.jpg";
                    return Card(
                        child: ListTile(
                      leading: Image.asset(image),
                      title: Text(item.row[1]),
                      subtitle: Text(item.row[2]),
                      trailing: Icon(Icons.launch),
                    ));
                  })
              : Center(
                  child: CircularProgressIndicator(),
                );
        }));

}

Сдвиг return Scaffold(.....) внутри .then не работает.Потому что сборка виджета ничего не возвращает.

Другой вариант, который я нашел, - async/await, но в конце та же проблема, код доступен ниже:

_getImagesPath() async {
    return await imgPath();
}

Вызов _getImagesPath() возвращает Future вместо фактических данных.

Я полагаю, что есть очень маленькая логическая ошибка, но я не могу найти ее сам.

Ответы [ 2 ]

1 голос
/ 21 сентября 2019

Перемещение этого фрагмента кода внутрь FutureBuilder должно решить проблему.

getImagesPath().then((path){
 imagesPath = path;
 print(imagesPath); //prints correct path
});

Итак, ваш окончательный код должен выглядеть следующим образом:

@override
Widget build(BuildContext context) {

return Scaffold(
    //removed
    body: FutureBuilder<List>(
        future: databaseHelper.getList(),
        initialData: List(),
        builder: (context, snapshot) {
          return snapshot.hasData
              ? ListView.builder(
                  itemCount: snapshot.data.length,
                  itemBuilder: (_, int position) {
                    getImagesPath().then((path){
                        imagesPath = path;
                    });

                    final item = snapshot.data[position];
                    final image = "$imagesPath/${item.row[0]}.jpg";
                    return Card(
                        child: ListTile(
                      leading: Image.file(File(image)),
                      title: Text(item.row[1]),
                      subtitle: Text(item.row[2]),
                      trailing: Icon(Icons.launch),
                    ));
                  })
              : Center(
                  child: CircularProgressIndicator(),
                );
        }));
}

Надеюсь, это поможет!

1 голос
/ 21 сентября 2019

Я вижу, что вы должны построить свой виджет из вывода двух фьючерсов.Вы можете использовать два FutureBuilders или использовать вспомогательный метод, чтобы объединить их в один и упростить код.

Также никогда не вычисляйте / не вызывайте асинхронную функцию из функции build.Он должен быть инициализирован раньше (либо в конструкторе, либо в методе initState), другой мудрый виджет может перекраситься навсегда.

Принимая решение, чтобы упростить код, лучше объединить оба будущих вывода в один класс, как показано ниже:

Данные, необходимые для build метода:


class DataRequiredForBuild {
  String imagesPath;
  List items;

  DataRequiredForBuild({
    this.imagesPath,
    this.items,
  });
}

Функция для извлечения всех необходимых данных:


Future<DataRequiredForBuild> _fetchAllData() async {
  return DataRequiredForBuild(
    imagesPath: await getImagesPath(),
    items: await databaseHelperGetList(),
  );
}

Теперь собираем все вместе в виджете:

Future<DataRequiredForBuild> _dataRequiredForBuild;

@override
void initState() {
  super.initState();
  // this should not be done in build method
  _dataRequiredForBuild = _fetchAllData();
}

@override
Widget build(BuildContext context) {
  return Scaffold(
    //removed
    body: FutureBuilder<DataRequiredForBuild>(
      future: _dataRequiredForBuild,
      builder: (context, snapshot) {
        return snapshot.hasData
            ? ListView.builder(
                itemCount: snapshot.data.items.length,
                itemBuilder: (_, int position) {
                  final item = snapshot.data.items[position];
                  final image = "${snapshot.data.imagesPath}/${item.row[0]}.jpg";
                  return Card(
                      child: ListTile(
                    leading: Image.asset(image),
                    title: Text(item.row[1]),
                    subtitle: Text(item.row[2]),
                    trailing: Icon(Icons.launch),
                  ));
                })
            : Center(
                child: CircularProgressIndicator(),
              );
      },
    ),
  );
}

Надеюсь, это поможет.

...