Flutter - Image.memory not refreshing after source change
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.
unselected
Updated on January 01, 2023Comments
-
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.
I am pretty much out of ideas at this point, any help would be greatly appreciated.