89

I'm new to Flutter and Dart, and I'm trying to build a Flutter app which displays the device information on the screen. For this purpose I'm trying to use this library: 'device_info' from here: https://pub.dartlang.org/packages/device_info#-readme-tab-

In the 'build' method of the MyApp class, I am trying to instantiate the object from 'device_info' package and call a property which happens to be an async property. Since the default build method is not asynchronous, how do I call this property in the build method? Following is my code:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin();
    AndroidDeviceInfo androidDeviceInfo = await deviceInfoPlugin.androidInfo;
    return MaterialApp(
      title: 'My Device Info',
      home: Scaffold(
        appBar: AppBar(
          title: Text('My Device Info'),
        ),
        body: Center(
          child: Text('Device model:' + 'Moto'),
        ),
      ),
    );
  }
}

3 Answers 3

125

I would suggest you to use a FutureBuilder:

import 'package:flutter/material.dart';

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  // save in the state for caching!
  DeviceInfoPlugin _deviceInfoPlugin;

  @override
  void initState() {
    super.initState();
    _deviceInfoPlugin = DeviceInfoPlugin();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'My Device Info',
      home: Scaffold(
        appBar: AppBar(
          title: Text('My Device Info'),
        ),
        body: FutureBuilder<AndroidDeviceInfo>(
          future: _deviceInfoPlugin.androidInfo,
          builder: (BuildContext context, AsyncSnapshot<AndroidDeviceInfo> snapshot) {
            if (!snapshot.hasData) {
              // while data is loading:
              return Center(
                child: CircularProgressIndicator(),
              );
            } else {
              // data loaded:
              final androidDeviceInfo = snapshot.data;
              return Center(
                child: Text('Android version: ${androidDeviceInfo.version}'),
              );
            }
          },
        ),
      ),
    );
  }
}

In general, when using FutureBuilder or Futures, you have to keep in mind that the enclosing widget can be rebuilt at any time (e.g. because the device was rotated, or the keyboard is shown). That means the build method is called again.

In this particular case it's not a problem because the plugin caches the value and returns it instantly, but in general you should NEVER create or get a Future inside of the build method. Instead, do it from initState or a click event handler:

import 'package:flutter/material.dart';

class FooWidget extends StatefulWidget {
  @override
  _FooWidgetState createState() => _FooWidgetState();
}

class _FooWidgetState extends State<FooWidget> {
  Future<int> _bar;

  @override
  void initState() {
    super.initState();
    _bar = doSomeLongRunningCalculation();
  }

  void _retry() {
    setState(() {
      _bar = doSomeLongRunningCalculation();
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        FutureBuilder<int>(
          future: _bar,
          builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
            if (snapshot.hasData) {
              return Text('The answer to everything is ${snapshot.data}');
            } else {
              return Text('Calculating answer...');
            }
          },
        ),
        RaisedButton(
          onPressed: _retry,
          child: Text('Retry'),
        )
      ],
    );
  }
}

Future<int> doSomeLongRunningCalculation() async {
  await Future.delayed(Duration(seconds: 5)); // wait 5 sec
  return 42;
}
Sign up to request clarification or add additional context in comments.

3 Comments

thank you boformer, I was able to build the app with the code changes you suggested. but when i run the app on emulator, the value of ${androidDeviceInfo.version} prints the text 'Instance of AndroidBuildVersion'. Am I missing anything here?
never mind, i fixed the above issue by accessing properties of the AndroidBuildVersion object.
This response was very helpful, however, I found the sentence "you should NEVER create or get a Future inside of the build method" very confusing, as you still have to get a Future inside the FutureBuilder. I think what you were trying to say is that you should not create the Future above the return-statement of the build method, as it would be recreated with every change of the UI, even after the Future has already been completed once. Is that right?
38

build() expects a sync result, so using async/await is inappropriate in build().

Either use FutureBuilder where you return a placeholder Container() while the async result is not yet available or move the async code to initState() and update the state using setState when the value becomes available to have the build be executed again.

5 Comments

initState() + setState is the best alternative for FutureBuilder in cases parent needs data from other building object to build a child object.
The "nice" thing is that neither initState nor setState can be async...
You can call a method from initState that is async though. Just move the code you tried to execute to another function and call it from initState. There is no need to await it in initState(). Just call setState(...) when your async execution is completed.
Great answer, however some source code example would make it even greater :) (for the answer and the above comment with initState)
Here is the sample code: @override void initState() { super.initState(); _audioPlayerService.isSoundTurnedOn.then((value) { setState(() { isSoundSwitched = value; }); }); } However, in my opinion, using FutureBuilder is better than handling in initState.
4

You can achieve it by use of await/async (By @Günter Zöchbauer said not need to import lib as per latest version of dart.)

and call functions out of build method.

  _getAndroidDeviceInfo() async{
    AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
    print(androidInfo.device);
  }

   _get_iOS_DeviceInfo() async{
    IosDeviceInfo iosDeviceInfo = await deviceInfo.iosInfo;
    print(iosDeviceInfo.model);
  }

5 Comments

You don't have to import dart:async for that. There are some classes like StreamSubscription, Completer, ... where the import is required. In previous versions it was required when you used the types Stream and Future as well, but in latest Dart these types are exported from dart:core and are therefore available automatically.
@GünterZöchbauer - Thanks for clear. I will update the answer
what if you have to return to build method?? async method should always have to return Future<?> object
This is not a correct answer matching with the question. See the problem description in the question again. The problem is How to call an aync function inside a stateless widget.
Answer from @boformer is correct even though using Stateless widget in this case is not possible.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.