is flutter (dart) able to make an api request in separate isolate? - dart

I made a function to post notification to a topic. It works great in normally, then I put it in compute function and hope it can posts notification in the background. But it not works.
Here is my code:
void onSendMessageInBackGround(String message) {
Future.delayed(Duration(milliseconds: 3000)).then((_) async{
Client client = Client();
final requestHeader = {'Authorization': 'key=my_server_key', 'Content-Type': 'application/json'};
var data = json.encode({
'notification': {
'body': 'tester',
'title': '$message',
},
'priority': 'high',
'data': {
'click_action': 'FLUTTER_NOTIFICATION_CLICK',
'dataMessage': 'test',
'time': "${DateTime.now()}",
},
'to': '/topics/uat'
});
await client.post('https://fcm.googleapis.com/fcm/send', headers: requestHeader, body: data);
});
}
call compute:
compute(onSendMessageInBackGround, 'abc');
Note: I has put the onSendMessageInBackGround function at the top level of my app as the library said
Is it missing something? or we can't do that?

You might need to add a return or await
void onSendMessageInBackGround(String message) {
return /* await (with async above) */ Future.delayed(Duration(milliseconds: 3000)).then((_) async{
It could be that the isolate shuts down before the request is made because you're not awaiting the Future

Function called from compute must be static or global.
Either i agree with pskink, compute here is not usefull.

Isolates communicate by passing messages back and forth. These messages can be primitive values, such as null, num, bool, double, or String, or simple objects such as the List in this example.
You might experience errors if you try to pass more complex objects, such as a Future or http.Response between isolates.
Got this from the documentation here

Related

How to log the response for a dart shelf request

I'm using the Dart Shelf package and I need to log the response it sends.
I've managed to log the request but the response technique is less clear:
final handler = const shelf.Pipeline()
.addMiddleware(corsHeaders())
.addMiddleware(shelf.logRequests(
logger: (message, isError) =>
_logRequest(message, isError: isError)))
.addHandler((req) async {
final res = await Router().call(req);
return res;
});
There two parts to the question.
how do I log the headers.
is it possible to log the body.
I know there is an issue in that the response body can only be read once.
As some of the responses are likely to be large I need to filter the requests for which the body is logged.
The answer is a bit of Dart-fu. You have an anonymous function returning an anonymous function.
var handler = const Pipeline()
.addMiddleware(
(handler) => (request) async {
final response = await handler(request);
print(response.headers);
// you could read the body here, but you'd also need to
// save the content and pipe it into a new response instance
return response;
},
)
.addHandler(syncHandler);

Wrong order of execution async handler

I have small app that execute requests to external service.
final app = Alfred();
app.post('/ctr', (req, res) async { // complex tender request
var data = await req.body;
await complexTendexAPIRequest(data as Map<String, dynamic>);
print('Hello World');
await res.json({'data': 'ok'});
});
Handler code:
complexTendexAPIRequest(Map<String, dynamic> data) async {
print('Request: $data');
try {
final response = await http.post(
Uri.parse(COMPLEX_URL),
headers: {'Content-Type': 'application/json', 'Authorization': 'bearer $ACCESS_TOKEN'},
body: json.encode(data)
);
if(response.statusCode == 200) {
var res = json.decode(response.body);
int latestId = res['id'];
String url = 'https://api.ru/v2/complex/status?id=$latestId';
stdout.write('Waiting for "complete" status from API: ');
Timer.periodic(Duration(seconds: 1), (timer) async {
final response = await http.get(
Uri.parse(url),
headers: {'Content-Type': 'application/json', 'Authorization': 'bearer $ACCESS_TOKEN'}
);
var data = json.decode(response.body);
if(data['status'] == 'completed') {
timer.cancel();
stdout.write('[DONE]');
stdout.write('\nFetching result: ');
String url = "https://api.ru/v2/complex/results?id=$latestId";
final response = await http.get(
Uri.parse(url),
headers: {'Content-Type': 'application/json', 'Authorization': 'bearer $ACCESS_TOKEN'}
);
stdout.write('[DONE]');
var data = prettyJson(json.decode(response.body));
await File('result.json').writeAsString(data.toString());
print("\nCreating dump of result: [DONE]");
}
});
}
else {
print('[ERROR] Wrong status code for complex request. StatusCode: ${response.statusCode}');
}
}
on SocketException catch(e) {
print('No Internet connection: $e');
} on TimeoutException catch(e) {
print('TenderAPI Timeout: $e');
} on Exception catch(e) {
print('Some unknown Exception: $e');
}
}
But output is very strange it's look like it's do not waiting complexTendexAPIRequest completion and go forward:
Waiting for "complete" status from API: Hello World
[DONE]
Fetching result: [DONE]
Creating dump of result: [DONE]
But should be:
Waiting for "complete" status from API: [DONE]
Fetching result: [DONE]
Creating dump of result: [DONE]
Hello World
I suppose that reason can be in Timer.periodic but how to fix it to get expected order and execution of:
print('Hello World');
await res.json({'data': 'ok'});
only after complexTendexAPIRequest completed.
upd: I rewrote code to while loop:
https://gist.github.com/bubnenkoff/fd6b4f0d7aeae7007680e7902fbdc1e9
it's seems that it's ok.
Alfred https://github.com/rknell/alfred
The problem is the Timer.periodic, as others have pointed out.
You do:
Timer.periodic(Duration(seconds: 1), (timer) async {
// do something ...
});
That sets up a timer, then immediately continues execution.
The timer triggers every second, calls the async callback (which returns a future that no-one ever waits for) and which does something which just might take longer than a second.
You can convert this to a normal loop, basically:
while (true) {
// do something ...
if (data['status'] == 'completed') {
// ...
break;
} else {
// You can choose your own delay here, doesn't have
// to be the same one every time.
await Future.delayed(const Duration(seconds: 1));
}
}
If you still want it to be timer driven, with fixed ticks, consider rewriting this as:
await for (var _ in Stream.periodic(const Duration(seconds: 1))) {
// do something ...
// Change `timer.cancel();` to a `break;` at the end of the block.
}
Here you create a stream which fires an event every second. Then you use an await for loop to wait for each steam event. If the thing you do inside the loop is asynchronous (does an await) then you are even ensured that the next stream event is delayed until the loop body is done, so you won't have two fetches running at the same time.
And if the code throws, the error will be caught by the surrounding try/catch, which I assume was intended, rather than being an uncaught error ending up in the Future that no-one listens to.
If you want to retain the Timer.periodic code, you can, but you need to do something extra to synchronize it with the async/await code around it (which only really understands futures and streams, not timers).
For example:
var timerDone = Completer();
Timer.periodic(const Duration(seconds: 1), (timer) async {
try {
// do something ...
// Add `timerDone.complete();` next to `timer.cancel()`.
} catch (e, s) {
timer.cancel();
timerDone.completeError(e, s);
}
});
await timerDone.future;
This code uses a Completer to complete a future and effectively bridge the gap between timers and futures that can be awaited (one of the listed uses of Completer in the documentation).
You may still risk the timer running concurrently if one step takes longer than a second.
Also, you can possibly use the retry package, if it's the same thing you want to retry until it works.

are these two asyncrounous functions indentical?

I have a function defined by:
static Future<http.Response> checkToken(token) async {
return await http.post("my-url", headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
}, body: json.encode({
"token": token
})).timeout(const Duration (seconds:5), onTimeout : () => null);
}
I wonder if this function is identical to this function:
static Future<http.Response> checkToken(token) {
return http.post("my-url", headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
}, body: json.encode({
"token": token
})).timeout(const Duration (seconds:5), onTimeout : () => null);
}
In the second definition I removed async / await part, cause on the web I found this statement:
Tip: If a function returns a Future, it’s considered asyncrounous; you do not need to mark the body of this function with async keyword. The async keyword is necessary only if you have used the await keyword in the body of your function.
Are these two functions identical?
It's true that the async keyword is not what makes a function asynchronous. The two versions of your function therefore are both asynchronous, and they're mostly equivalent but are subtly not identical.
The first version (which uses await) waits for a Future to complete and returns a new Future. The second version just returns the original Future directly.
Let's simplify the example:
final someFuture = Future.value(42);
Future<int> returnsFuture() => someFuture;
Future<int> awaitsFuture() async => await someFuture;
void main() {
final same = identical(returnsFuture(), awaitsFuture());
print('Identical Futures: $same'); // Prints: Identical Futures: false
}
In practice this usually shouldn't matter. Two cases where it might are:
Your code is somehow sensitive to specific Future instances.
await always yields, so the extra await could very slightly delay execution.
Since your asynchronously method does not contain the keyword await, then the two methods is indeed identical.

How to mock HttpClientResponse to return a String

I am trying to write a test after refactoring to dart:io.HttpClient following https://flutter.io/networking/
Everything seems to work well up until
var responseBody = await response.transform(utf8.decoder).join();
The following test throws a NoSuchMethodError: The method 'join' was called on null.
MockHttpClient http = new MockHttpClient();
MockHttpClientRequest request = new MockHttpClientRequest();
MockHttpHeaders headers = new MockHttpHeaders();
MockHttpClientResponse response = new MockHttpClientResponse();
MockStream stream = new MockStream();
when(http.getUrl(Uri.parse('http://www.example.com/')))
.thenReturn(new Future.value(request));
when(request.headers)
.thenReturn(headers);
when(request.close())
.thenReturn(new Future.value(response));
when(response.transform(utf8.decoder))
.thenReturn(stream);
when(stream.join())
.thenReturn(new Future.value('{"error": {"message": "Some error"}}'));
I did see How to mock server response - client on server side, but that uses the http package, not dart:io.
I also tried https://github.com/flutter/flutter/blob/master/dev/manual_tests/test/mock_image_http.dart but that also returns a null.
Much thanks in advance!
The problem is that when you mock stream you actually need to implement a ton of different methods to get it to work properly. It is better to use a real Stream if you can like in the example in the flutter repo. To make sure your body is correctly set, use the utf8 encoder.
final MockHttpClientResponse response = new MockHttpClientResponse();
// encode the response body as bytes.
final List<int> body = utf8.encode('{"foo":2}');
when(response.listen(typed(any))).thenAnswer((Invocation invocation) {
final void Function(List<int>) onData = invocation.positionalArguments[0];
final void Function() onDone = invocation.namedArguments[#onDone];
final void Function(Object, [StackTrace]) onError = invocation.namedArguments[#onError];
final bool cancelOnError = invocation.namedArguments[#cancelOnError];
return new Stream<List<int>>.fromIterable(<List<int>>[body]).listen(onData, onDone: onDone, onError: onError, cancelOnError: cancelOnError);
});

Wait while request is running

Here is a problem. When I ran these code:
String responseText = null;
HttpRequest.getString(url).then((resp) {
responseText = resp;
print(responseText);
});
print(responseText);
In console:
{"meta":{"code":200},"data":{"username":"kevin","bio":"CEO \u0026 Co-founder of Instagram","website":"","profile_picture":"http:\/\/images.ak.instagram.com\/profiles\/profile_3_75sq_1325536697.jpg","full_name":"Kevin Systrom","counts":{"media":1349,"followed_by":1110365,"follows":555},"id":"3"}}
null
It running asynchronously. Is there JAVA way with synchronized method? That will be await while request is done?
I found only one tricky way to do it and its funny -- wait for three seconds:
handleTimeout() {
print(responseText);
}
const TIMEOUT = const Duration(seconds: 3);
new Timer(TIMEOUT, handleTimeout);
And of course it works with bugs. So any suggestions?
MattB way work well:
var req = new HttpRequest();
req.onLoad.listen((e) {
responseText = req.responseText;
print(responseText);
});
req.open('GET', url, async: false);
req.send();
First, I'm assuming you're using this as a client-side script and not server-side. Using HttpRequest.getString will strictly return a Future (async method).
If you absolutely must have a synchronous request, you can construct a new HttpRequest object and call the open method passing the named parameter: async: false
var req = new HttpRequest();
req.onLoad.listen((e) => print(req.responseText));
req.open('GET', url, async: false);
req.send();
However it is highly recommended that you use async methods for accessing network resources as a synchronous call like above will cause the script to block and can potentially make it appear as though your page/script has stopped responding on poor network connections.

Resources