2

I have declared a function in my main.dart that would help me get the deviceId. I am trying to set this to a variable of an independent class, whose constructor is later created in one of my widget.

Function:

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
  }
}

Calling it in a class as:

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

I get below error:

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


Complete code:

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

It displays correct value for fullname on clicking submit but gives error for calling device id

MohitC
  • 4,541
  • 2
  • 34
  • 55

3 Answers3

3

await keyword can only used with async function.

only with function, but here you called await _getId() inside a class not inside a function.

try this.


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

    _getId().then((value) {
      _data.deviceId = value;
    });
    //print(_data.deviceId);
  }
Mahi
  • 1,297
  • 1
  • 14
  • 28
  • Yeah i can clearly see that. What is the solution? How do I assign its value to one of class variables? – MohitC May 17 '22 at 16:14
  • I have updated my answer. check it out, but still don't know that you are looking for. – Mahi May 17 '22 at 16:30
  • what is the ideal place to keep this? I have placed it inside the RegisterPageState – MohitC May 17 '22 at 16:42
  • initState method only declared inside of State class. So RegisterPageState is fine. – Mahi May 17 '22 at 16:43
  • what if I want to be accessible in my StatefulWidget? Because I would later be creating different states based on conditions – MohitC May 17 '22 at 16:58
  • I am not a native english speaker. So could you please clarify the above sentence. – Mahi May 17 '22 at 17:00
  • Is there a way to call this code inside `class RegisterPage extends StatefulWidget` class. I want to create my state based on value from deviceID – MohitC May 17 '22 at 17:02
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/244816/discussion-between-mahi-and-mohitc). – Mahi May 17 '22 at 17:23
1

You want to initialize deviceId from the result of an asynchronous operation. Some options:

  • Declare Future<String?> deviceId = _getId(); and require all callers to await it when accessing it.

  • Initialize it later:

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

    Note that with this approach, callers will not be notified when deviceId is eventually initialized.

  • Make callers initialize your class asynchronously:

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

Somewhat related: How to await future in global main dart of flutter?

jamesdlin
  • 81,374
  • 13
  • 159
  • 204
  • I like the second way of calling _getId() with then inside `_Data`'s constructor. Would `_data.deviceId` be set as soon as `_Data` class object is instantiated? – MohitC May 17 '22 at 17:01
  • @MohitC `_data.deviceId` will be initialized to `null` immediately. Since the desired value is obtained asynchronously, it will not be set to that value until some indeterminate time in the future. And, as I said, there would be no way for callers to know when it eventually does get set. It is not an approach that would be appropriate for all cases. – jamesdlin May 17 '22 at 17:13
  • what does the line `_Data._(this.deviceId);` do? – MohitC May 17 '22 at 17:19
  • @MohitC It's a named constructor (whose name is `_`, which makes it private). See https://stackoverflow.com/a/57816373/. – jamesdlin May 17 '22 at 17:22
  • 1
    The second approach is likely the worst approach as it locks you into having a nullable deviceId as well as your data model being uninitialized after the constructor executes, as well as the already listed pitfall of having no notification when it's done initializing. – void void May 17 '22 at 17:29
1

Many of the things you are doing would be considered bad practice. I will lay out a few changes and finally answer your question. Define your _Data class as follows:

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

This way your data model is not hardcoded. You may also consider making the fields final unless you have a good reason not to.

Declare the data state variable:

_Data? _data;

Define the initData method inside your state:

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

Finally override initState:

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

Inside your build method you can pay attention to whether or not the field is null (checking if it has been initialized). There's a bunch of room for improvement here once you get it running. First you should not make the getId method top level, it would be better to have it inside a service, and you should consider using a future builder instead of having the _data state attribute nullable.

void void
  • 1,080
  • 3
  • 8
  • thank you for suggestions, I will follow through, still very new to flutter. What if I want the deviceId initially itself, and based on value of deviceId i want to create different states. Can this code be called in Stateful widget class? – MohitC May 17 '22 at 17:06
  • You can obtain the deviceId in a similar way as data was obtained in my example. You can put your logic which depends on deviceId inside the initData function. Note that the deviceId is constant for one device and there's no way to predict its value for a random device. Also, yes, this code can (and is meant to be) put inside the state of your StatefulWidget, not in the StatefulWidget, note the distinction. – void void May 17 '22 at 17:22
  • I am trying to get a device id and will check whether that device id is registered with my server or not via a REST API. If its registered, then I want to create a different state. If its not registered, then I want to create the State of shown widget (registration page). Any suggestions? – MohitC May 17 '22 at 17:32
  • 1
    I would first separate that into a bloc or a service. Considering you want to do rather complex initialization logic, the second approach of jamesdlin which you mentioned liking the most will be the worst possible for you as the constructor of a data model is a horrible place for such logic. His third approach is also bad practice once you add that logic, as the model should not also be concerned with making http requests. You can await the server's response inside the initData method and place your logic there, and later separate it into the bloc or service. Consider asking a new question. – void void May 17 '22 at 17:37
  • Asked another question for it https://stackoverflow.com/questions/72310005/flutter-how-to-wait-for-async-call-to-finish-in-initstate – MohitC May 19 '22 at 19:30