Dialog in Wi-Fi Network Request API - android-wifi

I'm using the new API 29 for connecting the device to a wifi network in an Android 10 device:
private val connectivityManager: ConnectivityManager by inject()
override fun connectToNetwork(ssid: String, password: String) {
val networkRequest = buildNetworkRequest(ssid, password)
val networkCallback =
object : NetworkCallback() {
override fun onAvailable(network: Network) {
super.onAvailable(network)
Timber.d("Connected to network $ssid")
}
override fun onUnavailable() {
super.onUnavailable()
Timber.e("Unable to connect to network $ssid")
}
}
connectivityManager.requestNetwork(networkRequest, networkCallback, CONNECTION_TIME_OUT)
}
private fun buildNetworkRequest(ssid: String, password: String) =
NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.setNetworkSpecifier(buildWifiNetworkSpecifier(ssid, password))
.build()
private fun buildWifiNetworkSpecifier(ssid: String, password: String) =
WifiNetworkSpecifier.Builder()
.setSsid(ssid)
.setWpa2Passphrase(password)
.build()
A dialog appears with a "Device to use with " with the specified wifi network listed. The dialog has two buttons for "cancel" and "connect". When I click "connect", the device connects to the wifi network (I can see that in the system settings) and the connect button is disabled.
But the dialog does not go away and none of the methods in the requestNetwork callback is invoked. Eventually I reach the specified timeout and another dialog comes with "Something came up. The application has cancelled the request to choose a device".
What is happening here? I want to connect to a network and have the 'onAvailable' or 'onUnavailable' methods in the callback invoked.

After trying almost everything, I finally managed to make this work as expected. Don't know why, but when I went to the device's system settings and forgot the wifi network that I was trying to connect programatically, this started to work.
I'm glad I got rid of the problem, but I don't know what was causing it, and it's a risk that the same thing happens to a future user.

I had the same problem. By trial and error, I found out that this is due to the fact that the request occurs earlier than the network is finally connected. If you send a request after network availability, then everything works. Here is an example of code that solved my problem:
override fun onAvailable(network: Network) {
GlobalScope.launch(Dispatchers.IO) {
while (!isNetworkAvailable(ctx)) {
delay(200)
}
launch(Dispatchers.Main) {
// begin request....
}
}
cm.unregisterNetworkCallback(this)
}
and
private fun isNetworkAvailable(ctx: Context): Boolean {
val connectivityManager = ctx.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val nw = connectivityManager.activeNetwork ?: return false
val actNw = connectivityManager.getNetworkCapabilities(nw) ?: return false
return actNw.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
} else {
val nwInfo = connectivityManager.activeNetworkInfo ?: return false
return nwInfo.isConnected
}
}
Also, before connecting, you have to clear ssid for the correct connection:
private fun removeSsid(ctx : Context, ssid : String, pass : String){
val suggestion = WifiNetworkSuggestion.Builder()
.setSsid(ssid)
.setWpa2Passphrase(pass)
.build()
val wifiManager = ctx.getSystemService(WIFI_SERVICE) as WifiManager
val sList = ArrayList<WifiNetworkSuggestion>()
sList.add(suggestion)
wifiManager.removeNetworkSuggestions(sList)
}

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.

Can I wrap callback function into another to return a value from it?

I have a request to a server (I am using VK SDK). So I want to move all the stuff to a different place, then call it and get response back.
Here is what I have now:
object GetAlbumsService {
fun getAlbums():GetAlbumsResponse{
lateinit var responseObject: GetAlbumsResponse
val request = VKRequest("photos.getAlbums", VKParameters.from("need_system", "1"))
request.executeWithListener(object: VKRequest.VKRequestListener(){
override fun onComplete(response: VKResponse?) {
responseObject = Gson().fromJson(response?.json.toString(), GetAlbumsResponse::class.java)
}
})
return responseObject
}
}
But responseObject remains null. Am I right that onComplete function have no time to fill responseObject? If so, what can I do?
You can't return the result of an asynchronous callback from the function that initiates the background work. But you can allow the calling function to pass a callback.
fun getAlbums(onComplete: (GetAlbumsResponse) -> Unit) {
val request = VKRequest("photos.getAlbums", VKParameters.from("need_system", "1"))
request.executeWithListener(object: VKRequest.VKRequestListener(){
override fun onComplete(response: VKResponse?) {
responseObject = Gson().fromJson(response?.json.toString(), GetAlbumsResponse::class.java)
onComplete(responseObject)
}
})
}
Then from where you call this:
GetAlbumsService.getAlbums { response ->
// Do something with response
}
The code inisde the lambda is called when the result is ready.
You can also look into using coroutines, but that's too much to explain from scratch in an answer here.

ANGULAR 6 http get JSON

I am having trouble getting information from certain json file.
case 1 (works):
service:
private xmlToJson: string = 'https://rss2json.com/api.json?rss_url=';
getImg(Url: string) {
return this.http.get(this.xmlToJson + Url);
}
component:
private url='https://api.flickr.com/services/feeds/photos_public.gne';
public images: any = [];
this.imgDataService.getImg(this.url)
.subscribe(data => this.images = data);
HTML:
<h1>{{images.feed.title}}</h1>
case 2 (does not work):
service:
getImg(Url: string) {
return this.http.get(Url);
}
component:
private url = 'https://api.flickr.com/services/feeds/photos_public.gne?format=json&nojsoncallback=1';
public images: any = [];
this.imgDataService.getImg(this.url)
.subscribe(data => this.images = data);
HTML:
<h1>{{images.title}}</h1>
Any idea why case 2 doesn't work? I ran both JSONs here: https://jsonlint.com/ and they came out valid.
You have a CORS error.
Here is a StackBlitz showing the two calls and what is returned from the server. The call for the Flickr URL fails because the Flickr API doesn't (at least on this service) return headers for Access-Control-Allow-Origin to enable public access.
Ironically, you'll be able to call the web service from any code that is not running in a web browser.
Since you probably won't be able to convince them otherwise (others have tried), I would suggest you give up on Flickr.
A final note: You would be able to see the error if you opened your browser's developer tools and checked the console. That should be your first stop for any weird behaviour you encounter in web development.
Change the public images: any = []
to public images: any = {}
Images data isn't an array.
Instead of this.imgDataService.getImg(this.url) type this.imgDataService.getImg(this.url2)
you can try this approach if you know what kind of json response is expected then probably you can create class like
Example:
export class ImageModel{
name:string;
title:string;
src:string;
}
where name,title and src are json keys
then in you can do:
constructor(private httpService:HttpService){}
getImg(Url: string) {
return this.httpService.get<ImageModel>(
(image:ImageModel) => {return image}
}
new HttpService should automatically map your json data to the javascript class/Interface
and then you can read the image to get the json data.

Angular 6 HttpClient.get Observable does not assign value

I suppose that the answer will be very obvious, but still it evades me. I'm new on working with observables, and now I'm facing issues assigning a value from one. I had success if I define it (this._apps) as an Observable and asking from the view to the service using subscribe (But for my taste is was way convoluted (three levels inside a map just to return another observable with the array and then another function to subscribe the previous to assign the variable and another subscription in the view to finally show the information), inefficient and on top of that I could not get it "right" again). The task is very simple. Given the class Application
export class Application {
name: string;
baseUrl: string;
deprecated: boolean;
}
And the service (just the relevant code)
private _apps: Application[] = [];
constructor(private _http: HttpClient) {
this.getAllApplications().subscribe(apps => {
console.log('Apps subscriber');
this._apps = apps;
console.log('Apps subscriber Ends ' + apps);
},
err => {
console.log(err.status); // 401
console.log(err.error.error); // undefined
console.log(JSON.parse(err.error).error); // unauthorized
});
}
private getAllApplications() {
return this._http.get<Application[]>('http://development:4300/api/v1/apps');
}
From the constructor the function which gets the information from WebAPI is triggered, and the remote call is successful, but the variable this._apps is an empty array if I try to call it from anywhere in the code. I could not determine the type of the parameter "apps" in the subscribe function, but for some reason it cannot be assigned and the best answer given is that it is a function (See my first update) in one of my tries. Currently it returns in the console "[object Object]", but apps[0] gives undefined, so it is an empty Array.
This is the console output, just starting the application:
Angular is running in the development mode. Call enableProdMode() to enable the production mode.
Refreshing apps cache calling http://development:4300/api/v1/atbc-apps
Apps subscriber
Apps subscriber Ends [object Object]
I was trying this solution among many others that I forget (to use the more modern HttpClient instead the Http I used before), so what I'm doing wrong?
Update 1
I changed the constructor to this:
constructor(private _http: HttpClient) {
this.getAllApplications().subscribe(apps => {
console.log('apps length ' + apps.length);
this._apps = apps; // Remember private _apps: Application[] = [];
console.log('Apps subscriber Ends ' + apps.toString);
},
err => {
console.log(err.status); // 401
console.log(err.error.error); // undefined
console.log(JSON.parse(err.error).error); // unauthorized
});
}
and the declaration of the function called into this:
private getAllApplications(): Observable<Application[]> {
// the exactly the same as before
}
And now I got from the console this:
apps length undefined
Apps subscriber Ends
function () {
if (this instanceof Promise) {
return PROMISE_OBJECT_TO_STRING;
}
return originalObjectToString.apply(this, arguments);
}
That is the function I was talking about. Any ideas about why even though there is no errors (nor at compile time, neither at runtime), the returning object is not a real Application array?
Change this line:
private _apps: Application[] = [];
to:
_apps: Application[] = [];
Which will default to making it public. Then this line will see it:
this._apps = apps;
At the end I suppose is a mindset to work with Observables, and I tried to build a kind of cache, so the only way I could do it (let me know if there is a better way) was using the view to fill-out the cache. I could not do it from the service itself because the calling the function from the view is synchronous and to fill out the array is async. So I had to create a public setApplicationCache procedure which is filled out after calling the service from the view, it call the setApplicationCache( Application[] ) function and the rest works because it takes just the cache to do filtering and other operations or use it from other pages w/o calling the database again and again.
This is the code from the first view called (main page)
ngOnInit() {
this._myService.getAllApplications().subscribe(arrObjApps => {
this._myService.setApplicationsCache(arrObjApps)
this.listApps = this._myService.getApplications(true);
});
And the service has this functions:
private _apps: Application[] = [];
getAllApplications(): Observable<Application[]> {
return this._http.get('http://development:4300/api/v1/atbc-apps').pipe(
map( (response: Response) => {
let results = response.json().data.map( app => {
return new Application(app.name, app.baseUrl, app.deprecated);
});
return results;
})
);
}
getApplication(appName: string): Application {
return this._apps.find(app => app.name == appName);
}
getApplications(onlyActives: boolean): Application[] {
if (onlyActives) {
return this._apps.filter(app => app.deprecated == false);
} else {
return this._apps;
}
}
And as I stated the solution should be obvious. Just again the async mindset required to work with observables.

Weird .hasOwnProperty behaviour

In an effort to properly instantiate Typescript objects from data received over HTTP as JSON, I was exploring the possibility of using the for..in loop coupled with .hasOwnProperty() like so:
class User {
private name: string;
private age: number;
constructor(data: JSON) {
console.log('on constructor\ndata', data);
for (var key in data) {
console.log('key:', key);
if (User.hasOwnProperty(key)) {
console.log('User has key:', key);
this[key] = data[key];
}
}
}
displayInfo(): string{
return JSON.stringify(this);
}
}
let button = document.createElement('button');
button.textContent = "Test";
button.onclick = () => {
try{
let json = JSON.parse('{"name": "Zorro","age": "24"}');
let usr = new User(json);
console.log(usr.displayInfo());
}catch (error){
alert(error);
}
}
document.body.appendChild(button);
Using similar code in my project fails completely. That is expected as the compiled JS code has no awareness of the private TS vars and so, hasOwnProperty is always false.
However, I was using the Typescript Playground, and running that code there produces the following output in the console:
on constructor
data Object {name: "Zorro", age: "24"}
key: name
User has key: name
key: age
{"name":"Zorro"}
As you can see, there are clearly some unexpected things happening here. The first key is recognized and the new User instance is initialized with the value from the JSON, yet that does not happen for the second key.
Can someone explain why this happens?
As was pointed out in the comments, you should be using this.hasOwnProperty instead of User.hasOwnProperty. And as you noticed, this code is busted anyway because the property declarations in the class don't actually create own properties on the object (they would need to be initialized for this to happen).
But why did you get a hit on the name key? The User object is a constructor function for your class. Functions do have a name property:
function fn() { }
console.log(fn.name); // prints 'fn'
They don't have an age property, of course.
Your constructor would of course just have to look like this, if you want to construct User instances from plain JavaScript objects:
constructor(data: any) {
this.name = data.name;
this.age = data.age;
}