I'm currently playing around with Futures in flutter. I've some async functions that return a Future object. I register a Listener on the Future object with then(), so that I can update the ui as soon as the value comes in.
But the result is empty, because then() returns before all notes are loaded from the file system.
Future<List<Note>> loadNotes() async {
NoteService().findAll().then((result) {
result.forEach((note) => print(note.title)); //not printing -> result is emtpty...
});
}
//NoteService class
Future<List<Note>> findAll() async {
return noteRepository.findAll();
}
//NoteRepository class
#override
Future<List<Note>> findAll() async {
final Directory dir = await directory;
dir.list().toList().then((List<FileSystemEntity> list) async {
List<String> paths = List();
list.forEach((entity) => paths.add(entity.path));
List<File> _files = List();
paths.forEach((path) => _files.add(File(path)));
List<Note> notes = await _extractNotes(_files);
return Future.value(notes);
});
return Future.value(List());
}
Future<List<Note>> _extractNotes(List<File> _files) async {
List<Note> notes = List();
_files.forEach((file) {
String content = file.readAsStringSync();
print('content: ' + content); //this is getting printed correctly to the console
Map<String, dynamic> a = jsonDecode(content);
if(a.containsKey('codeSnippets')) {
notes.add(SnippetNoteEntity.fromJson(jsonDecode(content)));
} else {
notes.add(MarkdownNoteEntity.fromJson(jsonDecode(content)));
}
});
return Future.value(notes);
}
Related
I have updated dat to version 2.12 and I am getting this error:
The getter 'bodyBytes' isn't defined for the type 'Future Function(Uri, {Map<String, String>? headers})'.
Try importing the library that defines 'bodyBytes', correcting the name to the name of an existing getter, or defining a getter or field named 'bodyBytes'.
By code is like the following below:
I am getting 2 red lines below as
"bodyBytes": 1
"result.paths.first": 2
Code
pdf.dart:
class PDFApi {
static Future<File> loadAsset(String path) async {
final data = await rootBundle.load(path);
final bytes = data.buffer.asUint8List();
return _storeFile(path, bytes);
}
static Future<File> loadNetwork(String url) async {
final response = await http.get; Uri.parse(url);
final bytes = response.bodyBytes; <-- here: "bodyBytes": 1
return _storeFile(url, bytes);
}
static Future<File?> pickFile() async {
final result = await FilePicker.platform.pickFiles(
type: FileType.custom,
allowedExtensions: ['pdf'],
);
if (result == null) return null;
return File(result.paths.first); <-- here: "result.paths.first": 2
}
static Future<File?> loadFirebase(String url) async {
try {
final refPDF = FirebaseStorage.instance.ref().child(url);
final bytes = await refPDF.getData();
return _storeFile(url, bytes!);
} catch (e) {
return null;
}
}
static Future<File> _storeFile(String url, List<int> bytes) async {
final filename = basename(url);
final dir = await getApplicationDocumentsDirectory();
final file = File('${dir.path}/$filename');
await file.writeAsBytes(bytes, flush: true);
return file;
}
}
I changed final response = await http.get; Uri.parse(url); to final response = await http.get(Uri.parse(url)); thnx to pskink and return File(result.paths.first); to return File(result.paths.first!); , then it works just fine.
In my code, am trying a assign a string value to an empty string and display on the page but it keeps showing null but when I print it out, it shows the value.
String fName = '';
#override
void initState() {
super.initState();
getData();
}
getData() async {
FirebaseAuth _auth = FirebaseAuth.instance;
User _firebaseUser = _auth.currentUser;
print("============ MyHome ================");
print(_firebaseUser.uid);
_currentUser = await Database().getUserData(_firebaseUser.uid);
if (_currentUser != null) {
fName = _currentUser.firstName;
print(_currentUser.firstName);
}
}
database
Future<UserData> getUserData(String uid) async {
UserData returnValue = UserData();
try {
DocumentSnapshot _docSnapshot =
await _firestore.collection("users").doc(uid).get();
returnValue.uid = uid;
returnValue.firstName = _docSnapshot.data()["firstName"];
returnValue.lastName = _docSnapshot.data()["lastName"];
returnValue.userMail = _docSnapshot.data()["userMail"];
returnValue.userType = _docSnapshot.data()["userType"];
print("====================== on getData =============");
print(returnValue.firstName);
} catch (e) {
print(e);
}
return returnValue;
}
And whenever I try displaying the data it gives me null
Text("Hello, $fName"),
Please how do I do this or am I missing something
use setState to rebuild the widget tree with the value:
setState(() {
fName = _currentUser.firstName;
});
Since the getData function is async, flutter has already built the widget tree before getData finished. You'll now have to update the state using setstate.
setState(() {
fName = _currentUser.firstName;
});
You need to set the new state since we have made changes to the previous state (since your getData function is async.
setState(() {
fName = _currentUser.firstName;
});
So, I'm building my app in Flutter and unfortunately, I have recently come across an error. So what I want to do in my TestProvider class is to get data from firestore (what getQuestionFromFirebase() function is doing), and after that happens, I want to create a map from DocumentSnapshot (what questionMapFromFirebase() function is doing). And there comes an error, because I can't async in map function so my function doesn't wait for the result from previous function, and returns null. Any solutions? *I tried to return map from getQuestionFromFirebase() - Future, but later I can't use value from it because, my function wants pure map.
class TestProvider {
FirebaseFirestore _firestore = FirebaseFirestore.instance;
Future<DocumentSnapshot> getQuestionFromFirebase(String documentId) async {
return await _firestore.collection('questions').doc(documentId).get();
}
Map questionMapFromFirebase(String documentId) {
Map questionMapFromFirebase;
getQuestionFromFirebase(documentId).then((DocumentSnapshot carSnapshot) => {
questionMapFromFirebase = carSnapshot.data(),
});
return questionMapFromFirebase;
}
}
Later I'm using this function there:
I'm using this function later there
List<Question> listOfQuestions() {
List<int> range = numberInRange(amountOfQuestions);
List<Question> listOfQuestions;
for (int i = 1; i <= amountOfQuestions; i++) {
listOfQuestions.add(Question.fromMap(
_testProvider.questionMapFromFirebase(range[1].toString())));
}
return listOfQuestions;
}
And that's creating error when Future occurs.
The argument type 'Future<Map<dynamic, dynamic>>' can't be assigned to the parameter type 'Map<String, dynamic>'.
Edit:
So recently I've made some changes to my code and now it looks like that
class TestProvider {
FirebaseFirestore _firestore = FirebaseFirestore.instance;
Future<DocumentSnapshot> getQuestionFromFirebase(String documentId) async {
return await _firestore.collection('questions').doc(documentId).get();
}
Future<Map> questionMapFromFirebase(String documentId) async {
DocumentSnapshot ds = await getQuestionFromFirebase(documentId);
return ds.data();
}
}
and repository
class TestRepository {
final int amountOfQuestions;
TestRepository({
#required this.amountOfQuestions,
});
TestProvider _testProvider;
Future listOfQuestions() async {
List<int> range = numberInRange(amountOfQuestions);
List<Question> listOfQuestions;
for (int i = 1; i <= amountOfQuestions; i++) {
listOfQuestions.add(Question.fromMap(
await _testProvider.questionMapFromFirebase(range[i].toString())));
}
return listOfQuestions;
}
}
The problem I started to see that is that every time i tried to call function questionMapFromFirebase from TestProvider, it has been working just fine. But when i tried to call it from TestRepository it throw the error:
E/flutter (13348): [ERROR:flutter/lib/ui/ui_dart_state.cc(177)] Unhandled Exception: NoSuchMethodError: The method 'questionMapFromFirebase' was called on null.
E/flutter (13348): Receiver: null
E/flutter (13348): Tried calling: questionMapFromFirebase("2")
Any other sugestions how can I handle it?
Future<Map> questionMapFromFirebase(String documentId) async {
DocumentSnapshot ds = await getQuestionFromFirebase(documentId);
return ds.data();
}
Edit
check FutureBuilder class
example, it will be inside your widget tree where the list need to be shown.
return FutureBuilder(
future: _loadQuestions(),
builder: (context, snapshot) {
if(snapshot.connectionState == ConnectionState.done){
return widgetForListing(snapshot.data);
}
return Center(child: Text('Loading...'));
},
);
And your _loadQuestions function will be as
_loadQuestions() async {
List<int> range = numberInRange(amountOfQuestions);
List<Question> listOfQuestions = [];
for (int i = 1; i <= amountOfQuestions; i++) {
listOfQuestions.add(Question.fromMap(
await _testProvider.questionMapFromFirebase(range[1].toString())));
}
return listOfQuestions; //you can get this list in **snapshot.data** of future builder
}
In my class I'm loading some files, and for efficiency I wanted to make a thread safe cache. I see in the map class that there is a putIfAbsent method, but it doesn't accept async types. Also not sure if this structure in general is safe to use.
This is the style of what I'm trying to do:
final Map<String, String> _cache = new Map();
Future<String> parse(final String name) async {
_cache.putIfAbsent(name, () async { // this async is not allowed
return await new File(name).readAsString();
});
return _cache[name];
}
Since I can use async on the parameter I've opted to use locks instead, but it makes the code far more verbose..
final Lock _lock = new Lock();
final Map<String, String> _cache = new Map();
Future<String> parse(final String name) async {
if (!_cache.containsKey(name)) {
await _lock.synchronized(() async {
if (!_cache.containsKey(name)) {
_cache[name] = await new File(name).readAsString();
}
});
}
return _cache[name];
}
Does anyone know how I can simplify this code, or if there are better libraries I can use for thread safe cache?
What do you mean by "this async is not allowed"? I see no particular issue with the putIfAbsent code, and I believe it should work.
The one probelem I see is that the cache is not caching futures, but strings. Since your function is returning a future anyway, you might as well store the future in the cache.
I would write it as:
final Map<String, Future<String>> _cache = new Map();
Future<String> parse(final String name) =>
_cache.putIfAbsent(name, () => File(name).readAsString());
but apart from fixing the _cache map type, that is effectively the same, it's just avoiding creating and waiting for a couple of extra futures.
I've created an extension to support an asynchronous action for putIfAbsent:
extension MapUtils<K, V> on Map<K, V> {
Future<V> putIfAbsentAsync(K key, FutureOr<V> Function() action) async {
final V? previous = this[key];
final V current;
if (previous == null) {
current = await action();
this[key] = current;
} else {
current = previous;
}
return current;
}
}
You can use like this:
final Map<String, String> _cache = {};
Future<String> parse(final String name) async {
return await _cache.putIfAbsentAsync(
name,
() async => await File(name).readAsString(),
// ^^^^^ this `async` is now allowed
);
}
Hey there I am quite new to Dart Futures and I have the following situation.
Whenever a user types a letter in the UI the addressChanged() method in my ui_component is called. This method calls the method getProposals() in my maps componenet which does an asynchronous request to the google maps API. As soon as the results are here I want to return them to the UI Component which is going to populate the propasals dropdown in the UI.
I am stuck with the last step: How (and whats the best way) to return the results of an asynchronous callback function to a parent component (while keeping an reusable maps component?).
This is what I have tried:
1) UI_Component:
// I get called if a user typed a new letter
Future addressChanged(dynamic event) async {
String id = event.target.id;
String address = event.target.value;
if(id=="pickup") {
this.pickup = address;
} else if(id=="destination") {
this.destination = address;
}
// this is where I call the subcomponent and want to get the address propasals
String proposals = await googleMap.getProposals(address,id);
print(proposals);
populateProposalDropdown();
}
2) Google Map component:
Future getProposals(String address,String id) async {
await _getProposals(address,id);
}
Future _getProposals(String address,String id) async {
if(address != "") {
autocompleteService.getPlacePredictions(
new AutocompletionRequest()
..input = address
,
(predictions,status) {
List<String> result = [];
if(status == PlacesServiceStatus.OK) {
predictions.forEach(
(AutocompletePrediction prediction) =>
result.add(prediction.description)
);
}
// HERE is the problem: How do I return this result from the callback as a result of the getProposals method?
return result;
}
);
}
}
This method doesn't return any data
Future getProposals(String address,String id) async {
await _getProposals(address,id);
}
Change it to
Future getProposals(String address,String id) {
return _getProposals(address,id);
}
This would also work, but here async and await is redunant
Future getProposals(String address,String id) async {
return await _getProposals(address,id);
}
For _getProposals you can use a Completer
Future _getProposals(String address,String id) async {
if(address != "") {
Completer completer = new Completer();
autocompleteService.getPlacePredictions(
new AutocompletionRequest()
..input = address
,
(predictions,status) {
List<String> result = [];
if(status == PlacesServiceStatus.OK) {
predictions.forEach(
(AutocompletePrediction prediction) =>
result.add(prediction.description)
);
}
// HERE is the problem: How do I return this result from the callback as a result of the getProposals method?
completer.complete(result);
}
);
return completer.future;
}
return null;
}