Dart Future functions with callbacks on requests - asynchronous

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;
}
}

Related

Cannot use CancellationToken to end orchestrator function

I am using an orchestration to await two sub-orchestrations which are executing in parallel or a timeout of 5min, whatever comes first. I am passing a CancellationToken to the sub-orchestrators and therein throw on cancellation using token.ThrowIfCancellationRequested(). Also, since I have created a timer in the main orchestrator function, I am responsible to clear it in the case the timeout is not triggered itself. This is documented here.
main orchestrator:
public class Fxbm_workflow
{
[FunctionName(nameof(Fxbm_workflow))]
public async Task Run([OrchestrationTrigger] IDurableOrchestrationContext ctx, ILogger log)
{
log = ctx.CreateReplaySafeLogger(log);
var parentWorkflowId = ctx.InstanceId;
var trigger = ctx.GetInput<Trigger<OrchestrationInput2>>();
using var cts = new CancellationTokenSource();
var expireIn = ctx.CurrentUtcDateTime.AddMinutes(5);
var responseTasks = new [] {327, 41}.Select(id => {
var input = (id, cts.Token);
var childWorkflowId = $"{parentWorkflowId}_{nameof(Fxbm_receive_response_workflow)}_{id}";
return ctx.CallSubOrchestratorAsync<object>(nameof(Fxbm_receive_response_workflow), instanceId: childWorkflowId, input: input);
});
var timeoutTask = ctx.CreateTimer(expireIn, cts.Token);
// await tasks, choose winner
var winner = await Task.WhenAny(Task.WhenAll(responseTasks), timeoutTask);
if (winner == timeoutTask)
{
log.LogWarning("winner= timeout");
// no need to cts.Cancel() here
}
else
{
log.LogWarning("winner= all tasks have finished before the timeout");
cts.Cancel();
}
}
}
sub-orchestrator function:
public class Fxbm_receive_response_workflow
{
[FunctionName(nameof(Fxbm_receive_response_workflow))]
public async Task<object> Run([OrchestrationTrigger] IDurableOrchestrationContext ctx, ILogger log)
{
log = ctx.CreateReplaySafeLogger(log);
var (clientId, token) = ctx.GetInput<(int, CancellationToken)>();
token.ThrowIfCancellationRequested();
log.LogWarning($"waiting for response of clientId= {clientId}");
var evnt = await ctx.WaitForExternalEvent<object>(name: clientId.ToString());
log.LogWarning($"response received for clientId= {clientId}");
await ctx.CallActivityAsync(nameof(Fxbm_notifyOfNewReport), evnt);
return evnt;
}
}
However, this doesn't work. In the screenshot below there's the execution history extracted using DfMon. One of the two sub-orchestrations completes successfully. Before the other one can finish, the 5min timeout is triggered. Still, neither does the remaining sub-orchestration fail (due to token.ThrowIfCancellationRequested()), nor does the parent orchestration complete, inspite of calling cts.Cancel(). Both are still in a Running state.
It is my understanding the sub-orchestration should have failed/thrown and the parent orchestration should have concluded successfully. Does someone know why this is not the case here? Since orchestrator functions are re-executed again and again until finished, I do worry about unncessary $$$ cost of this when deployed in Azure.

Xamarin forms: Method is left after callunt await client.GetAsync

I am getting data from a server via Rest API. But Whenever i am waiting for the client response the Methos is left by the Debugger and the Program start loading the GUI even though at this point there is no Data to Display. Im already stuck for a couple of days on it. How can i make the Code to wait for the Response? Iam already using Await
My Method to get The Data: (Client Call in Line 8)
public async Task<ObservableCollection<Datensatz>> getDataFromAzure()
{
string URL = URLForContent;
_client = new HttpClient();
_client.DefaultRequestHeaders.Add("ApiKey", PW);
var result1 = await _client.GetAsync(URL, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
if (result1.StatusCode == System.Net.HttpStatusCode.OK)
{
var result = await result1.Content.ReadAsStringAsync();
var ContentFromJson = JsonConvert.DeserializeObject<ObservableCollection<Datensatz>>(result);
string json = JsonConvert.SerializeObject(ContentFromJson, Formatting.Indented);
var filename = #"data.json";
var destinatioPath = Path.Combine(Android.App.Application.Context.GetExternalFilesDir(null).ToString(), filename);
File.WriteAllText(destinatioPath, json);
App.Database_Main.FillMainDBWithJsonEntrys();
return ContentFromJson;
}
return null;
}
You can use the Wait method of the Task. Such as
Task result = getDataFromAzure()
result.Wait();
You can also use the Thread.sleep(1000) to make the main thread sleep for a while. But this will reduce the function of the application because we don't know how long time the async method need and if the time if more than 5 seconds will cause the ANR.

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.

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

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;
}

Sessions in dart

Usually the dart documentation has a lot of useful examples on almost any topic. Unfortunately I could not find anything on sessions in dart.
Could anyone validate this approach as a correct way to do sessions:
Browser sends GET request to sever.
Server responds with web-client.
Web-client sends user credentials.
a) Server checks credentials and generates session cookie.
b) Server sends session cookie back to client.
Web-client stores cookie for further use.
Web-client sends request for some user specific data, and attaches the cookie for verification.
My special interest lies in points 4, 5 and 6, since the others are well documented. If you could share some code snippets on this points, I would very much appreciate it.
EDIT:
After reading the comment from Günter Zöchbauer below I looked into shelf_auth. I realized that it requires rewriting the server app to use shelf.
So I did that.
The main.dart:
// imports of all necessary libraries
main() {
runServer();
}
/**
* Code to handle Http Requests
*/
runServer() {
var staticHandler = createStaticHandler(r"C:\Users\Lukasz\dart\auctionProject\web", defaultDocument: 'auctionproject.html');
var handler = new Cascade()
.add(staticHandler) // serves web-client
.add(routes.handler) // serves content requested by web-client
.handler;
io.serve(handler, InternetAddress.LOOPBACK_IP_V4, 8080).then((server) {
print('Listening on port 8080');
}).catchError((error) => print(error));
}
The routes.dart
import 'handlers.dart' as handler;
import 'package:shelf_route/shelf_route.dart';
import 'package:shelf_auth/shelf_auth.dart' as sAuth;
Router routes = new Router()
..get('/anonymous', handler.handleAnonymousRequest);
//..post('/login', handler.handleLoginRequest); << this needs to be implemented
//other routs will come later
The handlers.dart
import 'dart:async';
import 'dart:convert';
import 'dart:io' show HttpHeaders;
import 'databaseUtility.dart';
import 'package:shelf_exception_response/exception.dart';
import 'package:shelf/shelf.dart' as shelf;
import 'package:shelf_path/shelf_path.dart';
shelf.Response handleAnonymousRequest(shelf.Request request) {
return new shelf.Response.ok('got anonymous get request');
}
Unfortunately after reading the shelf_auth documentation I still don't quite know where to add the authentication. They use the Pipline syntax for the handler.
I'll describe how session works in Java with servlets. This could help you in making your implementation work. First off, I have to mention that session and authentication are two separate functions, although the latter depends on the former.
A session helps the server understand consecutive requests coming from the same browser without a big idle time in between. Take a look at the below example:
A user opened a browser A, visited your site
Kept clicking around various links using multiple tabs in browser A
Left the browser idle for 45 minutes
Continued clicking on the pages that he left open
Opened browser B, visited your site
Closed the tab for your website in browser B
Opened another new tab in browser B and clicked on a bookmark to
visit your site
Here is the impact on the server-side session for the above steps of the user:
New session created... let us say JSESSIONID 10203940595940
Same session applies for all requests from all tabs
Session expired on the server, and probably some memory was freed on server
Since Java is not able to locate a session matching JSESSIONID 10203940595940, it creates a new session and asks client to remember new JSESSIONID w349374598457
Requests from new browser are treated as new sessions because the JSESSIONID contract is between a single browser and the server. So, server assigns a new JSESSIONID like 956879874358734
JSESSIONID hangs around in the browser till browser is exited. Closing a tab doesnt clear the JSESSIONID
JSESSIONID is still used by browser, and if not much time elapsed, server would still be hanging on to that session. So, the sesison will continue.
Session use on the server-side:
A session is just a HashMap which maps JSESSIONIDs with another a
bunch of attributes.
There is a thread monitoring elapsed time for the sessions, and
removing JSESSIONIDs and the mapped attributes from memory once a
session expires.
Usually, there is some provision for applications to get an event
alert just when a session becomes ready for expiry.
Implementation details:
User's browser A sends a request to server. Server checks if there is
a Cookie by name JSESSIONID. If none is found, one is created on the
server. The server makes a note of the new JSESSIONID, the created
time, and the last request time which is the same as created time in
this case. In the HTTP response, the server attaches the new
JSESSIONID as a cookie.
Browsers are designed to keep attaching cookies for subsequent visits
to the same site. So, for all subsequent visits to the site, the
browser keeps attaching the JSESSIONID cookie to the HTTP request
header.
So, this time the server sees the JSESSIONID and is able to map the
request to the existing session, in case the session has not yet
expired. In case the session had already expired, the server would
create a new session and attach back the new JSESSIONID as a cookie
in HTTP response.
Authentication mechanisms just make use of the above session handling to detect "new sessions" and divert them to the login page. Also, existing sessions could be used to store attributes such as "auth-status" - "pass" or "fail".
Below is a small example of how this can be achieved (without client).
import 'dart:io';
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as io;
import 'package:shelf_router/shelf_router.dart';
import 'package:shelf_session/cookies_middleware.dart';
import 'package:shelf_session/session_middleware.dart';
import 'package:shelf_static/shelf_static.dart';
void main(List<String> args) async {
final router = Router();
router.get('/', _handleHome);
router.get('/login', _handleLogin);
router.get('/login/', _handleLogin);
router.post('/login', _handleLogin);
router.post('/login/', _handleLogin);
router.get('/logout', _handleLogout);
router.get('/logout/', _handleLogout);
final staticHandler =
createStaticHandler('../web', defaultDocument: 'index.html');
final handler = Cascade().add(staticHandler).add(router).handler;
final pipeline = const Pipeline()
.addMiddleware(logRequests())
.addMiddleware(cookiesMiddleware())
.addMiddleware(sessionMiddleware())
.addHandler(handler);
const address = 'localhost';
const port = 8080;
final server = await io.serve(pipeline, address, port);
print('Serving at http://${server.address.host}:${server.port}');
}
const _menu = '''
Home<br />
Log in<br />
Log out<br />''';
Future<Response> _handleHome(Request request) async {
final userManager = UserManager();
final user = userManager.getUser(request);
var body = '$_menu{{message}}<br />{{cookies}}';
if (user == null) {
body = body.replaceAll('{{message}}', 'You are not logged in');
} else {
body = body.replaceAll('{{message}}', 'You are logged in as ${user.name}');
}
final cookies = request.getCookies();
body = body.replaceAll('{{cookies}}',
cookies.entries.map((e) => '${e.key}: ${e.value}').join('<br />'));
request.addCookie(Cookie('foo', 'Foo'));
request.addCookie(Cookie('baz', 'Baz'));
return _render(body);
}
Future<Response> _handleLogin(Request request) async {
const html = '''
<form action="" method="post">
<label>Login</label><br />
<input name="login" type="text" /><br />
<label>Password</label><br />
<input name="password" type="password" /><br /><br />
<button>Log in</button>
</form>
''';
if (request.method == 'GET') {
return _render(_menu + html);
}
final body = await request.readAsString();
final queryParameters = Uri(query: body).queryParameters;
final login = queryParameters['login'] ?? ''
..trim();
final password = queryParameters['password'] ?? ''
..trim();
if (login.isEmpty || password.isEmpty) {
return _render(_menu + html);
}
final user = User(login);
final userManager = UserManager();
userManager.setUser(request, user);
return Response.found('/');
}
Future<Response> _handleLogout(Request request) async {
Session.deleteSession(request);
return Response.found('/');
}
Response _render(String body) {
return Response.ok(body, headers: {
'Content-type': 'text/html; charset=UTF-8',
});
}
class User {
final String name;
User(this.name);
}
class UserManager {
User? getUser(Request request) {
final session = Session.getSession(request);
if (session == null) {
return null;
}
final user = session.data['user'];
if (user is User) {
return user;
}
return null;
}
User setUser(Request request, User user) {
var session = Session.getSession(request);
session ??= Session.createSession(request);
session.data['user'] = user;
return user;
}
}
For client-server (password implies password hash):
Server code:
class ClientApi {
final _authenticate = createMiddleware(
requestHandler: (Request request) async {
final headers = request.headers;
final xAuthKey = headers['X-Auth-Key'];
if (xAuthKey is! String) {
return Response(401);
}
final xAuthEmail = headers['X-Auth-Email'];
if (xAuthEmail is! String) {
return Response(401);
}
final connection = await getConnection();
final statement = SelectStatement();
statement.fields.add('id');
statement.fields.add('password');
statement.tables.add('customers');
statement.where.add('id = ?');
final rows = await connection.query('$statement;', [xAuthEmail]);
for (final row in rows) {
final fields = row.fields;
final password = fields['password'] as String;
final apiKey = _getApiKey(password);
if (xAuthKey == apiKey) {
return null;
}
}
return Response(401);
},
);
Handler get handler {
final router = Router();
final routes = {
'login': _login,
};
for (final key in routes.keys) {
final value = routes[key]!;
router.post('/$key', const Pipeline().addHandler(value));
router.post('/$key/', const Pipeline().addHandler(value));
}
final routes2 = {
'add_to_cart': _addToCart,
};
for (final key in routes2.keys) {
final value = routes2[key]!;
router.post('/$key',
const Pipeline().addMiddleware(_authenticate).addHandler(value));
router.post('/$key/',
const Pipeline().addMiddleware(_authenticate).addHandler(value));
}
return router;
}
Future<Response> _login(Request request) async {
final params = await fromJson(request, LoginRequest.fromJson);
final connection = await getConnection();
final name = params.name.toLowerCase();
final statement = SelectStatement();
statement.tables.add('customers');
statement.fields.add('password');
statement.where.add('id = ?');
final rows = await connection.query('$statement;', [name]);
String? password;
for (final row in rows) {
final fields = row.fields;
password = fields['password'] as String;
break;
}
if (password != null && password == params.password) {
final apiKey = _getApiKey(password);
final user = LoginUser(
apiKey: apiKey,
name: name,
);
return toJson(LoginResponse(user: user).toJson());
}
return toJson(LoginResponse(user: null).toJson());
}
}
Client code:
class ClientApi {
void _addAuthHeaders(Map<String, String> headers) {
final user1 = UserService.user.value;
if (user1 != null) {
headers['X-Auth-Key'] = user1.apiKey;
headers['X-Auth-Email'] = user1.name;
}
}
Future<LoginResponse> login({
required String name,
required String password,
}) async {
final request = LoginRequest(
name: name,
password: password,
);
final json = await _post<Map>(
'login',
body: request.toJson(),
);
final response = LoginResponse.fromJson(json);
return response;
}
Future<T> _post<T>(
String path, {
Map<String, String>? headers,
Object? body,
}) async {
final url = Uri.parse('$_host/$path');
headers ??= {};
headers.addAll({
'Content-type': 'application/json',
});
final response = await http.post(
url,
body: jsonEncode(body),
headers: headers,
);
if (response.statusCode != HttpStatus.ok) {
throw StateError('Wrong response status code: ${response.statusCode}');
}
final json = jsonDecode(response.body);
if (json is! T) {
throw StateError('Wrong response');
}
return json;
}
}
Somewhere in the UI component.
void onClick(Event event) {
event.preventDefault();
final bytes = utf8.encode(password);
final digest = sha256.convert(bytes);
Timer.run(() async {
try {
final clientApi = ClientApi();
final response =
await clientApi.login(name: name, password: digest.toString());
final user = response.user;
if (user != null) {
UserService.user.value = User(apiKey: user.apiKey, name: user.name);
}
} catch (e) {
//
}
changeState();
});
}

Resources