In Flutter how can I return an object after completion of Future in nested async functions? - asynchronous

I have a function which calls various other functions that return a Future and after the completion of one Future another method that return a future is called and in the end i need to return a value as a variable but the problem is that function returns NULL instead of the value.
_getLocationPermission() waits and gets the required permission, i have to wait until I get the permission after getting permission I have to call _getCurrentLocation() which will return a Future < LocationData > and i have to pass this object's data to getWeatherDetails() and it will eventually return a Future< String > and i don't know how can i return this string in the return statement.
Future<String> getData() async {
String longitude, latitude, weatherDetails;
_getLocationPermission().then((permissionStatus) {
_getCurrentLocation().then((location) async {
longitude = location.longitude.toString();
latitude = location.latitude.toString();
weatherDetails = await getWeatherDetails(longitude, latitude);
print(longitude);
});
});
return weatherDetails;
}
Thanks!

You seem to be returning a resolve aync response from the getWeatherDetails function not a Future as your function return type shows.
Future<String> getData() async {
var weather;
_getLocationPermission().then((_){
var location = await _getCurrentLocation();
weather = getWeatherDetails(location.longitude.toString(), location.latitude.toString());
})
.catchError((error){
// Handle if location permission fails
});
return weather;
}

Related

How do I properly cast a response from a Firebase Function call in my Flutter app

I'm teaching myself Flutter by building a simple meal planner app. Part of what I want to do is to use a Firebase function for making calls to the API. I'm using the Spoonacular API, and I do not want to store the API key on the app itself, hence the Firebase backend.
I've set up a file called cloud_functions.dart, which I plan on using to make calls to my Firebase functions. The call to get the recipes is as follows:
Future<SearchRecipesComplexResponseBody> getRecipes() async {
HttpsCallable callable = getCallable('searchRecipes');
try {
final HttpsCallableResult<SearchRecipesComplexResponseBody> results = await callable({'number': 20, 'offset': 0});
print('Results: ');
print(results);
print('Results data:');
print(results.data);
return results.data;
} catch (e) {
print('Error: ');
print(e);
return null;
}
}
HttpsCallable getCallable(String callableName) {
FirebaseFunctions functions = FirebaseFunctions.instance;
if (kDebugMode) {
print('Running in debug mode');
functions.useFunctionsEmulator(origin: 'http://localhost:5001');
}
return functions.httpsCallable(callableName);
}
The code for SearchRecipesComplexResponseBody is as follows:
import 'package:meal_planner/models/recipe.dart';
import 'package:json_annotation/json_annotation.dart';
part 'search_recipes_complex_response_body.g.dart';
#JsonSerializable()
class SearchRecipesComplexResponseBody {
final int offset;
final int number;
final List<Recipe> results;
final int totalResults;
SearchRecipesComplexResponseBody({this.offset, this.number, this.results, this.totalResults});
factory SearchRecipesComplexResponseBody.fromJson(Map<String, dynamic> json) {
return _$SearchRecipesComplexResponseBodyFromJson(json);
}
}
The code for Recipe is as follows:
#JsonSerializable()
class Recipe {
final int id;
#JsonKey(includeIfNull: false)
final int calories;
#JsonKey(includeIfNull: false)
final String carbs;
#JsonKey(includeIfNull: false)
final String fat;
final String image;
final String imageType;
#JsonKey(includeIfNull: false)
final String protein;
final String title;
Recipe({#required this.id, this.calories, this.carbs, this.fat, this.image, this.imageType, this.protein, #required this.title});
factory Recipe.fromJson(Map<String, dynamic> json) {
return _$RecipeFromJson(json);
}
}
While I do get the data back that I'm expecting, there's something going on with the casting that I get this error when running the code:
type '_InternalLinkedHashMap<dynamic, dynamic>' is not a subtype of type 'Map<String, dynamic>'
When I went to debug the code, breaking on the print(results) line in the cloud_functions.dart file, I saw that the data does seem to match the format that I'm expecting
I've attempted to use the json_serializable utility to generate the JSON serialization code, but that didn't work either. I've tried removing the extraneous fields in the Recipe class to no avail.
I think the issue is something to do with the fact that I've got a property on the SearchRecipesComplexResponseBody that's a list of Recipes, but I can't seem to figure out what I'm doing wrong here. For all I know, I could be barking up the wrong tree. Does anyone have any ideas?
RESOURCES CHECKED:
Flutterfire Cloud Functions documentation - https://firebase.flutter.dev/docs/functions/usage/
Search on HttpCallableResult - https://duckduckgo.com/?q=flutterfire+httpcallableresult&ia=web
https://medium.com/codespace69/flutter-working-with-data-json-json-and-serialization-f90165b659d0
I figured it out
I updated the getRecipes function in cloud_functions.dart to be as follows:
Future<SearchRecipesComplexResponseBody> getRecipes() async {
HttpsCallable callable = getCallable('searchRecipes');
try {
final HttpsCallableResult results = await callable({'number': 20, 'offset': 0});
var convertedResult = Map<String, dynamic>.from(results.data);
SearchRecipesComplexResponseBody data = SearchRecipesComplexResponseBody.fromJson(convertedResult);
return data;
} catch (e) {
print('Error: ');
print(e);
return null;
}
}
I saw that I already had a fromJson function defined on my SearchRecipesComplexResponseBody class, but I hadn't been taking advantage of it. I needed to convert the response I got back from Firebase from an _InternalLinkedHashMap<dynamic, dynamic> to the Map<String, dynamic> type that fromJson uses.
I also needed to add anyMap: true inside my JsonSerializer attributes to get the nested list of Recipes in order for its fromJson. I'm not sure why that worked. Anyone have any thoughts?
You may use this to convert from _InternalLinkedHashMap to Map<String, dynamic>. This will get you overcome the error type '_InternalLinkedHashMap<dynamic, dynamic>' is not a subtype of type 'Map<String, dynamic>':
HttpsCallable callable = functions.httpsCallable('your-function');
final results = await callable();
final data = Map<String, dynamic>.from(results.data);

Dart - return data from a Stream Subscription block

I subscribe to a stream with the listen method so I can process the data as it comes in. If I found what I am looking for, I want to cancel the stream and return the data from the stream subscription block.
I have a working implementation but I am using a Completer to block the function from returning immediate and it feels messy, so I want to know if there's a better way to achieve this. Here is my code snippet:
Future<String> _extractInfo(String url) async {
var data = <int>[];
var client = http.Client();
var request = http.Request('GET', Uri.parse(url));
var response = await client.send(request);
var process = Completer();
var interestingData = '';
StreamSubscription watch;
watch = response.stream.listen((value) async {
data.clear(); //clear the previous stale data from the list so searching can be faster
data.addAll(value);
if (
hasInterestingData(data) //search the bytelist for the data I'm looking for
){
interestingData = extractInterestingData(data) //extract the data if it's been found. This is what I want my function to return;
await watch.cancel(); //cancel the stream subscription if the data has been found. ie: Cancel the download
process.complete('done'); //complete the future, so the function can return interestingData.
}
});
watch.onDone(() {
//complete the future if the data was not found...so the function will return an empty string
if (!process.isCompleted) {
process.complete('done');
}
});
await process.future; //blocks the sync ops below so the function doesn't return immediately
client.close();
return interestingData;
}
You should use await for for things like this when you are in an async function.
Future<String> _extractInfo(String url) async {
var client = http.Client();
var request = http.Request('GET', Uri.parse(url));
var response = await client.send(request);
await for (var data in response.stream) {
if (hasInterestingData(data)) {
return extractInterestingData(data); // Exiting loop cancels the subscription.
}
}
return "done";
}
However, while this approach is equivalent to your code, it's probably equally flawed.
Unless you are looking for a single byte, you risk the thing you are looking for being split between consecutive data events. You like do need to retain some part of the previous data array, or some state summarizing what you have already seen, but how much depends on what you are actually looking for.

Unable to Return Queried Data From Firebase (Flutter/Dart)

Context: I'm trying to query and return a String (imgUrl) from Firebase. I'm always able to print the string inside the query, but the returned value is always null. I'm wondering if my query is wrong and am not sure what best practices are.
Database Outline:
Query Function:
This is the code under our DatabaseService() class, which contains all database queries and updating functions.
String getImageUrl(String _uid) {
String _imgUrl;
Firestore.instance
.document('users/$_uid')
.get()
.then((value) => _imgUrl = value['imgUrl']);
return _imgUrl;
}
Main:
getImageUrl() is called under setImage(). The toast under setImage always returns null and so does the code under it.
String _uid;
// Sets variable '_uid' to the uid of the current user
// Gets called in initstate
Future _getUid() async {
FirebaseUser user = await FirebaseAuth.instance.currentUser();
_uid = user.uid;
}
// Sets the profile photo. If there is no existing profile photo online,
// grab the image on the device. If there is no image online OR on the device,
// Display the default image
void setImage(String url) {
// Get the url that's stored in the db
String _tempUrl = DatabaseService().getImageUrl(_uid); // always ends up being null
Fluttertoast.showToast(msg: "_tempUrl: $_tempUrl");
// Rest of the function
}
#override
void initState() {
super.initState();
_getUid();
}
Please let me know what to do to fix this as it's driving me crazy. Thanks in advance.
Change the method to the following:
Future<String> getImageUrl(String _uid) async {
String _imgUrl;
DocumentSnapshot value =
await Firestore.instance.document('users/$_uid').get();
_imgUrl = value['imgUrl'];
return _imgUrl;
}
use async/await to wait for the future to finish, and then call it like the following:
void setImage(String url) async{
// Get the url that's stored in the db
String _tempUrl = await DatabaseService().getImageUrl(_uid); // always ends up being null
Fluttertoast.showToast(msg: "_tempUrl: $_tempUrl");
// Rest of the function
}

Call async function in a sync way in Dart

I have this function, want to call it synchronously, because if I call it async then I should use FutureBuilder, which I don't prefer because it has extra flicker if user scrolls too fast:
Future<String> getRealHTML(int chapter) async {
var key = _Html.keys.toList()[chapter];
var val = _Html.values.toList()[chapter];
if (val.Content.startsWith("filename:")) {
EpubContentFileRef value = contentRefHtml[key];
return await value.readContentAsText();
}
return null;
}
readContentAsText() returns a Future, you cannot call it synchronously since it is an I/O request which means it will take time to finish the request, that is why it is called asychronously.

Dart Future functions with callbacks on requests

I have an issue with understanding Future functions in dart and async programming. The examples I've found in web do not answer my question, so I would be grateful for community help.
I have a function that asks for players messages list:
Future getMessagesInfo(response) async{
dialogs = []; // I need this list to be filled to proceed
messagesList = response["data"];
await getDialogsFunc(messagesList);
renderMessages();
}
It is a callback for a previous request, which doesn't really matters here.
So, here is getDialogsFunc, which purpose is to runs through the list and ask playerInfo for each ID in the response:
Future getDialogsFunc(messagesList) async{
for(var i=0;i<messagesList.length;i++){
if(messagesList[i]["player2"] != player.ID) await rm.getDialogInfo(messagesList[i]["player2"]);
if(messagesList[i]["player1"] != player.ID) await rm.getDialogInfo(messagesList[i]["player1"]);
}
}
Here is getDialogInfo, which actually sends a request for playerInfo and has a callback function that handles received info:
Future getDialogInfo(int id) async{
messages = querySelector("account-messages-tab");
var request = new Request();
Object data = {"id": ServerAPI.PLAYER_INFO, "data":{"id": id}};
await request.sendRequest(data,false, messages.onGotDialogInfo);
}
The request is a simple class, that handles the requests:
class Request{
Future sendRequest(Object data, bool action,Function callback) async{
HttpRequest request = new HttpRequest();
String url = "http://example.com";
await request
..open('POST', url)
..onLoadEnd.listen((e)=> callback(JSON.decode(request.responseText)))
..send(JSON.encode(data));
}
}
And finally here is a callback for the request:
Future onGotDialogInfo(response) async{
List dialog = new List();
dialog.add(response["data"]["id"]);
dialog.add(response["data"]["login"]);
dialogs.add(dialog);
}
In the first function I wanted to run renderMessages() after I have received information about all messages, so that dialogs List should contain relevant information. In my realisation which I tested with breakpoints the renderMessages() functions runs BEFORE onGotDialogInfo callback.
What should I do to wait for the whole cycle getDialogsFunc functions and only then go to renderMessages()?
whenComplete is like finally, it's called no matter whether the request returned normally or with an error. Normally then is used.
getDialogsFunc uses async but doesn't use await which is a bit uncommon. This might be your intention, but I'm not sure
Future getDialogsFunc(messagesList) async {
for(var i=0;i<messagesList.length;i++){
if(messagesList[i]["player2"] != player.ID)
await rm.getDialogInfo(messagesList[i]["player2"]);
if(messagesList[i]["player1"] != player.ID)
await rm.getDialogInfo(messagesList[i]["player1"]);
}
}
getMessagesInfo could then look like:
void getMessagesInfo(response) await {
dialogs = []; // I need this list to be filled to proceed
messagesList = response["data"];
await getDialogsFunc(messagesList)
renderMessages(messagesList);
}
I don't know where Request comes from, therefore hard to tell how that code should look like. It should at least use await for other awaits to work as expected.
Future getDialogInfo(int id) async {
messages = querySelector("account-messages-tab");
var request = new Request();
Object data = {"id": ServerAPI.PLAYER_INFO, "data":{"id": id}};
final response = await request.sendRequest(data,false);
messages.onGotDialogInfo(response)
}
update
class Request{
Future sendRequest(Object data, bool action) async{
Completer completer = new Completer();
HttpRequest request = new HttpRequest();
String url = "http://example.com";
await request
..open('POST', url)
..onLoadEnd.listen((_) {
if (request.readyState == HttpRequest.DONE) {
if (request.status == 200) {
// data saved OK.
completer.complete(JSON.decode(request.responseText)); // output the response from the server
} else {
completer.completeError(request.status);
}
}
})
..send(JSON.encode(data));
return completer.future;
}
}

Resources