Returning the result of a Future in a synchronous call

802 Views Asked by At

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++;
                          });
                        }
1

There are 1 best solutions below

7
On

It impossible to return async result in sync call. Future means it completes somewhere in future.

Remove processSignature from onChanged (why send signature each time it modified?) and process it in onSaved. Then you can use async/await to send signature to server and wait for result url.

class _SomeWidgetState extends State<SomeWidget> {
  /// Form key
  final formKey = GlobalKey<FormState>();

  /// Contains signature binary daya
  Uint8List signatureValue;

  @override
  void build(...) {
    return Column(
      children: [
        FormBuilderSignaturePad(
          ...
          onSaved(Uint8List value) async {
            signatureValue = value;
          },
        FlatButton(
          child: Text('Submit'),
          onPressed: () {
            _submit();
          }
        ),
      ],
    );
  }  

  /// Submits form
  Future< void> _submit() async {
    if (formKey.currentState.validate()) {
      formKey.currentState.save(); // calls all `onSaved` for each form widgets
      // So at this point you have initialized `signatureValue`
      try {
        final signatureUrl = await processSignature(signatureValue, context); // save into database
        await doSomethingWithUrl(signatureUrl); // insert into document
      } on SomeExceptionIfRequired catch (e) {
        // Show error if occurred
        ScaffoldMessenger.of(context).showSnackbar(...);
      }
    }
  }
}