'Flutter Unable to display info from asynchronous call on first page of app

UPDATE

I was able to solve my problem using the FutureBuilder widget. All the changes were in the file_listing.dart file. Here's the updated file:

start of file_listing.dart

import 'package:flutter/material.dart';
import 'dart:async';
import 'package:path/path.dart' as pathpkg;
import 'package:pythonga_expense_estimator/components/bottom_button.dart';
import 'package:pythonga_expense_estimator/services/directory_services.dart';

class FileListing extends StatefulWidget {
  FileListing();

  @override
  State<FileListing> createState() => _FileListingState();
}

class _FileListingState extends State<FileListing> {
  Map fileMap = {};

  Future<Map> getSavedEstimates() async {
    try {
      var savedEstimates = await DirectoryHelper.listOfFiles().then((resp) {
        return resp;
      });
      savedEstimates.forEach((key, value) => print(key));
      return savedEstimates;
    } catch (e) {
      throw Exception("Could Not Retrieve list of saved files");
    }
  }


  Future<List<Widget>> fileListMappedAsWidget() async {
    var fileHits =
        await getSavedEstimates(); //returns Future<Map<dynamic,dynamic>>
    List<Widget> newList = [];
    fileHits.forEach((k, v) {
      newList.add(Row(
        children: [
          Text(pathpkg.basename(v)),
          BottomButton(
              buttonTitle: 'Delete',
              onPressed: () => () {
                    setState(() {
                      k.deleteSync();
                      fileMap.remove(k);
                    });
                  })
        ],
      ));
    });
    // () => newList;
    return newList;
    // throw ("f");
  }

  @override
  

  Widget build(BuildContext context) {
    return Container(
      child: FutureBuilder(
          future: fileListMappedAsWidget(),
          // future: testRetrieve,
          builder: (BuildContext context, AsyncSnapshot<List> snapshot) {
            List<Widget> children;
            print(snapshot.connectionState);
            if (snapshot.hasData) {
              List<Widget> childStuff = [];
              for (int i = 0; i < snapshot.data!.length; i++) {
                childStuff.add(snapshot.data![i]);
              }
              children = childStuff;
            } else if (snapshot.hasError) {
              children = <Widget>[
                const Icon(Icons.error_outline, color: Colors.red, size: 60),
                Padding(
                    padding: const EdgeInsets.only(top: 16),
                    child: Text('Error: ${snapshot.error}'))
              ];
            } else {
              children = const <Widget>[
                SizedBox(
                    width: 60, height: 60, child: CircularProgressIndicator()),
                Padding(
                  padding: EdgeInsets.only(top: 16),
                  child: Text('Awaiting Result'),
                )
              ];
            }
            return Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: children,
              ),
            );
          }),
    );
  }
}

end of file_listing.dart

ORIGINAL QUESTION

I'm trying to create a page for a Flutter app. It will be the first page that the user sees when they launch the app.

The page will display a list of files, cost estimates that the user has saved previously and a button for them to create a new "estimate" which can then also be saved to a file.

The files are being saved on the device, and I'm using the path_provider and path packages to access them. Reading from and writing to the device file system are asynchronous operations.

I cannot get the list of files to appear automatically when the page loads and would like to get advice on how to do this. The results of the async function are futures of data types and I cannot get them to be just data types.

Here's what I have in terms of code:

(1) main.dart

It specifies the home page of the material app to be StartPage().

start of main.dart

import 'package:flutter/material.dart';
import 'constants/text_constants.dart';
import 'pages/start_page.dart';

void main() => runApp(PythongaVisitCostCalculator());

class PythongaVisitCostCalculator extends StatelessWidget {
  const PythongaVisitCostCalculator({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: kAppTitle1,
        theme: ThemeData(
          primarySwatch: Colors.lightBlue,
          elevatedButtonTheme: ElevatedButtonThemeData(
            style: ButtonStyle(
              shape: MaterialStateProperty.all<OutlinedBorder>(
                RoundedRectangleBorder(
                  borderRadius: BorderRadius.circular(50.0),
                ),
              ),
            ),
          ),
        ),
        home: StartPage(title: kYourTripsDescr));
  }
}

end of main.dart

(2) start_page.dart

This is the home page (stateful class) for the application, as specified in main.dart. One of the items that appears is a list of files saved by the user using the FileListing() widget, which is created in the file_listing.dart file, which follows.

start of start_page.dart

import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:pythonga_expense_estimator/components/round_icon_button.dart';
import 'package:pythonga_expense_estimator/components/file_listing.dart';
import 'package:pythonga_expense_estimator/pages/input_page.dart';
import '../constants/text_constants.dart';


class StartPage extends StatefulWidget {
  const StartPage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  State<StartPage> createState() => _StartPageState();
}

class _StartPageState extends State<StartPage> {
  Map fileList = {};
  @override

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text(kAppTitle2),
        centerTitle: true,
        automaticallyImplyLeading: false,
      ),
      body: SafeArea(
        child: Column(children: [
          Text(kYourEstimatesDescr),
          FileListing(),
          Row(
            children: [
              Text(kEstimateTripDescr),
              RoundIconButton(
                  icon: FontAwesomeIcons.plus,
                  onPressed: () {
                    Navigator.push(context,
                        MaterialPageRoute(builder: (context) {
                      return InputPage(title: 'New Estimate');
                    }));
                  })
            ],
          ),
        ]),
      ),
    );
  }
}

end of start_page.dart

(3) file_listing.dart

This stateful class returns a widget ,FileListing(), that is used in the StartPage() widget.

start of file_listing.dart

import 'package:flutter/material.dart';
import 'package:path/path.dart' as pathpkg;
import 'package:pythonga_expense_estimator/components/bottom_button.dart';
import 'package:pythonga_expense_estimator/services/directory_services.dart';

class FileListing extends StatefulWidget {
  FileListing();

  @override
  State<FileListing> createState() => _FileListingState();
}

class _FileListingState extends State<FileListing> {
  Map fileMap = {};

  Future<Map> getSavedEstimates() async {
    try {
      var savedEstimates = await DirectoryHelper.listOfFiles().then((resp) {
        return resp;
      });
      savedEstimates.forEach((key, value) => print(key));
      return savedEstimates;
    } catch (e) {
      throw Exception("Could Not Retrieve list of saved files");
    }
  }

  Future<List<Widget>> fileListMappedAsWidget() async {
    var fileHits = await getSavedEstimates();  //returns Future<Map<dynamic,dynamic>>
    List<Widget> newList = [];
    fileHits.forEach((k, v) {
      newList.add(Row(
        children: [
          Text(pathpkg.basename(v)),
          BottomButton(
              buttonTitle: 'Delete',
              onPressed: () => () {
                    setState(() {
                      k.deleteSync();
                      fileMap.remove(k);
                    });
                  })],
      ));
    });
  }

  @override
  Widget build(BuildContext context) {
    // if (fileList.isEmpty) {
    if (fileListMappedAsWidget().isEmpty) {
      return Container(
          child: Row(children: const [
        Center(child: Text("No Estimates on File")),
      ]));
    }
    return Column(
      //children: fileList,
      children: fileListMappedAsWidget(),
    );
  }
}

end of file_listing.dart

Dart Analysis gives me an error on the line: children: fileListMappedAsWidget(). The error is:

The argument type 'Future<List>' can't be assigned to the parameter type 'List'.

and an error on the line if (fileListMappedAsWidget().isEmpty) { The error is:

The getter 'isEmpty' isn't defined for the type 'Future<List>

I was expecting to that the return of fileListMappedAsWidget would be a List rather than a Future<List> once the asynchronous method completed and returned its response.

My question is "How can I transform that Future<List> to List so that my page will list the files saved by the user?

(4) directory_services.dart

Here's the code in directory_services.dart that reads the contents of the application data folder and returns a map of {File, filename text}. This code appears to be working correctly.

start of directory_services.dart

import 'dart:io' as io;
import 'package:path_provider/path_provider.dart' as pathprvdrpkg;
import 'package:path/path.dart' as pathpkg;
import '../constants/text_constants.dart';

class DirectoryHelper {
  static Future<Map<io.FileSystemEntity, String>> listOfFiles() async {
    List<io.FileSystemEntity> fileSystemEntityList =
        io.Directory(await localPath()).listSync();
    Map<io.FileSystemEntity, String> fileMap = {};
    for (int i = 0; i < fileSystemEntityList.length; i++) {
      if (pathpkg.extension(fileSystemEntityList[i].path) ==
          kEstimateFileExtension) {
        fileMap[fileSystemEntityList[i]] = fileSystemEntityList[i].path;
      }
    }
    return fileMap;
  }

  static localPath() async {
    // finds the correct local path using path_provider package
    try {
      final directory = await pathprvdrpkg.getApplicationDocumentsDirectory();
      // print("directory.path in DirectoryPath.localPath");
      return directory.path;
    } catch (e) {
      return Exception('Error: $e');
    }
  }
}

end of directory_services.dart

Thanks for your help and advice!



Solution 1:[1]

I was able to solve my problem using the FutureBuilder widget. All the changes were in the file_listing.dart file. Here's the updated file:

start of file_listing.dart

import 'package:flutter/material.dart';
import 'dart:async';
import 'package:path/path.dart' as pathpkg;
import 'package:pythonga_expense_estimator/components/bottom_button.dart';
import 'package:pythonga_expense_estimator/services/directory_services.dart';

class FileListing extends StatefulWidget {
  FileListing();

  @override
  State<FileListing> createState() => _FileListingState();
}

class _FileListingState extends State<FileListing> {
  Map fileMap = {};

  Future<Map> getSavedEstimates() async {
    try {
      var savedEstimates = await DirectoryHelper.listOfFiles().then((resp) {
        return resp;
      });
      savedEstimates.forEach((key, value) => print(key));
      return savedEstimates;
    } catch (e) {
      throw Exception("Could Not Retrieve list of saved files");
    }
  }


  Future<List<Widget>> fileListMappedAsWidget() async {
    var fileHits =
        await getSavedEstimates(); //returns Future<Map<dynamic,dynamic>>
    List<Widget> newList = [];
    fileHits.forEach((k, v) {
      newList.add(Row(
        children: [
          Text(pathpkg.basename(v)),
          BottomButton(
              buttonTitle: 'Delete',
              onPressed: () => () {
                    setState(() {
                      k.deleteSync();
                      fileMap.remove(k);
                    });
                  })
        ],
      ));
    });
    // () => newList;
    return newList;
    // throw ("f");
  }

  @override
  

  Widget build(BuildContext context) {
    return Container(
      child: FutureBuilder(
          future: fileListMappedAsWidget(),
          // future: testRetrieve,
          builder: (BuildContext context, AsyncSnapshot<List> snapshot) {
            List<Widget> children;
            print(snapshot.connectionState);
            if (snapshot.hasData) {
              List<Widget> childStuff = [];
              for (int i = 0; i < snapshot.data!.length; i++) {
                childStuff.add(snapshot.data![i]);
              }
              children = childStuff;
            } else if (snapshot.hasError) {
              children = <Widget>[
                const Icon(Icons.error_outline, color: Colors.red, size: 60),
                Padding(
                    padding: const EdgeInsets.only(top: 16),
                    child: Text('Error: ${snapshot.error}'))
              ];
            } else {
              children = const <Widget>[
                SizedBox(
                    width: 60, height: 60, child: CircularProgressIndicator()),
                Padding(
                  padding: EdgeInsets.only(top: 16),
                  child: Text('Awaiting Result'),
                )
              ];
            }
            return Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: children,
              ),
            );
          }),
    );
  }
}

end of file_listing.dart

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Carl Yerkovich