Flutter - Image.memory not refreshing after source change

696

Came up with a solution. I created a second variable to hold the new image string and showed an entirely new image widget once the second variable had value.

String _newImage;

In the success of the upload...

_newImage = uploadResult;
setState(() {});

Image widget...

child: (_newImage == null || _newImage == '')
         ? new Image.memory(base64Decode(_imageString), fit: BoxFit.fill)
         : new Image.memory(base64Decode(_newImage), fit: BoxFit.fill)

Not a very elegant solution, but it's a solution, but also not necessarily the answer as to why the original issue was there.

Share:
696
unselected
Author by

unselected

Updated on January 01, 2023

Comments

  • unselected
    unselected over 1 year

    I have a page that allows users to upload documents (as images). I have structured my page in a way that for each document type that can be uploaded a Document_Upload widget is used to reduce the amount of repeated code.

    On initial load I use a FutureBuilder to get all the documents the user has already uploaded from our REST Api and then populate each Document_Upload widget with the relevant data.

    On successful upload our REST Api returns the new image back to the Flutter app as a Byte Array so it can be displayed.

    The problem I am currently facing is that no matter what I try the image widget (Image.memory) does not display the new image, it just stays on the old one. I have tried almost everything I can think of/ find online to resolve this issue, including:

    • Calling setState({}); after updating the imageString variable - I can see the widget flash but it remains on the original image.
    • Using a function to callback to the parent widget to rebuild the entire child widget tree - same result as setState, all the widgets flash, but no update.
    • Calling imageCache.clear() & imageCache.clearLiveImages() before updating the imageString.
    • Using CircleAvatar instead of Image.memory.
    • Rebuilding the Image widget by calling new Image.memory() inside the setState call.

    I am starting to question if this is an issue related to Image.memory itself, however, using Image.File / Image.network is not an option with our current requirement.

    Refreshing the page manually causes the new image to show up.

    My code is as follows:

    documents_page.dart

    class DocumentsPage extends StatefulWidget {
      @override
      _DocumentsPageState createState() => _DocumentsPageState();
    }
    
    class _DocumentsPageState extends State<DocumentsPage>
        with SingleTickerProviderStateMixin {
      Future<Personal> _getUserDocuments;
      Personal _documents;
    
      @override
      void didChangeDependencies() {
        super.didChangeDependencies();
        _getUserDocuments = sl<AccountProvider>().getUserDocuments();
      }
    
      @override
      Widget build(BuildContext context) {
        return SingleChildScrollView(
          child: SafeArea(
            child: Center(
                child: Padding(
              padding: EdgeInsets.all(20),
              child: Container(
                  constraints: BoxConstraints(maxWidth: 1300),
                  child: buildFutureBuilder(context)),
            )),
          ),
        );
      }
    
      Widget buildFutureBuilder(BuildContext context) {
        var screenSize = MediaQuery.of(context).size;
        return FutureBuilder<Personal>(
            future: _getUserDocuments,
            builder: (context, AsyncSnapshot<Personal> snapshot) {
              if (!snapshot.hasData) {
                return Text("Loading");
              } else {
                if (snapshot.data == null) {
                  return Center(child: Text('Error: ${snapshot.error}'));
                } else {
                  _documents = snapshot.data;
    
                  return Column(
                    children: [
                      SizedBox(height: 20.0),
                      Text(
                        "DOCUMENTS",
                        textAlign: TextAlign.center,
                        style: TextStyle(
                            fontSize: 25,
                            fontWeight: FontWeight.bold,
                            color: AppColors.navy),
                      ),
                      Container(
                        constraints: BoxConstraints(maxWidth: 250),
                        child: Divider(
                          color: AppColors.darkBlue,
                          height: 20,
                        ),
                      ),
                      Container(
                          margin: EdgeInsets.only(top: 5.0, bottom: 5.0),
                          child: Text(
                              "These documents are required in order to verify you as a user",
                              style: TextStyle(fontSize: 14))),
                      Container(
                          margin: EdgeInsets.only(bottom: 25.0),
                          child: Text("View our Privacy Policy",
                              style: TextStyle(fontSize: 14))),
                      Container(
                          child: screenSize.width < 768
                              ? Column(
                                  children: [
                                    DocumentUpload(
                                        imageType: "ID",
                                        imageString: _documents.id),
                                    DocumentUpload(
                                      imageType: "Drivers License Front",
                                      imageString: _documents.driversLicenseFront,
                                    ),
                                    DocumentUpload(
                                      imageType: "Drivers License Back",
                                      imageString: _documents.driversLicenseBack,
                                    )
                                  ],
                                )
                              : Row(
                                  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                                  children: [
                                      DocumentUpload(
                                          imageType: "ID",
                                          imageString: _documents.id),
                                      DocumentUpload(
                                        imageType: "Drivers License Front",
                                        imageString: _documents.driversLicenseFront,
                                      ),
                                      DocumentUpload(
                                        imageType: "Drivers License Back",
                                        imageString: _documents.driversLicenseBack,
                                      ),
                                    ])),
                      Container(
                          child: screenSize.width < 768
                              ? Container()
                              : Padding(
                                  padding:
                                      EdgeInsets.only(top: 10.0, bottom: 10.0))),
                      Container(
                          child: screenSize.width < 768
                              ? Column(
                                  children: [
                                    DocumentUpload(
                                      imageType: "Selfie",
                                      imageString: _documents.selfie,
                                    ),
                                    DocumentUpload(
                                      imageType: "Proof of Residence",
                                      imageString: _documents.proofOfResidence,
                                    ),
                                    Container(width: 325)
                                  ],
                                )
                              : Row(
                                  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                                  children: [
                                      DocumentUpload(
                                        imageType: "Selfie",
                                        imageString: _documents.selfie,
                                      ),
                                      DocumentUpload(
                                        imageType: "Proof of Residence",
                                        imageString: _documents.proofOfResidence,
                                      ),
                                      Container(width: 325)
                                    ])),
                    ],
                  );
                }
              }
            });
      }
    }
    
    

    document_upload.dart

    class DocumentUpload extends StatefulWidget {
      final String imageType;
      final String imageString;
    
      const DocumentUpload({this.imageType, this.imageString});
    
      @override
      _DocumentUploadState createState() => _DocumentUploadState();
    }
    
    class _DocumentUploadState extends State<DocumentUpload> {
      String _imageType;
      String _imageString;
      bool uploadPressed = false;
      Image _imageWidget;
    
      @override
      Widget build(BuildContext context) {
        setState(() {
          _imageType = widget.imageType;
          _imageString = widget.imageString;
    
          _imageWidget =
              new Image.memory(base64Decode(_imageString), fit: BoxFit.fill);
        });
    
        return Container(
            constraints: BoxConstraints(maxWidth: 325),
            height: 200,
            decoration: BoxDecoration(
              borderRadius: BorderRadius.circular(20),
              boxShadow: [
                new BoxShadow(
                  color: AppColors.lightGrey,
                  blurRadius: 5.0,
                  offset: Offset(0.0, 3.0),
                ),
              ],
            ),
            child: Card(
                color: Colors.white,
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.circular(20.0),
                ),
                child: Column(children: <Widget>[
                  Padding(padding: EdgeInsets.only(top: 5.0)),
                  Row(
                    //ROW 1
                    children: <Widget>[
                      Expanded(
                        child: Text(
                          _imageType,
                          textAlign: TextAlign.center,
                          style: TextStyle(
                              fontSize: 18,
                              fontWeight: FontWeight.bold,
                              color: AppColors.darkBlue),
                        ),
                      ),
                    ],
                  ),
                  Row(
                    //ROW 2
                    children: <Widget>[
                      Expanded(
                        child: Container(
                            padding: EdgeInsets.only(left: 5.0, bottom: 5.0),
                            child: ClipRRect(
                              borderRadius: BorderRadius.circular(20.0),
                              child: _imageWidget,
                            )),
                      ),
                      Consumer<AccountProvider>(
                          builder: (context, provider, child) {
                        return Padding(
                            padding: EdgeInsets.all(10.0),
                            child: Column(
                                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                                children: <Widget>[
                                  Padding(
                                      padding:
                                          EdgeInsets.only(top: 5.0, bottom: 5.0),
                                      child: Icon(Icons.star,
                                          size: 20, color: AppColors.darkBlue)),
                                  Padding(
                                      padding:
                                          EdgeInsets.only(top: 5.0, bottom: 5.0),
                                      child: Text('Drag file here or',
                                          textAlign: TextAlign.center)),
                                  Padding(
                                      padding:
                                          EdgeInsets.only(top: 5.0, bottom: 5.0),
                                      child: DynamicGreyButton(
                                        title: uploadPressed
                                            ? "Uploading ..."
                                            : "Browse",
                                        onPressed: () async {
                                          FilePickerResult result =
                                              await FilePicker.platform.pickFiles(
                                                  type: FileType.custom,
                                                  allowedExtensions: [
                                                'jpg',
                                                'jpeg',
                                                'png'
                                              ]);
                                          if (result != null) {
                                            uploadPressed = true;
                                            Uint8List file =
                                                result.files.single.bytes;
                                            String fileType =
                                                result.files.single.extension;
    
                                            await provider
                                                .doUploadDocument(
                                                    _imageType, file, fileType)
                                                .then((uploadResult) {
                                              if (uploadResult == null ||
                                                  uploadResult == '') {
                                                showToast(
                                                    "Document failed to upload");
                                                return;
                                              } else {
                                                showToast("Document uploaded",
                                                    Colors.green, "#66BB6A");
                                                uploadPressed = false;
                                                _imageString = uploadResult;
                                                setState(() {});
                                              }
                                            });
                                          } else {
                                            // User canceled the picker
                                            uploadPressed = false;
                                          }
                                        },
                                      ))
                                ]));
                      })
                    ],
                  ),
                ])));
      }
    }
    

    Image Upload HTTP Call

      @override
      Future uploadDocuments(DocumentsUpload model) async {
        final response = await client.post(
            Uri.https(appConfig.baseUrl, "/api/Account/PostDocuments_Flutter"),
            body: jsonEncode(model.toJson()),
            headers: <String, String>{
              'Content-Type': 'application/json'
            });
    
        if (response.statusCode == 200) {
          var data = json.decode(response.body);
          return data;
        } else {
          return "";
        }
      }
    

    EDIT: Attached GIF of current behaviour.

    enter image description here

    I am pretty much out of ideas at this point, any help would be greatly appreciated.