Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Flutter: Correct approach to get value from Future

I have a function which returns images directory path, it performs some additional check like if directory exists or not, then it behaves accordingly.

Here is my code:

Future<String> getImagesPath() async {
   final Directory appDir = await getApplicationDocumentsDirectory();
   final String appDirPath = appDir.path;

   final String imgPath = appDirPath + '/data/images';

   final imgDir = new Directory(imgPath);

   bool dirExists = await imgDir.exists();

   if (!dirExists) {
        await new Directory(imgPath).create(recursive: true);
   }

   return imgPath;

}

This piece of code works as expected, but I'm having issue in getting value from Future.

Case Scenario: I have data stored in local database and trying to display it, inside listview. I'm using FutureBuilder, as explained in this answer. Each data row has an image connected with it (connected means, the image name is stored in db).

Inside Widget build method, I have this code:

@override
Widget build(BuildContext context) {
 getImagesPath().then((path){
     imagesPath = path;
     print(imagesPath); //prints correct path
   });
 print(imagesPath);   //prints null

return Scaffold(
    //removed
    body: FutureBuilder<List>(
        future: databaseHelper.getList(),
        initialData: List(),
        builder: (context, snapshot) {
          return snapshot.hasData
              ? ListView.builder(
                  itemCount: snapshot.data.length,
                  itemBuilder: (_, int position) {
                    final item = snapshot.data[position];
                    final image = "$imagesPath/${item.row[0]}.jpg";
                    return Card(
                        child: ListTile(
                      leading: Image.asset(image),
                      title: Text(item.row[1]),
                      subtitle: Text(item.row[2]),
                      trailing: Icon(Icons.launch),
                    ));
                  })
              : Center(
                  child: CircularProgressIndicator(),
                );
        }));

}

Shifting return Scaffold(.....) inside .then doesn't work. Because widget build returns nothing.

The other option I found is async/await but at the end, same problem, code available below:

_getImagesPath() async {
    return await imgPath();
}

Calling _getImagesPath() returns Future, instead of actual data.

I beleive there is very small logical mistake, but unable to find it myself.

like image 512
Alena Avatar asked Sep 21 '19 08:09

Alena


People also ask

How do you get the future function value in flutter?

How do you return a value from a function in flutter? There are two ways of getting a value from a Future . Using async-await : int _value = 0; // Instance variable. void func() async { int value = await _yourFuture(); // Await on your future.

How do you get the object from an instance of the future in flutter?

get() method returns a Future object: Calling a function that returns a Future, will not block your code, that's why that function is called asynchronous. Instead, it will immediately return a Future object, which is at first uncompleted. Future<T> means that the result of the asynchronous operation will be of type T .

How do you return a list from a Future function in flutter?

It could look like following: Future<List<User>> usersFuture = getUsers('DcofOiHWcjbjD0i18miW'); Create the future as member variable so you only fetch once (in case the method initiates a new future each time you call it). And then use it inside a FutureBuilder.


2 Answers

I see that you have to build your widget from the output of two futures. You can either use two FutureBuilders or have a helper method to combine them into one simplified code unit.

Also, never compute/invoke async function from build function. It has to be initialized before (either in constructor or initState method), otherwise the widget might end up repainting itself forever.

Coming to the solution: to simplify code, it is better to combine both future outputs into a single class as in the example below:

Data required for build method:


class DataRequiredForBuild {
  String imagesPath;
  List items;

  DataRequiredForBuild({
    this.imagesPath,
    this.items,
  });
}

Function to fetch all required data:


Future<DataRequiredForBuild> _fetchAllData() async {
  return DataRequiredForBuild(
    imagesPath: await getImagesPath(),
    items: await databaseHelperGetList(),
  );
}

Now putting everything together in Widget:

Future<DataRequiredForBuild> _dataRequiredForBuild;

@override
void initState() {
  super.initState();
  // this should not be done in build method.
  _dataRequiredForBuild = _fetchAllData();
}

@override
Widget build(BuildContext context) {
  return Scaffold(
    //removed
    body: FutureBuilder<DataRequiredForBuild>(
      future: _dataRequiredForBuild,
      builder: (context, snapshot) {
        return snapshot.hasData
            ? ListView.builder(
                itemCount: snapshot.data.items.length,
                itemBuilder: (_, int position) {
                  final item = snapshot.data.items[position];
                  final image = "${snapshot.data.imagesPath}/${item.row[0]}.jpg";
                  return Card(
                      child: ListTile(
                    leading: Image.asset(image),
                    title: Text(item.row[1]),
                    subtitle: Text(item.row[2]),
                    trailing: Icon(Icons.launch),
                  ));
                })
            : Center(
                child: CircularProgressIndicator(),
              );
      },
    ),
  );
}

Hope it helps.

like image 176
Chenna Reddy Avatar answered Sep 28 '22 17:09

Chenna Reddy


Moving this piece of code inside FutureBuilder should resolve the issue.

getImagesPath().then((path){
 imagesPath = path;
 print(imagesPath); //prints correct path
});

So your final code should look like this:

@override
Widget build(BuildContext context) {

return Scaffold(
    //removed
    body: FutureBuilder<List>(
        future: databaseHelper.getList(),
        initialData: List(),
        builder: (context, snapshot) {
          return snapshot.hasData
              ? ListView.builder(
                  itemCount: snapshot.data.length,
                  itemBuilder: (_, int position) {
                    getImagesPath().then((path){
                        imagesPath = path;
                    });

                    final item = snapshot.data[position];
                    final image = "$imagesPath/${item.row[0]}.jpg";
                    return Card(
                        child: ListTile(
                      leading: Image.file(File(image)),
                      title: Text(item.row[1]),
                      subtitle: Text(item.row[2]),
                      trailing: Icon(Icons.launch),
                    ));
                  })
              : Center(
                  child: CircularProgressIndicator(),
                );
        }));
}

Hope it helps!

like image 23
Atlas_Gondal Avatar answered Sep 28 '22 19:09

Atlas_Gondal