Возвращает результат Future при синхронном вызове

#flutter #dart #flutter-form-builder

#flutter #dart #flutter-конструктор форм

Вопрос:

Я использую панель подписи в пакете 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 означает, что он завершается где-то в будущем.

Удалить processSignature из onChanged (зачем отправлять подпись каждый раз при ее изменении?) и обработайте его 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(...);
      }
    }
  }
}
 

Комментарии:

1. Я добавил тег async в onsaved и включил ожидание, но перед завершением он запускает valuetransfor

2. Вы должны поместить код processSignature в обратный вызов, который находится final в последовательности вызовов.

3. Я переместил processsignature в valuetransformer, и теперь он возвращает строку «Future<string>», а не значение . Ожидаются все вызовы

4. Поместите свой промежуточный код здесь или где-нибудь.

5. Реорганизуйте ваш processSignature , чтобы он возвращался Future<String> , затем вызовите его в valueTransformer обратном вызове. Смотрите Фрагмент в моем ответе.