I'm using the signature pad in the FlutterFormBuilder package to capture a signature (FlutterFormBuilderSignaturePad), upload it to firebase storage and then return the download url to the application for storage in a document in firestore.
The problem im facing is that the upload takes a couple of seconds to complete (possibly longer on poor connection). I'm trying to await the call so i can pass the download url to the database however its ignoring my attempts.
Ive tried :
- Chaining my calls using the
.then()
and.whenComplete()
but valueTransformer still returns a blank string. - added async to the "valueTransformer", "onSaved" and "onChange" methods and awaited the calls
- moved the logic to save the signature between the three methods above in order to give the uimage time to upload
- onChanges fires a lot so i introduced a _processing flag so it didnt save the image multiple times and cause database timeouts. onChange was returning a url given a few seconds however i couldn't guarantee the signature was complete.
So my widget looking like this:
final SignatureController _controller = SignatureController(
penStrokeWidth: 5,
penColor: Colors.red,
exportBackgroundColor: Colors.blue,
);
String _signature;
File _signatureFile;
bool _processing;
return FormBuilderSignaturePad(
name: 'signature',
controller: _controller,
decoration: InputDecoration(labelText: "signature"),
initialValue: _signatureFile?.readAsBytesSync(),
onSaved: (newValue) async {
//called on save just before valueTransformer
await processSignature(newValue, context);
},
valueTransformer: (value) {
//called when the form is saved
return _signature;
},
onChanged: (value) {
//called frequently as the signature changes
if (_controller.isNotEmpty) {
if (_controller.value.length > 19) {
if (!_processing) {
processSignature(value, context).then((value) {
setState(() {
_processing = false;
});
});
}
}
}
},
)
My future for processing the upload and setting the state
Future<void> processSignature(dynamic signature, BuildContext context) async {
setState(() {
_processing = true;
});
var bytes = await _controller.toPngBytes();
final documentDirectory = await getApplicationDocumentsDirectory();
final file =
File(join(documentDirectory.path, 'signature${database.uid}.png'));
file.writeAsBytesSync(bytes);
var url = await storage.uploadImage(
context: context,
imageToUpload: file,
title: "signature${database.uid}.png",
requestId: database.currentRequest.id);
setState(() {
_signature = url.imageUrl;
_signatureFile = file;
});
}
UPDATES AFTER CHANGES BELOW
Process Signature:
Future<String> processSignature(
dynamic signature, BuildContext context) async {
var bytes = await _controller.toPngBytes();
final documentDirectory = await getApplicationDocumentsDirectory();
final file =
File(join(documentDirectory.path, 'signature${database.uid}.png'));
file.writeAsBytesSync(bytes);
var url = await storage.uploadImage(
context: context,
imageToUpload: file,
title: "signature${database.uid}.png",
requestId: database.currentRequest.id);
return url.imageUrl;
}
Signature Pad Widget:
return FormBuilderSignaturePad(
name: 'signature',
controller: _controller,
decoration: InputDecoration(labelText: "signature"),
initialValue: _signatureFile?.readAsBytesSync(),
onSaved: (newValue) async {},
valueTransformer: (value) async {
final savedUrl = await processSignature(value, context);
return savedUrl;
},
onChanged: (value) {},
);
Method where im seeing the "Future"
_formKey[_currentStep].currentState.save();
if (_formKey[_currentStep].currentState.validate()) {
//request from the database
var request = firestoreDatabase.currentRequest;
//this should be the url however its returning as
//"Future<String>"
var value = _formKey[_currentStep].currentState.value;
request.questions[_currentStep].result =
jsonEncode(_formKey[_currentStep].currentState.value);
request.questions[_currentStep].completedOn =
Timestamp.fromDate(new DateTime.now());
firestoreDatabase.updateRequest(request).then((value) {
if (_currentStep == _totalSteps - 1) {
//pop the screen
Navigator.pop(context);
} else {
setState(() {
_currentStep++;
});
}
It impossible to return async result in sync call.
Future
means it completes somewhere in future.Remove
processSignature
fromonChanged
(why send signature each time it modified?) and process it inonSaved
. Then you can use async/await to send signature to server and wait for result url.