How do I correctly implement a FutureBuilder in Flutter in this case?

I am trying to display a horizontally scrollable ListView of different images. This is supposed to be achieved with the Widget MultipleImageDemo. I use MultipleImagePicker in loadAssets() to create a list of Assets of the images that are selected from the iPhone gallery. Then, I convert these Assets into Files so that I can display them in a ListView using getImageFileFromAssets(Asset asset) from How to convert asset image to File?. However, the function for converting assets to Files is asynchronous so I need to use a FutureBuilder when I try to use this function inside the Widget build(BuildContext context).

This is how I tried to implement it:

Future<File> getImageFileFromAssets(Asset asset) async {
  final byteData = await asset.getByteData();

  final tempFile =
      File("${(await getTemporaryDirectory()).path}/${asset.name}");
  final file = await tempFile.writeAsBytes(
    byteData.buffer.asUint8List(byteData.offsetInBytes, byteData.lengthInBytes),
  );

class MultipleImageDemo extends StatefulWidget {
  @override
  _MultipleImageDemoState createState() => _MultipleImageDemoState();
}

class _MultipleImageDemoState extends State<MultipleImageDemo> {
  List<Asset> images = <Asset>[];

  @override
  void initState() {
    super.initState();
  }

  Future<ListView> DisplayPhotos() async {
    List<File> displayedPhotos = <File>[];
    for (var i = 0; i < images.length; i++) {
      File image_file = await getImageFileFromAssets(images[i]);
      displayedPhotos.add(image_file);
    }
    return ListView(
        scrollDirection: Axis.horizontal,
        children: List.generate(displayedPhotos.length, (index) {
          File displayedPhoto = displayedPhotos[index];
          return Container(
              height: MediaQuery.of(context).size.height,
              width: MediaQuery.of(context).size.width,
              child: Image.file(displayedPhoto));
        }));
  }

Future<void> loadAssets() async {
    List<Asset> resultList = <Asset>[];
    String error = 'No Error Detected';

    try {
      resultList = await MultiImagePicker.pickImages(
        maxImages: 300,
        enableCamera: true,
        selectedAssets: images,
        cupertinoOptions: CupertinoOptions(takePhotoIcon: "chat"),
        materialOptions: MaterialOptions(
          actionBarColor: "#abcdef",
          actionBarTitle: "Example App",
          allViewTitle: "All Photos",
          useDetailsView: false,
          selectCircleStrokeColor: "#000000",
        ),
      );
    } on Exception catch (e) {
      error = e.toString();
    }

    if (!mounted) return;

    setState(() {
      images = resultList;
      // _error = error;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        height: MediaQuery.of(context).size.height,
        width: MediaQuery.of(context).size.width,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            ElevatedButton(
              child: Text("Pick images"),
              onPressed: loadAssets,
            ),
            Expanded(
                child: FutureBuilder<ListView>(
                    future: DisplayPhotos(),
                    builder: (BuildContext context,
                        AsyncSnapshot<ListView> snapshot) {
                      List<Widget> children;
                      if (snapshot.hasData) {
                        children = <Widget>[
                          const Icon(
                            Icons.check_circle_outline,
                            color: Colors.green,
                            size: 60,
                          ),
                          Padding(
                            padding: const EdgeInsets.only(top: 16),
                            child: Text('Result: ${snapshot.data}'),
                          )
                        ];
                      }
                      return Center(
                        child: Column(
                          mainAxisAlignment: MainAxisAlignment.center,
                          crossAxisAlignment: CrossAxisAlignment.center,
                          children: children,
                        ),
                      );
                    })),
          ],
        ),
      ),
    );
  }
}

This is the error I get:

The following assertion was thrown building FutureBuilder<ListView>(dirty, state: _FutureBuilderState<ListView>#66af4):
'package:flutter/src/widgets/framework.dart': Failed assertion: line 1741 pos 14: 'children != null': is not true.
2

Either the assertion indicates an error in the framework itself, or we should provide substantially more information in this error message to help you determine and fix the underlying cause.
In either case, please report this assertion by filing a bug on GitHub:
  https://github.com/flutter/flutter/issues/new?template=2_bug.md
The relevant error-causing widget was
FutureBuilder<ListView>
package:project_startup/screens/addPage.dart:279
When the exception was thrown, this was the stack
#2      new MultiChildRenderObjectWidget
package:flutter/…/widgets/framework.dart:1741
#3      new Flex
package:flutter/…/widgets/basic.dart:4371
#4      new Column
package:flutter/…/widgets/basic.dart:4939
#5      _MultipleImageDemoState.build.<anonymous closure>
package:project_startup/screens/addPage.dart:323
#6      _FutureBuilderState.build
package:flutter/…/widgets/async.dart:775

How do I correctly implement the FutureBuilder? Is it even necessary to use it?

This is what is the simulator shows when I run this app even after I have selected several images from the gallery

Upvotes: 0

Views: 2300

Answers (2)

Inserting snapshot.data into children and then displaying children in the returned container seems to have solved the problem. A big thank you to the people who helped :).

Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        height: MediaQuery.of(context).size.height,
        width: MediaQuery.of(context).size.width,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            ElevatedButton(
              child: Text(PICKIMAGES),
              onPressed: loadAssets,
            ),
            Expanded(
                child: FutureBuilder<Container>(
                    future: DisplayPhotos(),
                    builder: (BuildContext context,
                        AsyncSnapshot<Container> snapshot) {
                      List<Widget> children = [];
                      if (snapshot.hasData) {
                        children = <Widget>[
                          snapshot.data,
                        ];
                      }
                      return Container(
                          height: MediaQuery.of(context).size.height,
                          width: MediaQuery.of(context).size.width,
                          child: ListView(
                              children: children));
                    })),
          ],
        ),
      ),
    );
  }
}

Upvotes: 0

Benjamin
Benjamin

Reputation: 6161

EDIT

To display the images you could do something like this:

return Center(
  child: Column(
    mainAxisAlignment: MainAxisAlignment.center,
    crossAxisAlignment: CrossAxisAlignment.center,
    children: [snapshot.data, ...children], // this line
  ),
);

Change List<Widget> children; to List<Widget> children = []; so that way it's initalized.

The problem comes from this:

return Center(
  child: Column(
    mainAxisAlignment: MainAxisAlignment.center,
    crossAxisAlignment: CrossAxisAlignment.center,
    children: children, // this line
  ),
);

Because children is null when the snapshot is loading (has no data), you get that error in the console.

Upvotes: 1

Related Questions