I'm learning Dart and I've hit a roadblock. I very much want to return a value from a json string processing function so I can use that value inside main(). (I'm trying to set some top-level variables to use with a one-way data bind with an html template.) I'm using HttpRequest.getString and a .then call to kick off the processing. But HttpRequest doesn't like being assigned to a variable so I'm not sure how to get anything back out of it.
processString(String jsonString) {
// Create a map of relevant data
return myMap;
}
void main() {
HttpRequest.getString(url).then(processString);
// Do something with the processed result!
}
I guess my question is how do I get a value back from a function that was called from an HttpRequest?
You're trying to do something that the Dart async model doesn't support. You'll have to handle the result of the async request:
In processString(),
In another function called from processString(),
In an anonymous function passed to then().
Or something similar. What you can't do is access it from further down in main():
processString(String jsonString) {
// Create a map of relevant data
// Do something with the processed result!
}
void main() {
HttpRequest.getString(url).then(processString);
// Any code here can never access the result of the HttpRequest
}
You may prefer:
processString(String jsonString) {
// Create a map of relevant data
return myMap;
}
void main() {
HttpRequest.getString(url).then((resp) {
map = processString(resp);
// Do something with the processed result!
});
// Any code here can never access the result of the HttpRequest
}
Related
I'm making a Future method that lives inside a seperate class, that fetches a bunch XKCD comics, and puts them in a List and returns it.
And that is all fine and dandy, but I would like to notify back when a single comic has been fetched, so I can show a progress dialog, on how far we are.
This is my code:
// This is inside my class ComicManager
Future<List<ComicModel>> generateComicList() async {
List<ComicModel> comicList = new List<ComicModel>();
ComicModel latestComic = await getLatestComic();
for (var i = 1; i <= latestComic.num; i++) {
try {
http.Response response =
await http.get('https://xkcd.com/${i}/info.0.json');
Map comicmap = json.decode(response.body);
var comic = new ComicModel.fromJson(comicmap);
comicList.add(comic);
print(comic.num);
// Notify here that we have fetched a comic
} catch (ex) {
// Comic could apparently not be parsed, skip it.
}
}
return comicList;
}
How should I solve this?
There seems no particularly elegant way to do this. From some flutter code samples, it seems using VoidCallBack listeners is an accepted way.
First register callback functions in a Set
Set<VoidCallBack> listeners
Then define the callback functions you needed. And add them to the set
void fun()
//...
listeners.add(fun);//Or you can define a method to do this or simply pass the function through the constructor of this class.
Finally, write a notifyListeners function or its equivalent and call it wherever you want
void notifyListeners(){
for(final listener in listeners){
listener();
}
}
If you want callback functions to carry an argument, just change the VoidCallBack to whatever function types.
Found a solution.
I just used Streams like so:
Stream<ComicProgressModel> getAllComicsStream() async* {
// Do what you need to do here
// This will respond back when you are listening to the stream
yield stuffToYield; // Can be anything, and you can yield as many times you want
// When you reach the end of the method, the onDone method will be called.
// So if you are running a for loop, and call yield multiple times it onDone is only called the the this method ends
}
Then I can just listen to events like so:
Stream comicStream =
ComicManager().getAllComicsStream().asBroadcastStream();
StreamSubscription comicsub = comicStream.listen((onData) {
// Do what i need
});
Super easy to be honest.
Scenario
If I want to read from a file and store the data in a Map, and if that map is being used multiple times for validation.
Is it possible for me to do this without having to change the return type of all methods, that use the above mentioned map, to Future?
Example:
Map metadata = null
Future readFromFile async {
.... metadata = await File.readingfromFile(...);
}
Future getRegion(..) async {
if(metadata == null) { await readFromFile() }
return metadata["region"]
}
Using the above code if a method(like isValidRegion,etc) that uses and needs getRegion(..) to complete, then the return type of isValidRegion should be converted to Future.
Future<bool> isValidRegion(..) async {
return ((await getRegionData(...)) != null )
}
If that isValidRegion is present within another methods, then the return type of them have to be changed to Future as well.
Future<String> parse(...) async {
....
if(await isValidRegion()) {
...
}
...
}
What is an elegant way to avoid this chain of futures as return types?
Async execution is contagious, there is nothing you can do to get back from async to sync execution.
What you can do is to do the read from the file synchronous to avoid the problem in the first place (if this is possible, if you read it from a network connection, this might not be possible).
I'm new Dart and have an iOS background so I might be using the language incorrectly which is leading to my code not working as expected but I also cannot find a solution online and thought I would ask it here incase someone else has experienced it and fixed it.
So what I'm trying to accomplish is have a model factory create a datasource in which you pass the request type (GET or POST), pass in an endpoint, and some params. Then it will return you back a future in which you listen for a then() call, this is called when the network request is successful/fails. The code I currently have works up until the point when I call completion.complete(responseObject);...nothing happens after that.
Code for the few functions that are involved:
Inside the Model Factory:
BaseDataSource bds = new BaseDataSource();
Map params = {"api_key": "random","session_id": "random", "account_id": "random"};
Future<Map> request = bds.makeRequest(NetworkingRequestType.GET, params, "music/songs");
request.then((Map responseObject) {
});
Inside the Datasource:
enum NetworkingRequestType {
GET, POST
}
class BaseDataSource {
static final String baseServerURL = config[appRunMode]["baseServerURL"];
Future<Map> makeRequest(NetworkingRequestType type, Map params, String endpoint) {
Future<Map> completion;
switch (type) {
case NetworkingRequestType.GET:
completion = _makeGetRequest(endpoint, params);
break;
case NetworkingRequestType.POST:
break;
}
return completion;
}
Future<Map> _makeGetRequest(String endpoint, Map params) {
final uri = new Uri(path: "${baseServerURL}${endpoint}",
queryParameters: params);
return HttpRequest.getString("${uri}").then(JSON.decode);
}
}
I can't spot anything wrong with this code, except that it does not propagate errors. It should work. If completion.complete is invoked then completion.future must complete, check that completion.complete is indeed invoked (e.g. check if there were any network errors on the console, check Network tab in DevTools to see request/response).
In general though I'd recommend avoiding explicit usage of completers. Just use the future returned by then instead of completer.future:
Future<Map> makeRequest(NetworkingRequestType type, Map params, String endpoint) {
switch (type) {
case NetworkingRequestType.GET:
return _makeGetRequest(endpoint, params);
case NetworkingRequestType.POST:
break;
}
}
Future<Map> _makeGetRequest(String endpoint, Map params) {
final uri = new Uri(path: "${baseServerURL}${endpoint}",
queryParameters: params);
return HttpRequest.getString("${uri}").then(JSON.decode);
}
Your code looks to me like some mixture of using Completer and not using Completer.
How the code would look like when using Completer:
Future<Map> makeRequest(NetworkingRequestType type, Map params, String endpoint) {
// Future<Map> completion; // <= if you want to use a completer this should be
Completer<Map> completion = new Completer<Map>()
switch (type) {
case NetworkingRequestType.GET:
// completion = _makeGetRequest(endpoint, params); // <= this would change to
_makeGetRequest(endpoint, params).then((result) => completion.complete(result));
break;
case NetworkingRequestType.POST:
// completion.complete({});
_makePostRequest(endpoint, params).then((result) => completion.complete(result));
break;
}
return completion.future;
}
but you probably don't need to use a Completer for this use case.
The code would then look like without using Completer:
Future<Map> makeRequest(NetworkingRequestType type, Map params, String endpoint) {
switch (type) {
case NetworkingRequestType.GET:
return _makeGetRequest(endpoint, params);
break;
case NetworkingRequestType.POST:
// return new Future.value({}); // or whatever you want to do here
return _makePostRequest(endpoint, params);
break;
}
}
caution: code not tested.
I'm using the Lawndart library to access browser data, and want to collect the results of a set of queries. Here's what I thought should work:
numberOfRecordsPerSection(callback) {
var map = new Map();
db_sections.keys().forEach((_key) {
db_sections.getByKey(_key).then((Map _section) {
int count = _section.length;
map[_key] = count;
});
}).then(callback(map));
}
However, when the callback is called, map is still empty (it gets populated correctly, but later, after all the Futures have completed). I assume the problem is that the Futures created by the getByKey() calls are not "captured by" the Futures created by the forEach() calls.
How can I correct my code to capture the result correctly?
the code from How do I do this jquery pattern in dart? looks very similar to yours
For each entry of _db.keys() a future is added to an array and then waited for all of them being finished by Future.wait()
Not sure if this code works (see comments on the answer on the linked question)
void fnA() {
fnB().then((_) {
// Here, all keys should have been loaded
});
}
Future fnB() {
return _db.open().then((_) {
List<Future> futures = [];
return _db.keys().forEach((String key_name) {
futures.add(_db.getByKey(key_name).then((String data) {
// do something with data
return data;
}));
}).then((_) => Future.wait(futures));
});
}
I would like to know how to define the data type and how to return the object (record) using getObject(). Currently, the only way that I have been able to use the result (record) outside of the function that obtains it is to call another function with the result. That way, the data-type does not need to be specified. However if I want to return the value, I need to define the data-type and I can't find what it is. I tried "dynamic" but that didn't appear to work. For example ":
fDbSelectOneClient(String sKey, Function fSuccess, String sErmes) {
try {
idb.Transaction oDbTxn = ogDb1.transaction(sgTblClient, 'readwrite');
idb.ObjectStore oDbTable = oDbTxn.objectStore(sgTblClient);
idb.Request oDbReqGet = oDbTable.getObject(sKey);
oDbReqGet.onSuccess.listen((val){
if (oDbReqGet.result == null) {
window.alert("Record $sKey was not found - $sErmes");
} else {
///////return oDbReqGet.result; /// THIS IS WHAT i WANT TO DO
fSuccess(oDbReqGet.result); /// THIS IS WHAT i'm HAVING TO DO
}});
oDbReqGet.onError.first.then((e){window.alert(
"Error reading single Client. Key = $sKey. Error = ${e}");});
} catch (oError) {
window.alert("Error attempting to read record for Client $sKey.
Error = ${oError}");
}
}
fAfterAddOrUpdateClient(oDbRec) {
/// this is one of the functions used as "fSuccess above
As someone else once said (can't remember who), once you start using an async API, everything needs to be async.
A typical "Dart" pattern to do this would be to use a Future + Completer pair (although there's nothing inherently wrong with what you've done in your question above - it's more a question of style...).
Conceptually, the fDbSelectOneClient function creates a completer object, and the function returns the completer.future. Then, when the async call completes, you call completer.complete, passing the value in.
A user of the function would call fDbSelectOneClient(...).then((result) => print(result)); to make use of the result in an async way
Your code above could be refactored as follows:
import 'dart:async'; // required for Completer
Future fDbSelectOneClient(String sKey) {
var completer = new Completer();
try {
idb.Transaction oDbTxn = ogDb1.transaction(sgTblClient, 'readwrite');
idb.ObjectStore oDbTable = oDbTxn.objectStore(sgTblClient);
idb.Request oDbReqGet = oDbTable.getObject(sKey);
oDbReqGet.onSuccess.listen((val) => completer.complete(oDbReqGet.result));
oDbReqGet.onError.first.then((err) => completer.completeError(err));
}
catch (oError) {
completer.completeError(oError);
}
return completer.future; // return the future
}
// calling code elsewhere
foo() {
var key = "Mr Blue";
fDbSelectOneClient(key)
.then((result) {
// do something with result (note, may be null)
})
..catchError((err) { // note method chaining ..
// do something with error
};
}
This future/completer pair only works for one shot (ie, if the onSuccess.listen is called multiple times, then the second time you will get a "Future already completed" error. (I've made an assumption on the basis of the function name fDbSelectOneClient that you are only expecting to select a single record.
To return a value from a single future multiple times, you'll probably have to use the new streams feature of the Future - see here for more details: http://news.dartlang.org/2012/11/introducing-new-streams-api.html
Note also, that Futures and Completers support generics, so you can strongly type the return type as follows:
// strongly typed future
Future<SomeReturnType> fDbSelectOneClient(String sKey) {
var completer = new Completer<SomeReturnType>();
completer.complete(new SomeReturnType());
}
foo() {
// strongly typed result
fDbSelectOneClient("Mr Blue").then((SomeReturnType result) => print(result));
}