Flutter — вызов асинхронной функции в независимом классе

Я объявил функцию в своем main.dart, которая поможет мне получить deviceId. Я пытаюсь установить это в переменную независимого класса, конструктор которого позже создается в одном из моих виджетов.

Функция:

Future<String?> _getId() async {
  var deviceInfo = DeviceInfoPlugin();
  if (Platform.isIOS) { // import 'dart:io'
    var iosDeviceInfo = await deviceInfo.iosInfo;
    return iosDeviceInfo.identifierForVendor; // Unique ID on iOS
  } else {
    var androidDeviceInfo = await deviceInfo.androidInfo;
    return androidDeviceInfo.androidId; // Unique ID on Android
  }
}

Вызов его в классе как:

class _Data {
  String? fullname = '';
  String doctor = 's';
  String? deviceId = await _getId();
}

Я получаю ошибку ниже:

The await expression can only be used in an async function.


Полный код:

import 'dart:io';
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:device_info_plus/device_info_plus.dart';

Future<String?> _getId() async {
  var deviceInfo = DeviceInfoPlugin();
  if (Platform.isIOS) { // import 'dart:io'
    var iosDeviceInfo = await deviceInfo.iosInfo;
    return iosDeviceInfo.identifierForVendor; // Unique ID on iOS
  } else {
    var androidDeviceInfo = await deviceInfo.androidInfo;
    return androidDeviceInfo.androidId; // Unique ID on Android
  }
}

void main() => runApp(const MaterialApp(
  title: 'Manager',
  home: RegisterPage(),
));


class RegisterPage extends StatefulWidget {
  const RegisterPage ({Key? key}) : super(key: key);
  @override
  State<StatefulWidget> createState() => _RegisterPageState();
}

class _Data {
  String? fullname = '';
  String doctor = 's';
  String? deviceId = await _getId();
}

class _RegisterPageState extends State<RegisterPage> {
  final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
  _Data _data = _Data();

  void submit() {
      _formKey.currentState!.save();
      print('Printing the login data.');
      print('Fullname: ${_data.fullname}');
      print('Doctor: ${_data.doctor}');
      print('Device Id: ${_data.getDeviceId()}');
  }

  @override
  Widget build(BuildContext context) {
    final Size screenSize = MediaQuery.of(context).size;

    return Scaffold(
      appBar: AppBar(
        title: const Text('Manager'),
      ),
      body: Container(
          padding: const EdgeInsets.all(20.0),
          child: Form(
            key: _formKey,
            child: ListView(
              children: <Widget>[
                TextFormField(
                    keyboardType: TextInputType.text,
                    decoration: const InputDecoration(
                        hintText: 'John Doe',
                        labelText: 'Full name'
                    ),
                    onSaved: (String? value) {
                      _data.fullname = value;
                    }
                ),
                Container(
                  width: screenSize.width,
                  margin: const EdgeInsets.only(
                      top: 20.0
                  ),
                  child: ElevatedButton(
                    onPressed: submit,
                    child: const Text(
                      'Register',
                      style: TextStyle(
                          color: Colors.white
                      ),
                    ),
                  ),
                )
              ],
            ),
          )
      ),
    );
  }
}

Он отображает правильное значение для полного имени при нажатии кнопки «Отправить», но выдает ошибку для вызова идентификатора устройства.

Формы c голосовым вводом в React с помощью Speechly
Формы c голосовым вводом в React с помощью Speechly
Пытались ли вы когда-нибудь заполнить веб-форму в области электронной коммерции, которая требует много кликов и выбора? Вас попросят заполнить дату,...
Стилизация и валидация html-формы без использования JavaScript (только HTML/CSS)
Стилизация и валидация html-формы без использования JavaScript (только HTML/CSS)
Будучи разработчиком веб-приложений, легко впасть в заблуждение, считая, что приложение без JavaScript не имеет права на жизнь. Нам становится удобно...
Flatpickr: простой модуль календаря для вашего приложения на React
Flatpickr: простой модуль календаря для вашего приложения на React
Если вы ищете пакет для быстрой интеграции календаря с выбором даты в ваше приложения, то библиотека Flatpickr отлично справится с этой задачей....
В чем разница между Promise и Observable?
В чем разница между Promise и Observable?
Разберитесь в этом вопросе, и вы значительно повысите уровень своей компетенции.
Что такое cURL в PHP? Встроенные функции и пример GET запроса
Что такое cURL в PHP? Встроенные функции и пример GET запроса
Клиент для URL-адресов, cURL, позволяет взаимодействовать с множеством различных серверов по множеству различных протоколов с синтаксисом URL.
Четыре эффективных способа центрирования блочных элементов в CSS
Четыре эффективных способа центрирования блочных элементов в CSS
У каждого из нас бывали случаи, когда нам нужно отцентрировать блочный элемент, но мы не знаем, как это сделать. Даже если мы реализуем какой-то...
1
0
45
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Ключевое слово Ждите может использоваться только с функцией асинхронный.

только с функцией, но здесь вы вызвали await _getId() внутри класса, а не внутри функции.

попробуй это.


  @override
  void initState() {
    // TODO: implement initState
    super.initState();

    _getId().then((value) {
      _data.deviceId = value;
    });
    //print(_data.deviceId);
  }

Да, я это ясно вижу. Каково решение? Как мне присвоить его значение одной из переменных класса?

MohitC 17.05.2022 18:14

Я обновил свой ответ. проверить его, но все еще не знаю, что вы ищете.

Mahi 17.05.2022 18:30

какое идеальное место для хранения этого? Я поместил его в RegisterPageState

MohitC 17.05.2022 18:42

Метод initState объявлен только внутри класса State. Так что RegisterPageState в порядке.

Mahi 17.05.2022 18:43

что, если я хочу быть доступным в своем StatefulWidget? Потому что позже я буду создавать разные состояния в зависимости от условий.

MohitC 17.05.2022 18:58

Я не носитель английского языка. Итак, не могли бы вы пояснить приведенную выше фразу.

Mahi 17.05.2022 19:00

Есть ли способ вызвать этот код внутри класса class RegisterPage extends StatefulWidget. Я хочу создать свое состояние на основе значения из deviceID

MohitC 17.05.2022 19:02

Давайте продолжить обсуждение в чате.

Mahi 17.05.2022 19:23

Вы хотите инициализировать deviceId из результата асинхронной операции. Некоторые варианты:

  • Объявите Future<String?> deviceId = _getId(); и потребуйте от всех вызывающих абонентов await при доступе к нему.

  • Инициализируйте его позже:

    class _Data {
      String? deviceId;
    
      _Data() {
        _getId().then((id) => deviceId = id);
      }
    }
    

    Обратите внимание, что при таком подходе вызывающие абоненты не будут уведомлены, когда deviceId в конечном итоге инициализируется.

  • Заставьте вызывающие стороны инициализировать ваш класс асинхронно:

    class _Data {
      String? deviceId;
    
      _Data._(this.deviceId);
    
      static Future<_Data> newData() async {
        var id = await _getId();
        return _Data._(id);
      }
    }
    

Отчасти связано: Как ждать будущего в глобальном главном дротике флаттера?

Мне нравится второй способ вызова _getId() с помощью конструктора _Data. Будет ли _data.deviceId установлен, как только будет создан экземпляр объекта класса _Data?

MohitC 17.05.2022 19:01

@MohitC _data.deviceId будет немедленно инициализирован в null. Поскольку желаемое значение получается асинхронно, оно не будет установлено на это значение до неопределенного времени в будущем. И, как я уже сказал, вызывающие абоненты не смогут узнать, когда он в конечном итоге будет установлен. Это не тот подход, который подходит для всех случаев.

jamesdlin 17.05.2022 19:13

что делает линия _Data._(this.deviceId);?

MohitC 17.05.2022 19:19

@MohitC Это именованный конструктор (чье имя _, что делает его закрытым). См. stackoverflow.com/a/57816373.

jamesdlin 17.05.2022 19:22

Второй подход, вероятно, является наихудшим подходом, поскольку он блокирует вас наличием обнуляемого идентификатора устройства, а также вашей модели данных, которая не инициализируется после выполнения конструктора, а также уже перечисленной ловушкой отсутствия уведомления, когда она выполнена инициализация.

void void 17.05.2022 19:29

Многие вещи, которые вы делаете, будут считаться плохой практикой. Выложу несколько изменений и наконец отвечу на ваш вопрос. Определите свой класс _Data следующим образом:

class _Data {
  String? fullname; 
  String doctor; 
  String? deviceId; 
  _Data(this.fullname, this.doctor, this.deviceId);
}

Таким образом, ваша модель данных не жестко запрограммирована. Вы также можете сделать поля окончательными, если у вас нет веских причин этого не делать.

Объявите переменную состояния данных:

_Data? _data;

Определите метод initData внутри вашего состояния:

  Future<void> initData() async {
    var dat = _Data("fn", "doc", await _getId());
    setState(() {_data = dat; });
  }

Наконец переопределить initState:

  @override
  void initState() {
    super.initState();
    initData();
  }

Внутри вашего метода сборки вы можете обратить внимание на то, является ли поле нулевым (проверив, было ли оно инициализировано). Здесь есть много возможностей для улучшения, как только вы запустите его. Во-первых, вы не должны делать метод getId верхнего уровня, было бы лучше иметь его внутри службы, и вам следует рассмотреть возможность использования будущего компоновщика вместо того, чтобы атрибут состояния _data обнулялся.

спасибо за предложения, я буду следовать, все еще очень новичок в флаттере. Что, если я хочу, чтобы изначально был сам deviceId, и на основе значения deviceId я хочу создать разные состояния. Можно ли вызвать этот код в классе виджетов Stateful?

MohitC 17.05.2022 19:06

Вы можете получить deviceId аналогично получению данных в моем примере. Вы можете поместить свою логику, которая зависит от deviceId, внутри функции initData. Обратите внимание, что deviceId является постоянным для одного устройства, и невозможно предсказать его значение для случайного устройства. Кроме того, да, этот код может (и должен быть) помещен в состояние вашего StatefulWidget, а не в StatefulWidget, обратите внимание на различие.

void void 17.05.2022 19:22

Я пытаюсь получить идентификатор устройства и проверю, зарегистрирован ли этот идентификатор устройства на моем сервере или нет через REST API. Если он зарегистрирован, я хочу создать другое состояние. Если он не зарегистрирован, я хочу создать состояние отображаемого виджета (страница регистрации). Какие-либо предложения?

MohitC 17.05.2022 19:32

Сначала я бы выделил это в блок или службу. Учитывая, что вы хотите сделать довольно сложную логику инициализации, второй подход jamesdlin, который вы упомянули больше всего, будет для вас худшим из возможных, поскольку конструктор модели данных - ужасное место для такой логики. Его третий подход также является плохой практикой, если вы добавите эту логику, поскольку модель не должна также заниматься выполнением HTTP-запросов. Вы можете дождаться ответа сервера внутри метода initData и поместить туда свою логику, а потом выделить ее в блок или сервис. Попробуйте задать новый вопрос.

void void 17.05.2022 19:37

Другие вопросы по теме