Return async value to initState()

Viewed 1397

I've the below code that is working fine, reading the csv data from url and printing the output:

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

void fetchUserData() async {
  final request = await HttpClient().getUrl(Uri.parse(
      'https://docs.google.com/spreadsheets/d/e/2PACX-1vQvf9tp4-fETDJbC-HRmRKvVFAXEAGO4lrYPpVeiYkB6nqqXdSs3CjX0eBMvjIoEeX9_qU6K2RWmzVk/pub?gid=0&single=true&output=csv'));
  final response = await request.close();
  List<List<dynamic>> rowsAsListOfValues;
  await for (final csvString in response.transform(const Utf8Decoder())) {
    rowsAsListOfValues =
        const CsvToListConverter().convert(csvString);
  }
  print(rowsAsListOfValues);
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  void initState() {
    super.initState();
    fetchUserData();
  }

  @override
  Widget build(BuildContext context) { // ... // }
}

Instead of getting the output printed, I need it to be returned into a variable, which I can display in y widget, I tried to do it as below:

Future<List<List<dynamic>>> fetchUserData() async {   /// change
  final request = await HttpClient().getUrl(Uri.parse(
      'https://docs.google.com/spreadsheets/d/e/2PACX-1vQvf9tp4-fETDJbC-HRmRKvVFAXEAGO4lrYPpVeiYkB6nqqXdSs3CjX0eBMvjIoEeX9_qU6K2RWmzVk/pub?gid=0&single=true&output=csv'));
  final response = await request.close();
  List<List<dynamic>> rowsAsListOfValues;
  await for (final csvString in response.transform(const Utf8Decoder())) {
    rowsAsListOfValues =
        const CsvToListConverter().convert(csvString);
  }

  return rowsAsListOfValues;    /// change
}

class _MyHomePageState extends State<MyHomePage> {
  var rowsAsListOfValues;  /// new

  @override
  void initState() {
    super.initState();
    rowsAsListOfValues = fetchUserData();    /// new
    print(rowsAsListOfValues);   /// new
  }

  @override
  Widget build(BuildContext context) { // ... // }
}

But I got the output as I/flutter ( 7505): Instance of 'Future<List<List<dynamic>>>'

How can I fix it?

enter image description here

4 Answers

You need to switch from initState to didChangeDependency in this case. Because you need to await some process and you cant wait in initState. However you can wait like this

@override
void didChangeDependencies() async {
  super.didChangeDependencies();
  rowsAsListOfValues = await fetchUserData();
  super.setState(() {}); // to update widget data
  /// new
  print(rowsAsListOfValues);
}

And this is the result

I/flutter (24313): [[vranches], [Dammam, 2], [Khobar, 3]]

You can wrap your code with Future.delayed() as given below.

 @override
  void initState() {
    super.initState();
    Future.delayed(Duration.zero,()async{
       rowsAsListOfValues =await fetchUserData();   
       setState(() {});
       print(rowsAsListOfValues);   // this return correct value
    });
   print(rowsAsListOfValues);  // this return null
  }

Full Code

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

void main() {
  runApp(App());
}

class App extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: HomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class HomePage extends StatefulWidget {
  HomePage({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _AppState createState() => _AppState();
}

Future<List<List<dynamic>>> fetchUserData() async {
  final request = await HttpClient().getUrl(Uri.parse(
      'https://docs.google.com/spreadsheets/d/e/2PACX-1vQvf9tp4-fETDJbC-HRmRKvVFAXEAGO4lrYPpVeiYkB6nqqXdSs3CjX0eBMvjIoEeX9_qU6K2RWmzVk/pub?gid=0&single=true&output=csv'));
  final response = await request.close();
  List<List<dynamic>> rowsAsListOfValues;
  await for (final csvString in response.transform(const Utf8Decoder())) {
    rowsAsListOfValues =
        const CsvToListConverter().convert(csvString);
  }
  return rowsAsListOfValues;
}

class _AppState extends State<HomePage> {
  var rowsAsListOfValues;
  @override
  void initState() {
    super.initState();
    Future.delayed(Duration.zero, () async {
      rowsAsListOfValues = await fetchUserData();
      setState(() {});
      print(rowsAsListOfValues);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              '$rowsAsListOfValues',
            ),
          ],
        ),
      ),
    );
  }
}

The initState method is synchronous, and does not support async. I recommend the use of FutureBuilder, but you can also move the code to an async function.

FutureBuilder

import 'package:flutter/material.dart' show
  Widget, FutureBuilder, AsyncSnapshot
;

class _MyHomePageState extends State<MyHomePage> {

  static Future<void> fetchUserData() {
    return Future().delayed(
      Duration(seconds: 10),
      () => 'loaded'
    );
  }

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

  @override
  Widget build(BuildContext context) {

    return FutureBuilder(
      future: Future.wait([
        fetchUserData()
      ]),

      builder: (
        BuildContext context,
        AsyncSnapshot snapshot
      ) {
        if (snapshot.hasData) {
          return Text(snapshot.data);
        }
        return Text('loading...');
      }
    );
  }
}

Async function

@override
void initState () {
  super.initState();
  (() async {
    rowsAsListOfValues = await fetchUserData();
    print(rowsAsListOfValues);
  })();
}

OR

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

void initLoad() async {
  rowsAsListOfValues = await fetchUserData();
  print(rowsAsListOfValues);
}

I feel more relaxed when using then() with async functions. You can try this:

  

    fetchUserData().then((value) {
       setState(() {
        rowsAsListOfValues = value;
       });
    });   


Or you can use await like this.

  @override
  void initState() async {
    super.initState();

    rowsAsListOfValues = await fetchUserData();
                      
    print(rowsAsListOfValues);  
  }
Related