Reputation: 109
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:
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.
Upvotes: 3
Views: 2574
Reputation: 109
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.
Upvotes: 1