在同步调用中返回 Future 的结果

Posted

技术标签:

【中文标题】在同步调用中返回 Future 的结果【英文标题】:Returning the result of a Future in a synchronous call 【发布时间】:2020-12-17 18:50:29 【问题描述】:

我正在使用 FlutterFormBuilder 包中的签名板来捕获签名(FlutterFormBuilderSignaturePad),将其上传到 firebase 存储,然后将下载 url 返回到应用程序以存储在 firestore 中的文档中。

我面临的问题是上传需要几秒钟才能完成(在连接不良时可能会更长)。我正在尝试等待电话,以便我可以将下载 url 传递给数据库,但它忽略了我的尝试。

我试过了:

使用 .then().whenComplete() 链接我的调用,但 valueTransformer 仍然返回一个空白字符串。 将异步添加到“valueTransformer”、“onSaved”和“onChange”方法并等待调用 在上述三个方法之间移动了保存签名的逻辑,以便给 uimage 上传时间 onChanges 触发很多,所以我引入了一个 _processing 标志,因此它没有多次保存图像并导致数据库超时。 onChange 在几秒钟后返回一个 url,但我不能保证签名是完整的。

所以我的小部件看起来像这样:

  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;
                    );
                  );
                
              
            
          ,
        )

处理上传和设置状态的未来

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;
    );
  

以下更改后的更新

进程签名:

 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;
  

签名板小部件:

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) ,
        );

我看到“未来”的方法

_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】:

同步调用无法返回异步结果。 Future 表示它在未来的某个地方完成。

onChanged中删除processSignature(为什么每次修改都发送签名?)并在onSaved中处理。然后你可以使用 async/await 向服务器发送签名并等待结果 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(...);
      
    
  

【讨论】:

我将 async 标签添加到 onsaved 并包含 await 但它在完成之前运行 valuetransfor 你应该将processSignature的代码放在一个回调中,即final在调用序列中。 我已将 processsignature 移至 valuetransformer,现在它返回一个字符串 "Future" 而不是 value 。所有电话都在等待中 把你的中间代码放在这里或其他地方。 重构你的processSignature,让它返回Future&lt;String&gt;,然后在valueTransformer回调中调用它。请参阅我的答案中的 sn-p。

以上是关于在同步调用中返回 Future 的结果的主要内容,如果未能解决你的问题,请参考以下文章

JAVA多线程模式-Future

不同服务调用方式的比较

Future配合线程池进行多线程任务并返回结果

同步调用,异步回调和 Future 模式

RPC调用基础及Java学习笔记

多线程(二十二异步执行-Futrue模式)