Loading a file using rootBundle - dart

I need to load a string from a file. The following code always returns null:
static String l( String name ) {
String contents;
rootBundle
.loadString( 'i10n/de.yaml' )
.then( (String r) { contents = 'found'; print( 'then()' ); })
.catchError( (e) { contents = '#Error#'; print( 'catchError()' ); })
.whenComplete(() { contents = 'dd'; print( 'whenComplete()' ); })
;
print( 'after' );
if ( null == contents ) {
return '#null#';
}
String doc = loadYaml( contents );
return doc;
}
I have added this to the flutter: section in pupspec.yaml section:
assets:
- i10n/de.yaml
- i10n/en.yaml
The file i10n/de.yaml exists.
I'm aware, that rootBundle.loadString() is async. Therefore I appended the then() call - assuming that
(String r) { contents = 'found'; }
only gets executed if the Future returned by rootBundle.loadString() is able to return a value.
Actually, the method always returns '#null#'. Therefore, I added the print() statements, which output this:
I/flutter (22382): after
I/flutter (22382): then()
I/flutter (22382): whenComplete()
OK, obviously the future of loadString() executes later than the final print() statement.
Q: But how do I force the future to execute, so that I may retrieve its value?
In other words: How do I wrap some async stuff in certain code to retrieve its value immediately?
PS: First day of flutter/dart. Probably a trivial question...

the .then() is getting executed, but after the rest of the body. As you mention loadString() returns a Future, so completes in the future. To wait for a Future to complete use await. (Note that when you mark the function as async, the function must now return a Future itself - as it has to wait for loadString to complete in the future, so it itself has to complete in the future...) When you call l('something') you will have to await the result.
Future<String> l(String name) async {
try {
String contents = await rootBundle.loadString('i10n/de.yaml');
return contents == null ? '#null#' : loadYaml(contents);
} catch (e) {
return 'oops $e';
}
}
It's no big deal that lots of your utility functions become async as a result of having to wait for things (there's a lot of waiting - for files to read, http requests to complete, etc). You'll eventually end up at the top with something like (call this from initState)
refresh(String s) {
l(s).then((r) {
setState(() {
i18nStuff = r;
});
});
}
to set the state of your Widget when the i18nStuff is ready, and a Widget that has this in its build to switch between a dummy ui for the few milliseconds until it's ready, and then the real UI.
Widget build() {
if (i18nStuff == null) {
return new Container();
}
return new Column(
// build the real UI here
);
}

Related

Riverpod with hooks beaks when widget is disposedI

I have Flutter mobile app that is using Riverpod with hooks.
I have the following function that I would like to be called when the widget is disposed:
useEffect(
() {
final firestoreRepo =
ref.read(firebaseFirestoreRepositoryProvider);
return () async {
try {
// I get exception at this line.
// I need this future to be called when the
// widget is disposed.
// Calling this future earlier is not userful
// for my business logic.
final relationship =
await ref.read(relationshipWithProvider(pid).future);
if (relationship?.unseen ?? false) {
await firestoreRepo?.updateRelatinoship(pid: pid);
}
} catch (e, st) {
// print error
}
};
},
[],
);
I keep getting this error at the line shown in the comment above.
I/flutter ( 5967): Looking up a deactivated widget's ancestor is unsafe.
I/flutter ( 5967): At this point the state of the widget's element tree is no longer stable.
How can I sold this problem
We can initially get our relationship and then await and use it:
useEffect(
() {
final firestoreRepo = ref.read(firebaseFirestoreRepositoryProvider);
final relationship = ref.read(relationshipWithProvider(pid).future);
return () async {
try {
if (await relationship?.unseen ?? false) {
await firestoreRepo?.updateRelatinoship(pid: pid);
}
} catch (e, st) {
// print error
}
};
},
[],
);
As far as I can tell, this won't contradict the logic of the business process, because one way or another, we'll have to make the relationshipWithProvider(pid) request early (when we initialize the widget) or late (when we delete the widget).

Mapping a Stream<List> to another type is returning a Stream<Null>

I'm trying to transform a Stream of a list of one type into a Stream of a list of another type, and having an issue with this.
I have this list of Habits that I'm streaming from Firebase, and I want to accept that stream in a function, and return a new stream that is a list of ViewModels of another type from it. But my function is returning a stream of the wrong type.
Here is my code:
Stream<List<HabitCompletionViewModel>> _getTodaysHabits(
Stream<List<Habit>> habitsStream) {
var result = habitsStream.map((habitsList) {
habitsList.map(
(habit) async {
await _getHabitCompletionsCurrent(habit);
HabitCompletion completion = habit.completions!.firstWhere(
(completion) => completion.date
.dayEqualityCheck(DateTime.now().startOfDate()));
return HabitCompletionViewModel(completion: completion, habit: habit);
},
).toList();
});
return result;
}
I am getting a compile error because the result variable is showing as type Stream<Null> when I hover over it, where I would expect it to be Stream<List<HabitCompletionViewModel>>. Any idea what I'm doing wrong?
Your outer .map call does not have a return statement which is why you are getting a Stream<Null>.
So add a return statement like so:
Stream<List<HabitCompletionViewModel>> _getTodaysHabits(
Stream<List<Habit>> habitsStream) {
var result = habitsStream.map((habitsList) {
// added return statement here
return habitsList.map(
(habit) async {
await _getHabitCompletionsCurrent(habit);
HabitCompletion completion = habit.completions!.firstWhere(
(completion) =>
completion.date.dayEqualityCheck(DateTime.now().startOfDate()));
return HabitCompletionViewModel(completion: completion, habit: habit);
},
).toList();
});
return result;
}
However the above code still has an error because it is now returning a Stream<List<Future<HabitCompletionViewModel>>> instead of the desired Stream<List<HabitCompletionViewModel>>. To solve this you can use .asyncMap instead of .map.
Stream<List<HabitCompletionViewModel>> _getTodaysHabits(
Stream<List<Habit>> habitsStream) {
var result = habitsStream.asyncMap((habitsList) {
return Stream.fromIterable(habitsList).asyncMap(
(habit) async {
await _getHabitCompletionsCurrent(habit);
HabitCompletion completion = habit.completions!.firstWhere(
(completion) =>
completion.date.dayEqualityCheck(DateTime.now().startOfDate()));
return HabitCompletionViewModel(completion: completion, habit: habit);
},
).toList();
});
return result;
}

Neo4j transaction with array of queries with js-neo4j-driver

I want to pass an n-number of cypher-queries to a neo4j-transaction and I am thinking about a good approach.
At the moment I have a working approach that takes the array-item or if it is not available a dummy-query. (Code below)
I believe this is not best-practice. Does anybody know or have an idea how this can be done better?
function Neo4jTransaction(QueryArray) {
const session = driverWrite.session();
const tx = session.beginTransaction();
tx.run(QueryArray[0] || "RETURN 0")
tx.run(QueryArray[1] || "RETURN 0")
tx.run(QueryArray[2] || "RETURN 0")
tx.run(QueryArray[3] || "RETURN 0")
.then(result => {
return tx.commit()
}).then(() => {
session.close()
driverWrite.close()
}).catch(exception => {
console.log(exception)
session.close()
driverWrite.close()
})
}
First, if you have an array, you might want to iterate over it. Second, tx.run() returns a Promise that you need to catch if it fails. In your code, it is called 4 times in a row, but only the last one waits for the result and catches the error. I looks like some lines of the code are missing.
neo4j-driver documentation gives a good example on explicit transactions: https://github.com/neo4j/neo4j-javascript-driver#explicit-transactions
The queries are executed sequentially. If one fails the whole transaction will be rolled back.
async function neo4jTransaction(queryArray) {
const session = driver.session();
const txc = session.beginTransaction();
try {
for (const query of queryArray) {
await txc.run(query || 'RETURN 0');
}
await txc.commit();
} catch (e) {
await txc.rollback();
return Promise.reject(e);
} finally {
await session.close();
}
}

Execute Futures until a parameter becomes true

I launch a request to a server with a future "requestServer".
I would like to poll a system for a specific value (passed from false to true, when request is done) and return when finished.
Code could be like that, but "while" synchronous and "checkOperation" is asynchronous?
return requestServer().then((operation) {
var done = false;
while (done)
return checkOperation(operation).then((result) {
done = (result == true);
});
sleep(10);
}
});
Any ideas ?
I guess this is not exactly what you want but as far as I know there is no way to block execution so you have to use callbacks.
void main(List<String> args) {
// polling
new Timer.periodic(new Duration(microseconds: 100), (t) {
if(isDone) {
t.cancel();
someCallback();
}
});
// set isDone to true sometimes in the future
new Future.delayed(new Duration(seconds: 10), () => isDone = true);
}
bool isDone = false;
void someCallback() {
print('isDone: $isDone');
// continue processing
}
You can of course pass the callback as parameter instead of hardcode it, because functions are first class members in Dart.
Polling doesn't work very well for async. It is better to wait for a signal from the thing that must complete.
Günter Zöchbauer's answer shows you how to poll anyway, by sampling with a timer.
As an alternative, it would be better to not have a boolean done, but instead complete another future when you are ready. This is busy-polling, which polls again as soon as a result comes back, which may be more intensive than you need. Using timer based polling can be more efficient if you don't need the result as soon as possible.
return requestServer().then((operation) {
var completer = new Completer();
void poll(result) {
if (!result) {
operation.then(poll, onError: completer.completeError);
} else {
completer.complete();
}
}
poll(false);
return completer.future;
});
(Code not really tested, since I don't have your requestServer).
When you want build functions that return Futures, it is sometimes useful to use Completers. Think that requestServer() is living in the Future too, so you will have threat the result as a Future.
return requestServer().then((operation) {
// This is necessary then you want to control async
// funcions.
Completer completer = new Completer();
//
new Timer.periodic(const Duration(seconds: 10), (_) {
checkOperation(operation).then((result) {
// Only when the result is true, you pass the signal
// that the operation has finished.
// You can alse use `completer.complete(result)` if you want
// to pass data inside of the future.
if (result == true) completer.complete();
});
});
// You return the future straight away.
// It will be returned by requestServer();
return completer.future;
});
I use a function like this in a TestUtil library:
static Future<bool> waitUntilTrue(bool Function() callback,
{Duration timeout: const Duration(seconds: 2),
Duration pollInterval: const Duration(milliseconds: 50)}) {
var completer = new Completer<bool>();
var started = DateTime.now();
poll() {
var now = DateTime.now();
if (now.difference(started) >= timeout) {
completer.completeError(Exception('timed out in waitUntilTrue'));
return;
}
if (callback()) {
completer.complete(true);
} else {
new Timer(Duration(milliseconds: 100), () {
poll();
});
}
}
poll();
return completer.future;
}
And then in my test code I'll do something like:
await TestUtil.waitUntilTrue(() => someObj.isDone);
Edit:
Note that if you're using this in a testWidgets test, you have to do a little extra, since it relies on real async work happening:
await tester.runAsync<bool>(
() => TestUtil.waitUntilTrue(() => myObj.isLoaded),
additionalTime: Duration(seconds: 5));

How can I access the result of the response of HttpRequest in Dart?

After many attempts to get the content of the response in HttpRequest, I failed completely to know or understand why I can't have what I want, and I must mention that I can log and manipulate the response only inside an onReadyStateChange (onLoad and onLoadEnd are giving me the same results!), but I really want that value outside the callback.
Here is the part of code that I'm stuck with
Map responsData;
req=new HttpRequest()
..open(method,url)
..send(infojson);
req.onReadyStateChange.listen((ProgressEvent e){
if (req.readyState == HttpRequest.DONE ){
if(req.status == 200){
responsData = {'data': req.responseText};
print("data receaved: ${ req.responseText}");
//will log {"data":mydata}
}
if(req.status == 0){
responsData = {'data':'No server'};
print(responsData );
//will log {"data":No server}
}
}
});
//anything here to get responsData won't work
You have to assign an onLoad callback before you call send.
I'm not sure what you mean with only inside an onReadyStateChange.
Maybe you want to assign the responseText to a variable outside the the callback.
Create a method:
Future<String> send(String method, String url, String infojson) {
var completer = new Completer<String>();
// var result;
req=new HttpRequest()
..open(method,url)
..onLoad.listen((event) {
//print('Request complete ${event.target.reponseText}'))
// result = event.target.responseText;
completer.complete(event.target.responseText);
})
..send(infojson);
return completer.future;
}
and call this method like
var result;
send(method, url).then(
(e) {
// result = e;
print('Request complete ${e}'));
});

Resources