Actionscript Wait For Asynchronous Event Within Function - actionscript-3

I need a little help with asynchronous events in ActionScript 3. I am writing a simple class that has two functions, both of which return strings(logic and code outlined below). Due to the asynchronous nature of the AS3 HTTPService, the return value line is always reached before a result is returned from the service, yielding an empty string. Is it possible to include some type of logic or statement in this function that will make it wait for a response before returning a value? Is there a framework that handles this type of stuff?
Call service
Parse JSON result, isolate value of interest
Return Value
public function geocodeLocation(address:String):Point
{
//call Google Maps API Geocode service directly over HTTP
var httpService:HTTPService = new HTTPService;
httpService.useProxy = false;
httpService.url = //"URL WILL GO HERE";
httpService.method = HTTPRequestMessage.GET_METHOD;
var asyncToken : AsyncToken = httpService.send();
asyncToken.addResponder( new AsyncResponder( onResult, onFault));
function onResult( e : ResultEvent, token : Object = null ) : void
{
//parse JSON and get value, logic not implemented yet
var jsonValue:String=""
}
function onFault( info : Object, token : Object = null ) : void
{
Alert.show(info.toString());
}
return jsonValue; //line reached before onResult fires
}

You should define onResult and onFault in your app – wherever you call geocodeLocation – and then pass them into your function as arguments after the address. Your onResult function will receive the data, parse the Point and do something with it. Your geocodeLocation function won't return anything.
public function geocodeLocation(address:String, onResult:Function, onFault:Function):void
{
//call Google Maps API Geocode service directly over HTTP
var httpService:HTTPService = new HTTPService;
httpService.useProxy = false;
httpService.url = //"URL WILL GO HERE";
httpService.method = HTTPRequestMessage.GET_METHOD;
var asyncToken : AsyncToken = httpService.send();
asyncToken.addResponder( new AsyncResponder( onResult, onFault));
}
And then in your app somewhere:
function onResult( e : ResultEvent, token : Object = null ) : void
{
var jsonValue:String=""
//parse JSON and get value, logic not implemented yet
var point:Point = new Point();
//do something with point
}
function onFault( info : Object, token : Object = null ) : void
{
Alert.show(info.toString());
//sad face
}
var address:String = "your address here";
geocodeLocation(address, onResult, onFault);
When the web service responds, control will pass either to your onResult function, where you will parse the Point and do something useful with it, or to your onFault function.
BTW you might run into problems calling the Google Maps geocoder this way, it's probably better to use the official SDK and take advantage of their code: http://code.google.com/apis/maps/documentation/flash/services.html

Related

How can I send custom object from my callable Firebase Cloud Function in TypeScript to my Unity app?

I'm trying to use Firebase and its callable Cloud Functions for my Unity project.
With the docs and different posts I found on the web I struggle to understand how returning data works. (I come from Azure Functions in C#)
I use TypeScript, and try to return a custom object CharactersResponse:
export class CharactersResponse //extends CustomResponse
{
Code!: CharactersCode;
CharacterID?: string;
}
export enum CharactersCode
{
Success = 0,
InvalidName = 2000,
CharacterNameAlreadyExists = 2009,
NoCharacterSlotAvailable = 3000,
InvalidCharacterClass = 4000,
EmptyResponse = 9000,
UnknownError = 9999,
}
(Custom Response is a parent class with only an UnknownErrorMessage string property, that I use for adding extra message when needed, but only in Unity. I don't need it in my functions.)
I have the same in my C# Unity Project:
public class CharactersResponse : CustomResponse
{
public CharactersCode Code;
public string CharacterID;
}
public enum CharactersCode
{
Success = 0,
InvalidName = 2000,
CharacterNameAlreadyExists = 2009,
NoCharacterSlotAvailable = 3000,
InvalidCharacterClass = 4000,
EmptyResponse = 9000,
UnknownError = 9999,
}
I'm still learning but I found it useful to do this way for displaying correct messages in Unity (and also regarding localization).
When the Code is 0 (Success), I will usually need to get some data at the same time like in this example CharacterID, or CharacterLevel, CharacterName etc.. CharacterResponse will be used for all functions regarding Characters like "GetAllCharacters", "CreateNewCharacter" etc..
My Function (CreateNewCharacter) looks like this:
import * as functions from "firebase-functions";
import { initializeApp } from "firebase-admin/app";
import { getFirestore } from "firebase-admin/firestore";
import { CharactersResponse } from "./CharactersResponse";
import { CharactersCode } from "./CharactersResponse";
import { StringUtils } from "../Utils/StringUtils";
// DATABASE INITIALIZATION
initializeApp();
const db = getFirestore();
// CREATE NEW CHARACTER
export const CreateNewCharacter =
functions.https.onCall((data, context) =>
{
// Checking that the user is authenticated.
if (!context.auth)
{
// Throwing an HttpsError so that the client gets the error details.
throw new functions.https.HttpsError('failed-precondition', 'The function must be called ' +
'while authenticated.');
}
// TEST
data.text = '';
// Authentication / user information is automatically added to the request.
const uid: string = context?.auth?.uid;
const characterName: string = data.text;
// Check if UserID is present
if (StringUtils.isNullOrEmpty(uid))
{
// Throwing an HttpsError so that the client gets the error details.
throw new functions.https.HttpsError('failed-precondition', 'Missing UserID in Auth Context.');
}
const response = new CharactersResponse();
if (StringUtils.isNullOrEmpty(characterName))
{
response.Code = CharactersCode.InvalidName;
console.log("character name null or empty return");
return response; // PROBLEM IS HERE *****************
}
console.log("end return");
return "Character created is named : " + characterName + ". UID = " + uid;
});
In Unity, the function call looks like this:
private static FirebaseFunctions functions = FirebaseManager.Instance.Func;
public static void CreateNewCharacter(string text, Action<CharactersResponse> successCallback, Action<CharactersResponse> failureCallback)
{
Debug.Log("Preparing Function");
// Create the arguments to the callable function.
var data = new Dictionary<string, object>();
data["text"] = text;
// Call the function and extract the operation from the result.
HttpsCallableReference function = functions.GetHttpsCallable("CreateNewCharacter");
function.CallAsync(data).ContinueWithOnMainThread((task) =>
{
if (task.IsFaulted)
{
foreach(var inner in task.Exception.InnerExceptions)
{
if (inner is FunctionsException)
{
var e = (FunctionsException)inner;
// Function error code, will be INTERNAL if the failure
// was not handled properly in the function call.
var code = e.ErrorCode;
var message = e.Message;
Debug.LogError($"Code: {code} // Message: {message}");
if (failureCallback != null)
{
failureCallback.Invoke(new CharactersResponse()
{
Code = CharacterCode.UnknownError,
UnknownErrorMessage = $"ERROR: {code} : {message?.ToString()}"
});
}
}
}
}
else
{
Debug.Log("About to Deserialize response");
// PROBLEM IS HERE *********************
CharactersResponse response = JsonConvert.DeserializeObject<CharactersResponse>(task.Result.Data.ToString());
Debug.Log("Deserialized response");
if (response == null)
{
Debug.LogError("Response is NULL");
}
else
{
Debug.Log("ELSE");
Debug.Log($"Response: {response}");
Debug.Log(response.Code.ToString());
}
}
});
}
The problem :
In my Unity C# code, task.Result.Data contains the CharactersCode I've set in my function, but I can't find a way to convert it to CharactersResponse. (It worked in Azure Functions). Moreover, the line just after Deserialization Debug.Log("Deserialized response"); is not executed. The code seems stuck in the deserialization process.
I tried with and without extending my TypeScript class with CustomResponse(because I don't need it in my Function so I didn't extended it at first).
I also tried setting a CharacterID because I thought maybe it didn't like the fact that this property was missing but the result is the same.
I don't understand what is the problem here? If any of you can help.
Thanks.
HttpsCallableResult.Data is of type object!
=> Your ToString will simply return the type name something like
System.Object
or in your case the result is a dictionary so it prints out that type.
=> This is of course no valid JSON content and not what you expected.
Simply construct the result yourself from the data:
var result = (Dictionary<string, object>)task.Result.Data;
CharactersResponse response = new CharactersResponse
{
Code = (CharactersCode)(int)result["Code"],
CharacterID = (string)result["CharacterID"];
};
I wanted to implement derHugo's solution but couldn't find a way to convert task.Result.Data to Dictionary<string, object>.
The code was stuck at var result = (Dictionary<string, object>)task.Result.Data; even in step by step debugging and no error popped up.
OLD SOLUTION:
So I did a little research and stumbled upon this post and ended up using this instead :
var json = JsonConvert.SerializeObject(task.Result.Data);
CharactersResponse response = JsonConvert.DeserializeObject<CharactersResponse>(json);
I basically convert the task.Result.Data to JSON and convert it back to CharactersResponse and it works. I have what I wanted.
However, I seem to understand that it is not the best solution performance-wise, but for now it is okay and I can now move forward in the project, I'll try to find a better solution later.
NEW SOLUTION:
I wanted to try one last thing, out of curiosity. I wondered what if I convert to JSON at the beginning (in my function) instead of at the end (in my Unity app). So I did this in my function's TypeScript code:
response.Code = CharactersCode.InvalidName;
var r = JSON.stringify(response); // Added this line
return r; // return 'r' instead of 'response'
In my C# code, I retried this line of code:
CharactersResponse response = JsonConvert.DeserializeObject<CharactersResponse>(task.Result.Data.ToString());
And it works ! I just needed to convert my object to JSON in my function before returning it. It allows me to "save" one line of code to process on the client side compared to the old solution.
Thanks derHugo for your answer as it helped me finding what I want.

GetStringAsync method not responding

I'm trying to get some custom columns values (longitude,latitude) from ASPNetUsers Table from the DB , When I send a Get request throw browser I get a 200 ok with the requested json .. but when I try to use GetStringAsync to deserialize the response in my xamarin app I don't get any response .
In AccountController class
// POST api/Account/GetUserPostion
[Route("GetUserPostion")]
public LocationDataToPostAsync GetUserPostion()
{
var store = new UserStore<ApplicationUser>(new ApplicationDbContext());
var manager = new ApplicationUserManager(store);
LocationDataToPostAsync locationData = new LocationDataToPostAsync();
var model = manager.FindById(User.Identity.GetUserId());
locationData.UserId = User.Identity.GetUserId();
if (model.Longitude != null) locationData.Longitude = (double) model.Longitude;
if (model.Latitude != null) locationData.Latitude = (double) model.Latitude;
return locationData;
}
In ApiService class in xamarin forms app
public async Task<LocationDataToPostAsync> GetUserLocationAsync(string accessToken)
{
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var json = await client.GetStringAsync("http://10.0.2.2:45455/api/Account/GetUserPostion");
var location = JsonConvert.DeserializeObject<LocationDataToPostAsync>(json);
return location;
}
It is unclear from your code if the Task is awaited or you are calling .Result or .GetAwaiter().GetResult() on the Task. However, as we found out in the comments adding .ConfigureAwait(false) fixed your issue.
This indicates that the code cannot return to the context it came from, so adding .ConfigureAwait(false) the code doesn't return to the context.
In your case the context is probably the UI thread and when it tries to return the UI thread is blocked.
The most likely scenario why the UI Thread is block is because you called your Task in a wrong manner. If you call it with .Result on the UI thread you are synchronously blocking the UI thread, hence anything that tries to return to the UI thread, will deadlock, since you are blocking that.
The easy fix here is to just add .ConfigureAwait(false) in your code. The better solution would be not to block the UI thread by awaiting the Task.

accessing dynamic property when calling Action....MVC

I have an Action method that returns JSON, for brevity, I excluded code. :
public ActionResult SetMasterLocation(string masterValue)
{
json = new JavaScriptSerializer().Serialize(masterLocation);
return Json(json, JsonRequestBehavior.AllowGet);
}
I need to call this method and access the JSON string that gets returned:
var jVendors = SetMasterLocation(masterValue);
When I run it and inspect the output, I see the JSON string in a dynamic property called Data:
But if I try to access data like this, the app will not compile because the compiler says Cannot resolve symbol 'Data':
var jVendors = SetMasterLocation(masterValue);
var data = jVendors.Data;
How do I access the Data property at runtime?
Return JsonResult
return new JsonResult()
{
Data = someData,
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
Then, you can access Data property of the result

How to parse a JSONP response and return a value using OpenLayers?

I am using OpenLayers and geoserver to build an app. very new to everything and this is my first app. Now I am trying to use the WMS getFeatureInfo to fetch feature information and show in popups when users click on a feature. To get around the cross domain issue, I am now trying to get JSONP response. the response i got is:
parseResponse({"type":"FeatureCollection","features":[{"type":"Feature","id":"Fire_Boundary_Pro.1","geometry":{"type":"MultiPolygon","coordinates":[[[[414495.86280000024,6451439.778],[414501.3269999996,6451437.0124],[414572.5887000002,6451444.5907],[414614.8359000003,6451368.1611],[414679.86149999965,6451410.5882],[414793.0769999996,6451376.6343],[414905.6501000002,6451419.4576],[414983.7874999996,6451315.405],[414978.77660000045,6451203.6776],[415021.0197999999,6451127.2464],[415051.8420000002,6450994.8769],[415029.2346000001,6450855.0812],[414899.8300999999,6450693.4524],[414882.8183000004,6450595.5852],[414776.48950000014,6450517.9117],[414747.5351999998,6450426.9246],[414688.4584999997,6450384.5476],[414605.3772,6450369.8903],[414568.95940000005,6450460.3295],[414555.8437000001,6450606.8071],[414473.11259999964,6450550.2695],[414468.34250000026,6450410.6221],[414433.15529999975,6450354.4835],[414350.7204999998,6450263.0455],[414273.40699999966,6450269.3751],[414076.47389999963,6450365.4401],[414061.89190000016,6450388.7117],[414037.87590000033,6450380.4262],[413891.39940000046,6450430.6506],[413934.48699999973,6450516.7853],[413948.07650000043,6450636.9786],[413961.37650000025,6450791.4776],[414092.2400000002,6450861.1987],[414153.67080000043,6450897.9731],[414179.43510000035,6450913.3962],[414281.23610000033,6450965.7158],[414279.7922,6451137.244],[414352.3854,6451189.3169],[414395.91280000005,6451223.991],[414350.94269999955,6451360.8451],[414495.86280000024,6451439.778]]]]},"geometry_name":"the_geom","properties":{"area":8.09003398112E-5,"Shape_Leng":4319.38797802,"Shape_Area":828429.079784}}]})
However I am not sure how to parse the JSONP response and obtain a property value. I am trying to use the OpenLayers.Format.JSON.read method (not sure if this is the right way to do it) but it returns an error that it is an undefined constructor. Here is my code:
map.events.register('click', map, function (e) {
document.getElementById('nodelist').innerHTML = "Loading... please wait...";
var params = {
REQUEST: "GetFeatureInfo",
EXCEPTIONS: "text/javascript",
BBOX: map.getExtent().toBBOX(),
SERVICE: "WMS",
//use JSONP format
INFO_FORMAT: 'text/javascript',
QUERY_LAYERS: map.layers[0].params.LAYERS,
FEATURE_COUNT: 50,
Layers: 'Bushfire_Com_Study:Fire_Boundary_Pro',
WIDTH: map.size.w,
HEIGHT: map.size.h,
format: format,
styles: map.layers[0].params.STYLES,
srs: map.layers[0].params.SRS,
// handle the wms 1.3 vs wms 1.1 madness
if(map.layers[0].params.VERSION == "1.3.0") {
params.version = "1.3.0";
params.j = parseInt(e.xy.x);
params.i = parseInt(e.xy.y);
} else {
params.version = "1.1.1";
params.x = parseInt(e.xy.x);
params.y = parseInt(e.xy.y);
}
// merge filters
if(map.layers[0].params.CQL_FILTER != null) {
params.cql_filter = map.layers[0].params.CQL_FILTER;
}
if(map.layers[0].params.FILTER != null) {
params.filter = map.layers[0].params.FILTER;
}
if(map.layers[0].params.FEATUREID) {
params.featureid = map.layers[0].params.FEATUREID;
}
OpenLayers.loadURL("http://localhost:8080/geoserver/Bushfire_Com_Study/wms", params, this, setHTML, setHTML);
OpenLayers.Event.stop(e);
});
// sets the HTML provided into the nodelist element
function setHTML(response){
var json_format = new OpenLayers.Format.JSON();
var object = json_format.read(response);
document.getElementById('nodelist').innerHTML = object.features[0].properties['area'];
};
An old question but I couldn't find an answer anywhere else. Most important sources for a solution were
http://dev.openlayers.org/docs/files/OpenLayers/Protocol/Script-js.html
and
http://docs.geoserver.org/stable/en/user/services/wms/vendor.html#wms-vendor-parameters.
My code contained things similar to the following.
// The Script protocol will insert the JSONP response in to the DOM.
var protocol = new OpenLayers.Protocol.Script({
url: someUrl,
callback: someCallbackFunction,
});
// GeoServer specific settings for the JSONP request.
protocol.callbackKey = 'format_options';
protocol.callbackPrefix = 'callback:';
// WMS parameters like in the question
var params={
REQUEST: "GetFeatureInfo",
EXCEPTIONS: "text/javascript",
INFO_FORMAT: 'text/javascript',
//etc
};
// Send the request.
protocol.read({
params: params
});
// Callback to handle the response.
function someCallbackFunction(response) {
for(var feature in response.features) {
// Do something with the returned features.
}
}

Not able to read nested properties from doPost(e)

I am trying to create a webservice using the Contentservice in Apps Script and doPost(e) function to interact with Google Apps AdminDirectory service
Here is the overview of my code. I saved this as my server and published it as a websapp
function doPost(e) {
var data = e.parameter;
var resourceType = data.resourceType;
var method = data.method;
var resource = data.resource;
var resourceParams = resource.parameters;
//other code to work with AdminDIrectory
// return ContentService.createTextOutput(myoutputdata).setMimeType(ContentService.MimeType.JSON);
}
In my client code which I wrote using Apps Script to test the webservice
function test() {
var jsonData = {
authKey : 'HwZMVe3ZCGuPOhTSmdcfOqsl12345678',
resourceType : 'user',
method : 'get',
resource : {
parameters : {
userKey : 'waqar.ahmad#mydomain.com'
}
}
}
var url = 'https://script.google.com/macros/s/xyzabc12345678_wE3CQV06APje6497dyI7Hh-mQMUFM0pYDrk/exec';
var params = {
method : 'POST',
payload : jsonData
}
var resp = UrlFetchApp.fetch(url, params).getContentText();
Logger.log(resp);
}
Now when I try to read e.parameter.resource.parameters on server side, it gives error and shows that e.parameter.resource is string type.
How I can read nested objects on server side? It seems, it is recognizing only first level parameters.
Using
function doPost(e) {
return ContentService.createTextOutput(JSON.stringify(e.parameter)).setMimeType(ContentService.MimeType.JSON);
You get the response
"{"authKey":"HwZMVe3ZCGuPOhTSmdcfOqsl12345678","resource":"{parameters={userKey=waqar.ahmad#mydomain.com}}","method":"get","resourceType":"user"}"
so it looks like it is flattening any nests into a string. In the comments of a similar problem it is noted that the documentation for UrlFetchApp permits the payload to be "It can be a String, a byte array, or a JavaScript key/value map", but I assume this doesn't extend to nested key/value maps.
As noted in the other answer the solution would be to stringify the payload e.g.
var params = {
method : 'POST',
payload : {'data':JSON.stringify(jsonData)}
};
I had problems handling the payload just as a string which is why I used a key value
On the server side script you can handle with
function doPost(e) {
var data = JSON.parse(e.parameter.data);
var userKey = data.resource.parameters.userKey;
...
}
Maybe it arrives as JSON string? If that's the case you'd have to parse it back-end, something like:
resourceObj = JSON_PARSE_METHOD(e.parameter.resource);
for example in PHP this would be
$resourceObj = json_decode(e->parameter->resource);
You could find out if it's a JSON string by printing its value (or debugging it) on the backend.
Hope this helps. Cheers