Flutter: setState() inside GestureDetector is not working

Issue

In my code I am loading network images into a grid. Then I allow the user to replace a network image by selecting one from their own gallery. Please check the below code.

Widget _buildPhotoSection() {
    return MediaQuery.removePadding(
      context: context,
      removeTop: true,
      child: GridView.builder(
          shrinkWrap: true,
          gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
            crossAxisCount: 3,
          ),
          itemCount: 5,
          itemBuilder: (BuildContext context, int index) {
            XFile? imageFile;
            bool check = false;
            return GestureDetector(
              onTap: () async {
                final XFile? image =
                    await _picker.pickImage(source: ImageSource.gallery);

                setState(() {
                  imageFile = image;
                  check = true;
                  print("DONE");
                });
              },
              child: Stack(
                children: [
                  Card(
                    color: Colors.amber,
                    child: Container(
                      padding: EdgeInsets.all(1),
                      child: check == false
                          ? index <= imageList.length - 1
                              ? CachedNetworkImage(
                                  width: 200,
                                  height: 150,
                                  imageUrl: imageList[index].imageURL == ""
                                      ? "https://www.freeiconspng.com/uploads/no-image-icon-6.png"
                                      : imageList[index].imageURL,
                                  placeholder: (context, url) =>
                                      CircularProgressIndicator(),
                                  errorWidget: (context, url, error) =>
                                      Icon(Icons.error),
                                  fit: BoxFit.fill,
                                )
                              : CachedNetworkImage(
                                  width: 200,
                                  height: 150,
                                  imageUrl:
                                      "https://www.freeiconspng.com/uploads/no-image-icon-6.png",
                                  placeholder: (context, url) =>
                                      CircularProgressIndicator(),
                                  errorWidget: (context, url, error) =>
                                      Icon(Icons.error),
                                  fit: BoxFit.fill,
                                )
                          : Image.file(
                              File(imageFile!.path),
                              width: 200,
                              height: 150,
                            ),
                    ),
                  ),
                  Align(
                      alignment: Alignment.bottomCenter,
                      child: TextButton(
                          onPressed: () {},
                          child: Text(
                            'Change',
                          )))
                ],
              ),
            );
          }),
    );
  }

here I can pick the image but the image from gallery never get displayed. It looks like the setState was working even though it is called.

Why is this happening and how can I fix this?

Solution

Your variable XFile? imageFile; is defined in _buildPhotoSection function – not in the widget itself. So you are practically updating local variable in setState() call. Once setState() finishes – it will notify Flutter engine to rebuild the widget – and your imageFile variable will be reinitialized – not holding the value you set in previous iteration.

I think you should just move XFile? imageFile; and bool check = false; to class level, this should do the trick.

Edit: after your comment about multiple images – here’s my suggestion:

var imageFile=<int, XFile>{};

Widget _buildPhotoSection() {
    return MediaQuery.removePadding(
      context: context,
      removeTop: true,
      child: GridView.builder(
          shrinkWrap: true,
          gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
            crossAxisCount: 3,
          ),
          itemCount: 5,
          itemBuilder: (BuildContext context, int index) {
//             XFile? imageFile;
            //bool check = false;
            return GestureDetector(
              onTap: () async {
                final XFile? image =
                    await _picker.pickImage(source: ImageSource.gallery);

                setState(() {
                  imageFile[index] = image;
                  //check = true;
                  print("DONE");
                });
              },
              child: Stack(
                children: [
                  Card(
                    color: Colors.amber,
                    child: Container(
                      padding: EdgeInsets.all(1),
                      child: imageFile.containsKey(index) == false
                          ? index <= imageList.length - 1
                              ? CachedNetworkImage(
                                  width: 200,
                                  height: 150,
                                  imageUrl: imageList[index].imageURL == ""
                                      ? "https://www.freeiconspng.com/uploads/no-image-icon-6.png"
                                      : imageList[index].imageURL,
                                  placeholder: (context, url) =>
                                      CircularProgressIndicator(),
                                  errorWidget: (context, url, error) =>
                                      Icon(Icons.error),
                                  fit: BoxFit.fill,
                                )
                              : CachedNetworkImage(
                                  width: 200,
                                  height: 150,
                                  imageUrl:
                                      "https://www.freeiconspng.com/uploads/no-image-icon-6.png",
                                  placeholder: (context, url) =>
                                      CircularProgressIndicator(),
                                  errorWidget: (context, url, error) =>
                                      Icon(Icons.error),
                                  fit: BoxFit.fill,
                                )
                          : Image.file(
                              File(imageFile[index]!.path),
                              width: 200,
                              height: 150,
                            ),
                    ),
                  ),
                  Align(
                      alignment: Alignment.bottomCenter,
                      child: TextButton(
                          onPressed: () {},
                          child: Text(
                            'Change',
                          )))
                ],
              ),
            );
          }),
    );
  }

You will note that I converted imageFile to a map – that will hold an index and XFile that user selected. I also removed check variable – I’m checking if the Map contains the index key or not, and based on that the image should render.

I didn’t try to compile this, so few errors might be inthere.

Answered By – Andrija

Answer Checked By – Clifford M. (FlutterFixes Volunteer)

Leave a Reply

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