Dart: Use Futures to asynchronously fill a static var - dart

I have defined a static var as Map for all instances of my element. If it contains a specific key, it should use the value. If the key is not contains the instance should get the data with a request and save it in the static map, so other instances could use it.
static var data = new Map();
func() {
if (Elem.data.containsKey(['key']) {
list = Elem.data['key'];
}
else {
Helper.getData().then((requestedData) {
list = requestedData;
Elem.data.addAll({ 'key' : requestedData });
}
}
The Problem is that all my instances go into the else, since the key is not contained in the Map at the moment the other instances are at the if. So i need them to wait, until the Data is in the Map.

static var data = new Map();
static Completer _dataCompleter;
Future<bool> func() {
if(_dataCompleter == null) {
_dataCompleter = new Completer();
Helper.getData().then((requestedData) {
list = requestedData;
Elem.data.addAll({ 'key' : requestedData });
_dataCompleter.complete(true);
})
}
if(_dataCompleter.isCompleted) {
return new Future.value(true);
}
return _dataCompleter.future;
}
and call it like
func().then((success) => /* continue here when `key` in `data` has a value.

In response to Günter Zöchbauer. I generally avoid using Completers directly:
static var data = new Map();
static Future _pendingFuture;
Future func() {
if (_pendingFuture == null) {
_pendingFuture = Helper.getData().then((requestedData) {
list = requestedData;
Elem.data.addAll({ 'key' : requestedData });
});
}
return _pendingFuture;
}

Related

Conditional member access for map children?

Lets say you are trying to access deeply nested children in a map and you are not able to expect their parents to be there. Example:
Map awesomeMap = {
"this":{
"is":{
"sometimes":"not here"
}
}
}
Map notAwesomeMap = {
"this":{
"haha":{
"we":"switched"
}
}
}
When I go to access notAwesomeMap['this']['is']['sometimes'] it will return an error because ['this']['is'] is null, and you cannot look for the value ['sometimes'] of null.
So that's fine, but I was hoping to be able to use conditional member access operators...
notAwesomeMap['this']?.['is']?.['sometimes']
but that doesn't work...
Short of wrapping everything in a try block, is there a good way to handle these situations?
Edit: I tried playing around with this and I didn't find anything really illuminating, but maybe this gives someone an idea
void main() {
Map nestedMap = {
'this':{
'is':{
'sometimes':'here'
}
}
};
final mapResult = nestedMap['this'];
print(mapResult); //returns {is: {sometimes: here}}
final nullResult = nestedMap['this']['is an'];
print(nullResult); // returns null
final nullifiedResult = nullify(nestedMap['this']['is an']['error']);
print(nullifiedResult); // returns error, but is this possible another way?
final errorResult = nestedMap['this']['is an']['error'];
print(errorResult); // returns error
}
nullify(object){
try {
final result = object;
return result;
}
catch (e) {
return null;
}
}
One way would be
final result = (((nestedMap ?? const {})['this'] ?? const {})['is an'] ?? const {})['error'];
See also Null-aware operator with Maps
You could write a simple function to help do what you want:
R lookup<R, K>(Map<K, dynamic> map, Iterable<K> keys, [R defaultTo]);
Example usage:
final result = lookup(inputMap, ['this', 'is', 'something']);
Example implementation:
https://dartpad.dartlang.org/1a937b2d8cdde68e6d6f14d216e4c291
void main() {
var nestedMap = {
'this':{
'is':{
'sometimes':'here'
}
}
};
print(lookup(nestedMap, ['this']));
print(lookup(nestedMap, ['this', 'is']));
print(lookup(nestedMap, ['this', 'is', 'sometimes']));
print(lookup(nestedMap, ['this', 'is', 'error']));
// Bail out on null:
print(lookup(nestedMap, ['error'], 'Default Value'));
}
R lookup<R, K>(Map<K, dynamic> map, Iterable<K> keys, [R defaultTo]) {
dynamic current = map;
for (final key in keys) {
if (current is Map<K, dynamic>) {
current = current[key];
} else {
return defaultTo;
}
}
return current as R;
}
I like #matanlurey's approach, but have made two changes:
Drop the defaultTo since you can still use ?? which is more readable.
Swallow type-cast errors.
R lookup <R, K>(Map<K, dynamic> map, Iterable<K> keys) {
dynamic current = map;
for (final key in keys) {
if (current is Map<K, dynamic>) {
current = current[key];
}
}
try{
return current as R;
} catch(e){
// do nothing
}
}
Usage is similar
String someValue = lookup(nestedMap, ['some', 'value']) ?? 'Default Value';

how to return a string from a function which is listening some stream in dart?

i have a function called foo which is listening to the stdout, what i want is to return some string which i got from stdout. here is my function;
dynamic foo(process) {
return (
process.stdout.transform(UTF8.decoder).listen((data) {
String s = data.toString();
// print(s);
if (s.contains("received event of")) {
var s1 = s.split(":");
print("${s1[1]}");
return s1[1];
}
}));
}
I want to return s1 to the calling function
here a callback function do the trick
foo(process, callback) {
process.stdout.transform(UTF8.decoder).listen((data) {
String s = data.toString();
if (s.contains("received event of")) {
String message = s.split(":")[1];
callback(message);
}
});
}
and here i am calling the method and printing the data which i get get from stream.
foo(process,(data){print(data);})
This should do what you want
Future<String> dynamic foo(process) {
return process.stdout.transform(UTF8.decoder).map((data) {
String s = data.toString();
// print(s);
if (s.contains("received event of")) {
var s1 = s.split(":");
print("${s1[1]}");
return s1[1];
} else {
return null;
}
}).where((val) => val != null).first;
}
Your custom code either returns a valid value or null.
I changed listen to map to be able to use additional stream methods.
where filters invalid values (null) and returns the first non-null value.
The caller of the foo method needs to handle the returned Future (using for example async/await) to get the value when it becomes available.
Use it like
bar() async {
...
var input = await foo(proc);
print(input);
}
I think that everybody wants this:
import 'dart:io';
import 'dart:convert';
// Using system encoding:
var outputStr = await process.stdout.transform(systemEncoding.decoder).join();
// Using UTF-8 encoding:
var outputStr = await process.stdout.transform(utf8.decoder).join();

Questions about using Futures and Completers

Hey have I read all I can find about futures, but I would like some more advice on proper usage.
I am writing an API library, that bridges the gap between HTTP Requests and the app. So I use the future returned by HTTP in most cases, however sometimes the data is already retrieved. Is that the appropriate time to use a Completer?
ex.
String _someData = "";
Future<String> getSomeData(){
if (_someData == ""){
return Api.getSomeData().then((String someData){
_someData = someData;
return _someData;
});
} else {
var completer = new Completer();
completer.complete(_someData);
return completer.future;
}
}
-edit- Also if I create a Completer, but end up not using its future or calling complete. Will that cause a mem leak? Should I call its complete method or dispose of it somehow?
Thanks :)
You can use the named constructor Future.value if the value is immediately accessible. You won't need a Completer.
String _someData = "";
Future<String> getSomeData(){
if (_someData == ""){
return Api.getSomeData().then((String someData){
_someData = someData;
return _someData;
});
} else {
return new Future.value(_someData);
}
}
And for your second question, if you create a Completer without using it, I guess the garbage collector will simply free its memory when there won't be anymore references to it in your code.
Use an async function instead.
import "dart:async";
String _someData = "";
Future<String> getSomeData() async {
if (_someData == "") {
_someData = await Api.getSomeData();
}
return _someData;
}
Compiler generates approximately the following code:
import "dart:async";
String _someData = "";
Future<String> getSomeData() {
var $_awaiter = new Completer<String>();
try {
if (_someData == "") {
Api.getSomeData().then(($) {
$_awaiter.complete($);
}).catchError((e, s) {
$_awaiter.completeError(e, s);
});
return $_awaiter.future;
} else {
$_awaiter.complete(_someData);
return $_awaiter.future;
}
} catch (e, s) {
$_awaiter.completeError(e, s);
return $_awaiter.future;
}
}

How do I create a blank Future in Dart + how do I return a future currently in progress?

I'm trying to create a server-side Dart class that performs various data-related tasks. All of these tasks rely on the database having been first initialized. The problem is that the init of the database happens asynchronously (returns a Future). I first tried to put the init code into the constructor, but have given up on this approach as it seems to not be viable.
I am now attempting to figure out how to force the DB initialization as a first step in any method call that accesses data. So in other words, when attemptLogin() is called below, I'd like to first check if the DB has been initialized and initialize it if necessary.
However, there are two obstacles. If the database hasn't been initialized, the code is straightforward - initialize the db, then use the then() method of the returned future to do the rest of the function. If the db is not yet initialized, what do I attach my then() method to?
Second related question is what happens when a database is currently being initialized but this process is not yet complete? How can I pull in and return this "in-progress" Future?
This is the basic gist of the code I'm trying to wrangle:
class DataManager {
bool DbIsReady = false;
bool InitializingDb = false;
Db _db;
Future InitMongoDB() {
print("Initializing MongoDB");
InitializingDb = true;
_db = new Db("mongodb://127.0.0.1/test");
return _db.open().then((_) {
DbIsReady = true;
InitializingDb = false;
});
}
Future<List> attemptLogin(String username, String password) {
Future firstStep;
if ((!DbIsReady) && (!InitializingDb) {
Future firstStep = InitMongoDB()
}
else if (InitializingDb) {
// Need to return the InitMongoDB() Future that's currently running, but how?
}
else {
// How do I create a blank firstStep here?
}
return firstStep.then((_) {
users = _db.collection("users");
return // ... rest of code cut out for clarity
});
}
}
Thanks in advance for your help,
Greg
Just return
return new Future<bool>.value(true);
// or any other value instead of `true` you want to return.
// or none
// return new Future.value();
Just keep the future alive:
class DataManager {
Future _initializedDb;
Future initMongoDb() { ... }
Future<List> attemptLogin(String username, String password) {
if (_initializedDb == null) {
_initializedDb = initMongoDB();
}
return _initializedDb.then((db) {
users = db.collection("users");
return // ... rest of code cut out for clarity
});
}
}
You might need to pay attention for the error-case. It's up to you if you want to deal with errors in the initMongoDB or after it.
One of the possible solutions:
import "dart:async";
void main() {
var dm = new DataManager();
var selectOne = dm.execute("SELECT 1");
var selectUsers = dm.execute("SELECT * FROM users");
var users = selectOne.then((result) {
print(result);
return selectUsers.then((result) {
print(result);
});
});
users.then((result) {
print("Goodbye");
});
}
class Event {
List<Function> _actions = new List<Function>();
bool _raised = false;
void add(Function action) {
if (_raised) {
action();
} else {
_actions.add(action);
}
}
void raise() {
_raised = true;
_notify();
}
void _notify() {
if (_actions.isEmpty) {
return;
}
var actions = _actions.toList();
_actions.clear();
for (var action in actions) {
action();
}
}
}
class DataManager {
static const int _STATE_NOT_INITIALIZED = 1;
static const int _STATE_INITIALIZING = 2;
static const int _STATE_READY = 3;
Event _initEvent = new Event();
int _state = _STATE_NOT_INITIALIZED;
Future _init() {
if (_state == _STATE_NOT_INITIALIZED) {
_state = _STATE_INITIALIZING;
print("Initializing...");
return new Future(() {
print("Initialized");
_state = _STATE_READY;
_initEvent.raise();
});
} else if (_state == _STATE_INITIALIZING) {
print("Waiting until initialized");
var completer = new Completer();
_initEvent.add(() => completer.complete());
return completer.future;
}
return new Future.value();
}
Future execute(String query, [Map arguments]) {
return _init().then((result) {
return _execute(query, arguments);
});
}
Future _execute(String query, Map arguments) {
return new Future.value("query: $query");
}
}
Output:
Initializing...
Waiting until initialized
Initialized
query: SELECT 1
query: SELECT * FROM users
Goodbye
I think that exist better solution but this just an attempt to answer on your question (if I correctly understand you).
P.S. EDITED at 11 July 2014
Slightly modified (with error handling) example.
import "dart:async";
void main() {
var dm = new DataManager();
var selectOne = dm.execute("SELECT 1");
var selectUsers = dm.execute("SELECT * FROM users");
var users = selectOne.then((result) {
print(result);
return selectUsers.then((result) {
print(result);
});
});
users.then((result) {
print("Goodbye");
});
}
class DataManager {
static const int _STATE_NOT_INITIALIZED = 1;
static const int _STATE_INITIALIZING = 2;
static const int _STATE_READY = 3;
static const int _STATE_FAILURE = 4;
Completer _initEvent = new Completer();
int _state = _STATE_NOT_INITIALIZED;
Future _ensureInitialized() {
switch (_state) {
case _STATE_NOT_INITIALIZED:
_state = _STATE_INITIALIZING;
print("Initializing...");
new Future(() {
print("Initialized");
_state = _STATE_READY;
// throw null;
_initEvent.complete();
}).catchError((e, s) {
print("Failure");
_initEvent.completeError(e, s);
});
break;
case _STATE_INITIALIZING:
print("Waiting until initialized");
break;
case _STATE_FAILURE:
print("Failure detected");
break;
default:
print("Aleady intialized");
break;
}
return _initEvent.future;
}
Future execute(String query, [Map arguments]) {
return _ensureInitialized().then((result) {
return _execute(query, arguments);
});
}
Future _execute(String query, Map arguments) {
return new Future.value("query: $query");
}
}
For those that are still wondering how to create a blank Future in Dart and later complete them, you should use the Completer class like in the next example.
class AsyncOperation {
final Completer _completer = new Completer();
Future<T> doOperation() {
_startOperation();
return _completer.future; // Send future object back to client.
}
// Something calls this when the value is ready.
void finishOperation(T result) {
_completer.complete(result);
}
// If something goes wrong, call this.
void _errorHappened(error) {
_completer.completeError(error);
}
}
Future<Type> is non nullable in Dart, meaning that you have to initialize it to a value. If you don't, Dart throws the following error:
Error: Field should be initialized because its type 'Future<Type>' doesn't allow null.
To initialize a Future<Type>, see the following example:
Future<String> myFutureString = Future(() => "Future String");
Here "Future String" is a String and so the code above returns an instance of Future<String>.
So coming to the question of how to create a blank/empty Future, I used the following code for initializing an empty Future List.
Future<List> myFutureList = Future(() => []);
I found this link to be quite useful in understanding Futures in Flutter and Dart: https://meysam-mahfouzi.medium.com/understanding-future-in-dart-3c3eea5a22fb

How to implement dynamic properties in Dart?

I would like to be able to back a dynamic property with a Map using a lookup in noSuchMethod(). However the latest changes makes the incoming property reference name unavailable. I can understand the minification scenario requiring us to use Symbols rather than Strings for names, but this makes implementing serializable dynamic properties difficult. Anyone have good ideas on how to approach this problem?
I can't use String names since the String names are not fixed between calls to the minifier. (This would completely break serialization)
You can access the original name with MirrorSystem.getName(symbol)
So a dynamic class could look like :
import 'dart:mirrors';
class A {
final _properties = new Map<String, Object>();
noSuchMethod(Invocation invocation) {
if (invocation.isAccessor) {
final realName = MirrorSystem.getName(invocation.memberName);
if (invocation.isSetter) {
// for setter realname looks like "prop=" so we remove the "="
final name = realName.substring(0, realName.length - 1);
_properties[name] = invocation.positionalArguments.first;
return;
} else {
return _properties[realName];
}
}
return super.noSuchMethod(invocation);
}
}
main() {
final a = new A();
a.i = 151;
print(a.i); // print 151
a.someMethod(); // throws
}
You could do something like this:
import 'dart:json' as json;
main() {
var t = new Thingy();
print(t.bob());
print(t.jim());
print(json.stringify(t));
}
class Thingy {
Thingy() {
_map[const Symbol('bob')] = "blah";
_map[const Symbol('jim')] = "oi";
}
final Map<Symbol, String> _map = new Map<Symbol, String>();
noSuchMethod(Invocation invocation) {
return _map[invocation.memberName];
}
toJson() => {
'bob': _map[const Symbol('bob')],
'jim': _map[const Symbol('jim')]};
}
Update - dynamic example:
import 'dart:json' as json;
main() {
var t = new Thingy();
t.add('bob', 'blah');
t.add('jim', 42);
print(t.bob());
print(t.jim());
print(json.stringify(t));
}
class Thingy {
final Map<Symbol, String> _keys = new Map<Symbol, String>();
final Map<Symbol, dynamic> _values = new Map<Symbol, dynamic>();
add(String key, dynamic value) {
_keys[new Symbol(key)] = key;
_values[new Symbol(key)] = value;
}
noSuchMethod(Invocation invocation) {
return _values[invocation.memberName];
}
toJson() {
var map = new Map<String, dynamic>();
_keys.forEach((symbol, name) => map[name] = _values[symbol]);
return map;
}
}
If you only need "dynamic properties", it should be enough to use Symbols as keys in the Map. If you also want to serialize that map, then you need to keep track of the original String names and use those for serialization. When deserializing, you'd have to create new Symbols from those Strings.
Note that all these scenarios (and basically everything that involves new Symbol) require a compiler to create a mapping of original names to the minified ones and put this mapping into the program, which of course makes it bigger.
Thanks for the solution of #Alexandre Ardhuin, I made some modification to make it runnable.
import 'dart:mirrors';
class object {
final _properties = new Map<String, Object>();
object();
object.from(Map<String, Object> initial) {
initial.entries.forEach((element) => _properties[element.key] = element.value);
}
noSuchMethod(Invocation invocation) {
if (invocation.isAccessor) {
final realName = MirrorSystem.getName(invocation.memberName);
if (invocation.isSetter) {
// for setter realname looks like "prop=" so we remove the "="
final name = realName.substring(0, realName.length - 1);
_properties[name] = invocation.positionalArguments.first;
return;
} else {
return _properties[realName];
}
}
return super.noSuchMethod(invocation);
}
#override
String toString() {
return _properties.toString();
}
}
main() {
// we can't use var or object type here, because analysis will consider
// https://dart.dev/tools/diagnostic-messages#undefined_setter
// The setter 'i' isn't defined for the type 'object'
// So dynamic is required here!
dynamic a = object.from({'a': 123, 'b': 234});
a.i = 151;
print(a); // print {a: 123, b: 234, i: 151}
try {
a.someMethod(); // throws NoSuchMethodError
} catch (e) {
print(e);
}
}

Resources