flutterflutter-listviewflutter-camera

ListView in Future not being updated using setState


Package multiple_image_camera on pub.dev has some limitations so I'm trying to create my own component that can be customized. I tried placing the imagesFromCameraRoll.add(img) inside and outside setState. Also tried to put takePicture contents into the onPressed event, did not work.

  Future<void> openCamera(BuildContext context) async {
    await startCamera(0); // rear
    if (!mounted) return;
    Navigator.of(context).push(
      MaterialPageRoute(
        builder: (context) => Scaffold(
          appBar: AppBar(title: const Text('Fotos')),
          body: Stack(
            children: [
              SizedBox(
                width: MediaQuery.of(context).size.width,
                height: MediaQuery.of(context).size.height,
                child: FittedBox(
                  fit: BoxFit.cover,
                  child: SizedBox(
                    width: MediaQuery.of(context).size.width,
                    height: MediaQuery.of(context).size.width / cameraController.value.aspectRatio,
                    child: CameraPreview(cameraController),
                  ),
                ),
              ),
              imagesFromCameraRoll.isNotEmpty
                  ? SafeArea(
                      child: Align(
                        alignment: Alignment.bottomLeft,
                        child: Column(
                          mainAxisSize: MainAxisSize.min,
                          children: [
                            SingleChildScrollView(
                              scrollDirection: Axis.horizontal,
                              child: Padding(
                                padding: const EdgeInsets.only(left: 8.0, right: 8.0, bottom: 150),
                                child: Container(
                                  height: 100,
                                  decoration: BoxDecoration(borderRadius: BorderRadius.circular(8.0)),
                                  child: ListView.builder(
                                    shrinkWrap: true,
                                    scrollDirection: Axis.horizontal,
                                    itemCount: imagesFromCameraRoll.length,
                                    itemBuilder: (context, index) {
                                      return ClipRRect(
                                        borderRadius: BorderRadius.circular(15),
                                        child: Image.file(
                                          File(imagesFromCameraRoll[index].path),
                                          height: 90,
                                          fit: BoxFit.cover,
                                        ),
                                      );
                                    },
                                  ),
                                ),
                              ),
                            ),
                          ],
                        ),
                      ),
                    )
                  : Container(),
            ],
          ),
          floatingActionButton: FloatingActionButton.large(
            onPressed: () async {
              takePicture();
            },
            child: const Icon(Icons.camera_alt),
          ),
          floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
        ),
      ),
    );
  }

  List<XFile> imagesFromCameraRoll = [];
  void takePicture() async {
    if (cameraController.value.isTakingPicture || !cameraController.value.isInitialized) {
      return;
    }
    try {
      final XFile img = await cameraController.takePicture();
      setState(() {
        imagesFromCameraRoll.add(img);
      });
    } catch (e) {
      if (kDebugMode) {
        print(e);
      }
    }
  }

any help would be really appreciated. thanks


Solution

  • The navigator push method is called imperatively when you call openCamera, so the widget returned by the MaterialPageRoute's builder method won't be reactive to the state changes of the current StatefulWidget state.

    You should extract the Scaffold to its own StatefulWidget and manage the states (such as imagesFromCameraRoll) inside this new StatefulWidget state. Move methods that modify those states to this new widget as well.

    Future<void> openCamera(BuildContext context) async {
      await startCamera(0); // rear
      if (!mounted) return;
      Navigator.of(context).push(
        MaterialPageRoute(
          builder: (context) => FotosScreen(),
        ),
      );
    }
    
    class FotosScreen extends StatefulWidget {
      const FotosScreen({super.key});
    
      @override
      State<FotosScreen> createState() => _FotosScreenState();
    }
    
    class _FotosScreenState extends State<FotosScreen> {
      List<XFile> imagesFromCameraRoll = [];
    
      void takePicture() async {
        if (cameraController.value.isTakingPicture ||
            !cameraController.value.isInitialized) {
          return;
        }
        try {
          final XFile img = await cameraController.takePicture();
          setState(() {
            imagesFromCameraRoll.add(img);
          });
        } catch (e) {
          if (kDebugMode) {
            print(e);
          }
        }
      }
    
      @override
      Widget build(BuildContext context) {
        return const Scaffold(
          // ...
        );
      }
    }