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.
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.
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 .
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.
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.
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!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With