Dart return Future.value is always null - flutter

I am trying to build a URL from a Firebase Storage file but the Future<String> I have built always seems to return null. This is the Future I am calling:
Future<String> getUrlFromStorageRefFromDocumentRef(
DocumentReference docRef) async {
try {
docRef.get().then((DocumentSnapshot documentSnapshot) {
if (documentSnapshot.exists) {
String filename = documentSnapshot.get('file');
firebase_storage.Reference ref = firebase_storage
.FirebaseStorage.instance
.ref()
.child('/flamelink/media/$filename');
if (ref == null) {
return Future.error("Storage Reference is null");
} else {
print(ref.fullPath);
return Future.value(
'https://storage.googleapis.com/xxxxxxxxx.appspot.com/${ref.fullPath}');
}
} else {
return Future.error('No Snapshot for DocumentReference ${docRef.id}');
}
});
} catch (e) {
print(e);
return Future.error('No DocumentReference for ID ${docRef.id}');
}
}
The line in question is :
return Future.value(
'https://storage.googleapis.com/xxxxxxxxx.appspot.com/${ref.fullPath}');
It's worth noting that the String is generated from the Firebase Storage path and everything looks perfect until it comes to return the value.
It should return the String value back to my calling code which at the moment looks like this:
DocButtonCallback docCallback = () async {
bool isKidsDoc = item.screenId == StringsManager.instance.screenIdKids;
try {
// first we need to get the URL for the document ...
var url = await AssetManager.instance
.getUrlFromStorageRefFromDocumentRef(isKidsDoc
? feature.relatedDocumentKidsRef
: feature.relatedDocumentRef);
String urlString = url.toString();
canLaunch(urlString).then((value) {
launch(urlString);
}).catchError((error) {
// TODO: open alert to tell user
});
} catch (error) {
print(error);
}
};
I have tried many different ways to get that String including:
DocButtonCallback docCallback = () async {
bool isKidsDoc = item.screenId == StringsManager.instance.screenIdKids;
await AssetManager.instance
.getUrlFromStorageRefFromDocumentRef(isKidsDoc
? feature.relatedDocumentKidsRef
: feature.relatedDocumentRef)
.then((urlString) {
canLaunch(urlString).then((value) {
launch(urlString);
}).catchError((error) {
// TODO: open alert to tell user
});
}).catchError((error) {
// TODO: open alert to tell user
});
};
For some reason, the Future always returns null. What am I doing wrong here?

You are returning the Future value inside the then() callback, which essentially returns this value from the callback itself rather than from your getUrlFromStorageRefFromDocumentRef() function. There you should only need to add a return statement before that:
Current:
docRef.get().then((DocumentSnapshot documentSnapshot) {
if (documentSnapshot.exists) {
...
After:
/// Adding the return statement here to return the actual value
/// returned internally by the then callback
return docRef.get().then((DocumentSnapshot documentSnapshot) {
if (documentSnapshot.exists) {
...
If you hover over the then() callback, your IDE should show you that this callback will return Future<T> (or whatever generic type placeholder) which need to be returned as well in order to make it available

Related

How to pass a `where` clause to a function in Dart?

There is a function, getItems, and I would like to be able to have multiple where to modify the resulting list. I am new to Dart and cannot find the syntax for passing in a where.
I tried creating functions with custom where to call getItems, but cannot due to the async nature of getItems.
Future<List<IioMenuItem>> getItems() async {
// ...
final db = await openDatabase(path, readOnly: true);
final List<Map<String, dynamic>> maps = await db.query('menu_items');
final dbFilteredItems = maps.map((item) => IioMenuItem(
// assign values code removed
)).where((element) { // <-- make 'where' replaceable
if (filterState == FilterState.all) {
return true;
} else {
return element.type.name == filterState.name;
}
}).toList(growable: false);
return List.generate(dbFilteredItems.length, (i) {
return dbFilteredItems[i];
});
}
The failed attempt
Future<List<IioMenuItem>> menuItems(FilterState filterState) async {
final dbFilteredItems = getItems().where((element) { // The method 'where' isn't defined for the type 'Future'.
if (filterState == FilterState.all) {
return true;
} else {
return element.type.name == filterState.name;
}
}).toList(growable: false);
return List.generate(dbFilteredItems.length, (i) {
return dbFilteredItems[i];
});
}
Can I please get help?
The term you're looking for is a "closure" or "first class function".
See Functions as first-class objects on the Language guide.
"A where" isn't a thing. It's not a noun. Iterable.where is just the name of a function, and that function happens to take a function as a parameter, and uses it to determine what things to keep.
In this specific case, you want a function that takes a IioMenuItem, and returns a boolean that determins where or not to keep it. The type of that is a bool Function(IioMenuItem) (see Function).
I called it "predicate":
Future<List<IioMenuItem>> menuItems(
FilterState filterState,
bool Function(IioMenuItem) predicate // <- Take it in as a parameter
) async {
return (await getItems())
.where(predicate) // <- pass it along as an argument to `where`
.toList(growable: false);
}
You can pass any test inside a where.
filterItems(Map<String,dynamic> element) {
if (filterState == FilterState.all) {
return true;
} else {
return element.type.name == filterState.name;
}
}
final dbFilteredItems = maps.map((item) => IioMenuItem(
// assign values code removed
)).where(filterItems).toList(growable: false);
Use then in future
getItems().then((value) => value.where((element ...
Use await to call async functions.
Future<List<IioMenuItem>> menuItems(FilterState filterState) async {
final dbFilteredItems = (await getItems()).where((element) { // await has to be used here.
if (filterState == FilterState.all) {
return true;
} else {
return element.type.name == filterState.name;
}
}).toList(growable: false);
return dbFilteredItems;
}

How to await a listener being called and return a Future value

I have a Future function which adds a listener to a ValueNotifier. How can I return a value that I retrieve when the listener is called?
Map<String,ValueNotifier<String>> _data = {};
Future<String> getAnswer(String text) async {
if (_data["answer"] == null || _data["answer"]!.value.isEmpty) {
_data["answer"] = ValueNotifier<String>("");
_data["answer"]!.addListener(() {
if (_data["answer"]!.value.isNotEmpty) {
// somehow return _data["answer"]!.value
} else {
// continue waiting for next listener call
}
});
} else {
return _data["answer"]!.value;
}
return await //not sure what to put here.
}
Ok I found a solution that works in this case is to use Completer() as follows:
Map<String,ValueNotifier<String>> _data = {};
Future<String> getAnswer(String text) async {
var completer = Completer<String>();
if (_data["answer"] == null || _data["answer"]!.value.isEmpty) {
_data["answer"] = ValueNotifier<String>("");
_data["answer"]!.addListener(() {
if (_data["answer"]!.value.isNotEmpty) {
completer.complete(_data["answer"]!.value);
_data["answer"]!.dispose();
}
});
} else {
return _data["answer"]!.value;
}
return completer.future;
}
Seems like you always want to use the value of _data['answer'] as soon as it gets updated. In this case you should try using Riverpod library: https://pub.dev/packages/flutter_riverpod This library will help you in watching a notifier and then rebuild stateful widgets when the data is updated.

Future returns list before being populated

I have the following method:
Future<List<Job>> getUserJobs() async {
Query query = _firebaseDatabase
.reference()
.child("jobs")
.child(_firebaseAuth.currentUser.uid)
.orderByKey();
List<Job> userJobs = [];
if (query == null) {
return userJobs;
}
query.onValue.listen((event) {
Map<dynamic, dynamic> values = event.snapshot.value;
values.forEach((key, value) {
userJobs.add(Job.fromJson(key, Map.from(value)));
});
});
return userJobs;
}
I want to get this response in another class, however, the list returned by the above method is always []. I checked and the userJobs list is indeed populated but the return statement is executed before.
The structure of the database is:
Job collection has user IDs and for each user ID I have several job keys (each with its job data).
Try this:
Future<List<Job>> getUserJobs() async {
List<Job> userJobs = [];
// Query query =
await _firebaseDatabase
.reference()
.child("jobs")
.child(_firebaseAuth.currentUser.uid)
.once()
.orderByKey().then((result) async {
if (result.value != null) {
result.value.forEach((key, childSnapshot) {
userJobs.add(Job.fromJson(key, Map.from(childSnapshot)));
});
} else {
print(
'getUserJobs() no jobs found');
}
}).catchError((e) {
print(
'getUserJobs() error: $e');
});
// if (query == null) {
// return userJobs;
// }
// query.onValue.listen((event) {
// Map<dynamic, dynamic> values = event.snapshot.value;
// values.forEach((key, value) {
// userJobs.add(Job.fromJson(key, Map.from(value)));
// });
// });
return userJobs;
}
your loop also needs to be async..otherwise the method will return before the loop finishes, returning the empty List.. been there and got quite frustrated by this..
also always use .catchError callback.. it tells you what's going wrong ;)

How to asynchronously call JavaScript in Flutter?

I have a flutter web where i have a JavaScript function like this:
async function doSomething(value) {
let x = await something(x);
return x
}
When I'm now in dart, I have:
final result = await js.context.callMethod('doSomething', ['someValue']));
This returns [object Promise] when I print it, but it does ignore await, does not have a .then function and is therefore not working with promiseToFuture either.
How can I wait for JavaScript to be executed?
Just await doesn't work for js.context.callMethod.
This must be added somewhere in your code, maybe like javascript_controller.dart
#JS()
library script.js;
import 'package:js/js.dart';
import 'dart:js_util';
#JS()
external dynamic doSomething();
and then
Future getSomething(String someValue) async {
var result = await promiseToFuture(doSomething());
return result;
}
Maybe use a Future builder to wait for JS to execute?
Future getSomething(String someValue) async {
var result = await js.context.callMethod('doSomething', [someValue]));
return result;
}
FutureBuilder(
future: getSomething(someValue),
builder: (context, snapshot) {
if (snapshot.hasData) {
var data = snapshot.data;
print(data);
} else {
return Loading();
}
});
Place it in index.html
<script src="script.js" defer></script>
In Script.js
async function showAlert(href,hashtag,quote) {
if (response) {
window.dartFunc("asd");
dartFunc("asd");
} else if () {
alert("error adding points");
}
else{
alert("error dialog close");
}
});
};
In your dart file in initstate()
setProperty(window, 'dartFunc', js.allowInterop(dartFunc));
sss() async {
await js.context.callMethod('showAlert', [
'',
'',
''
]);
}
String dartFunc(String str) {
Common.showToast('Music');
Common.showToast(str);
return 'Inside dartFunc: ' + str;
}
I tried the same method to do asynchronously from js file using async and used --promisetofuture in dart file but i am unable to wait until we get response from js file for flutter web
async function getPhoneNumber() {
let res = await someFunction to wait for someTime;
return res;
}
function somfunc() async{
var number = await
promiseToFuture(js.context.callMethod('getPhoneNumber'));
}

How to synchronize a call from the asynchronous function in dart

I am working on my first app in Flutter, I have a bit of experience with Java and js, but I never worked with flutter before so sorry if my question will seem ridiculous to you.
The app is the voice assistant chatbot, and it is supposed to perform text to speech on each new message that customer receives, my problem is that since I am using firebase messaging all of the requests that I receive are in the asynchronous call, but I need to synchronize the access to the text to speech service otherwise I run into problem of having one text interrupt another.
This is what my code looks like at the moment:
Firebase messaging:
onMessage: (Map<String, dynamic> message) {
return this.handleBotMessage(appState, message);
},
Method that desides how to handle each particular message:
Future handleBotMessage(
Store<AppState> store,
Map<String, dynamic> dataJson,
) {
#logic that convert the message into json and extracts the message type
if (type == MessageType.CHAT_MESSAGE) {
return handleChatMessage(store, subtype, messageMap);
}
}
The method that handles text messages:
Future<dynamic> handleChatMessage(
Store<AppState> store,
MessageSubtype subtype,
Map<String, dynamic> message,
) {
#Text to speach is build as a singleton and this always returns the same instance
TextToSpeech tts = TextToSpeech();
if (subtype == MessageSubtype.TEXT) {
TextMessage textMessage = TextMessage.fromJson(message);
return tts
.speak(textMessage.text)
.then((result) => store.dispatch(NewBotMessageAction(textMessage)));
} else if (subtype == MessageSubtype.QUICK_REPLY) {
QuickReplyMessage qrMessage = QuickReplyMessage.fromJson(message);
return tts
.speak(qrMessage.text)
.then((result) => store.dispatch(NewQrOptionsAction(qrMessage)));
} else {
throw new Exception('Unexpected message subtype!');
}
}
The method that actually performs the text to speech
Future<dynamic> speak(String text) async {
return flutterTts.speak(text).then((resp) {
ttsRunning = false;
print(resp);
return resp;
}, onError: (obj, st) {
ttsRunning = false;
print(obj);
print(st.toString());
});
}
Text to speech initialization
Future init() async {
await flutterTts.setLanguage("en-US");
var res = await flutterTts.isLanguageAvailable("en-US");
print(res);
return res;
}
https://pub.dev/packages/flutter_tts
Ok, I have found the solution, the issue was as frank06 pointed out with the fact that flutter tts completes the future immediately rather than after the whole phrase was spoken.
So here is my solution, it is not perfect, but it works:
Completer completer;
Future<dynamic> speak(String text) {
print('Started speeking');
print(new DateTime.now().toIso8601String());
if (TextToSpeech.lastRequest == null) {
lastRequest = _executeSpeech(text);
} else {
lastRequest = lastRequest.then((resp) {
return _executeSpeech(text);
});
}
return lastRequest;
}
Future<dynamic> _executeSpeech(String text) {
completer = Completer();
flutterTts.speak(text).then((resp) {
ttsRunning = false;
print(resp);
return resp;
}, onError: (obj, st) {
ttsRunning = false;
print(obj);
print(st.toString());
});
return completer.future;
}
flutterTts.setCompletionHandler(() {
print('Finished speeking');
print(new DateTime.now().toIso8601String());
ttsState = TtsState.stopped;
completer.complete(ttsState);
});
flutterTts.setErrorHandler((msg) {
ttsState = TtsState.stopped;
completer.complete(ttsState);
});
If you don't want new messages interrupting those being spoken, you can queue them up. This way the new messages will wait for the current message to finish. Check out this approach:
Queue of Future in dart