Android Kotlin what is Promise? - android

I work on Android (Kotlin) with a WebView who load some Page and i have interection with this page with
#JavascriptInterface
My client write some function i had to implement, for exemple :
#JavascriptInterface
fun fileExists(path: String): Promise<Boolean>? {
return null
}
But class Promise is not found, and I don't know what is it and which library I need to use.
His only answer is "Look how Cordova transforme Promise into Java Object"
Maybe someone can help me or just give some start of information, now I even don't know direction
I have try this 2
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlin_version"
but not Pormise Object inside.
Thank you for your help

Promise as a pattern is well known in JS world, but it's not so popular among Android folks, maybe because of the fact that we have a very powerful RxJava library. But what if you need RxJava just for a single value response (Single) such as single network request and couple transformation operation like flatMap and map. If this is the case then you should consider Promise pattern that works well for single value response.
Example use of Promise:
fun postItem(item: Item) {
preparePostAsync()
.thenCompose { token ->
submitPostAsync(token, item)
}
.thenAccept { post ->
processPost(post)
}
}
fun preparePostAsync(): Promise<Token> {
// makes request an returns a promise that is completed later
return promise
}

Related

Creating a queue for the evaluateJavascript function on a WebView

I have a hybrid app; some of my Activities use a WebView to display web content. The web app that I show in the WebView has a JS interface that lets me send commands to the web app to navigate different places or do other things.
For example, if I need my web app to navigate to the "user profile" page, I execute a command like:
class SomeActivity: AppCompatActivity {
...
webView.evaluateJavascript("navigateTo(\"userprofile\")")
...
}
Then, I get a response via the JS interface, and the app reacts accordingly.
I introduced a JS queue to improve performance, so the JS commands are executed sequentially. Instead of calling the evaluateJavascript() function directly on the WebView, I've created a custom WebView component with this JS queue set as a property.
class SomeActivity: AppCompatActivity {
...
webView.jsQueue.queueEvaluateJavascript("navigateTo(\"userprofile\")")
...
}
Now I would like to add a new behaviour on top of that, which is being able to pre-process the commands within the queue. What I mean by pre-processing is that if I ever queue commands of the same "type", like:
class SomeActivity: AppCompatActivity {
...
webView.jsQueue.queueEvaluateJavascript("navigateTo(\"userprofile\")")
webView.jsQueue.queueEvaluateJavascript("navigateTo(\"about-me\")")
webView.jsQueue.queueEvaluateJavascript("navigateTo(\"user-list\")")
...
}
What I would like to happen is that the queue is smart enough to ditch those two first "navigate" commands - "navigateTo(\"userprofile\")" and "navigateTo(\"about-me\")" - because I don't want my WebView to navigate to those two places just to finally navigate to "navigateTo(\"user-list\")".
The implementation of this JS queue looks like this:
class JsQueue(
private val webView: WebView,
private val scope: CoroutineScope
) {
init {
scope.launch {
for (jsScript in jsChannel) {
runJs(jsScript)
}
}
}
private val jsChannel = Channel<String>(BUFFERED)
fun queueEvaluateJavascript(script: String) {
runBlocking {
jsChannel.send(script)
}
}
suspend fun runJs(script: String) = suspendCoroutine<String> { cont ->
webView.evaluateJavascript(script) { result ->
cont.resume(result)
}
}
}
How can I pre-process the js commands in the Channel<String> so I
ditch duplicated js commands?
Also, sometimes my WebView will become invisible, and I want to pause the queue when that happens. I wonder if there's any way to programmatically pause a Channel?
Edit #1
Also, sometimes my WebView will become invisible, and I want to
pause the queue when that happens. I wonder if there's any way to programmatically pause a Channel?
I've tried using this PausableDispatcher implementation, and it seems to be doing the trick.
All of the command examples you gave follow a specific pattern: they're all functions. We can use this to our advantage!
First, let's create some terminology.
navigateTo() is a function (of course!).
And lets call the navigateTo part of the function a type.
An example of some types are:
console.log() => `console.log`,
gotoUrl(url) => `gotoUrl`.
I just made this terminology up. But it will help you understand the logic.
Now, what we need to do is look at the array of commands, understand it's type, and check if any other commands have the same type. If they do, they need to be excluded from the queue before the queue is executed.
Easy!
I've written a sample code that you can integrate with your script:
// Example array of commands for demonstration.
let commands = [
'navigateTo("a")',
'navigateTo("b")',
'navigateTo("c")',
];
/** A list of non-duplicate types*/
let types = [];
/** A list of non-duplicate commands */
let newCommands = [];
// Reverse the array because the most important commands start from the end of array.
for(let command of commands.reverse()){
let type = command.slice(0, command.indexOf('('));
// Determine if type already exists
let alreadyExists = false;
for(let commandType of types){
if(type == commandType){
alreadyExists = true;
break;
}
}
if(alreadyExists)
// Type already exists. Do not add to command list.
continue;
// This type & command does not exist.
// Update command & type arrays
types.push(type);
newCommands.push(command)
}
// New Commands
console.log("Commands: ", newCommands);
// If you want to keep same queue order without duplicates:
console.log("Commands: ", newCommands.reverse())
Let me know if I missed the mark answering this question. Otherwise, cheers to a great queue system!

Firebase Functions AppCheck keeps failing my device

I have been trying to integrate AppCheck with my Android app, but I can't seem to make a valid request.
As for test purposes, I have been using the following code:
Android Code
class Application : MultiDexApplication() {
override fun onCreate() {
FirebaseApp.initializeApp(this)
val appCheck = FirebaseAppCheck.getInstance()
if (BuildConfig.DEBUG) appCheck.installAppCheckProviderFactory(DebugAppCheckProviderFactory.getInstance(), true)
else appCheck.installAppCheckProviderFactory(SafetyNetAppCheckProviderFactory.getInstance(), true)
super.onCreate()
}
}
class TestActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
val data = "This is a test!"
Firebase.functions.getHttpsCallable("test").call(data).addOnCompleteListener {
if (it.isSuccessful) {
val result = it.result.data
print(result)
}
else {
val exception = it.exception
print(exception)
}
}
}
}
Function Code
const functions = require("firebase-functions")
exports.test = functions.https.onCall((data, context) => {
if (context.app == undefined) {
functions.logger.error("Test - Failed validating device integrity!")
throw new functions.https.HttpsError("failed-precondition", "The function must be called from an App Check verified app")
}
return data
})
I have added the DebugAppCheckProviderFactory token to the Firebase console, but no matter what I do, if it is an emulator or physical device, when I call the function, the failed-precondition exception is always thrown.
Checking the function logs, I can see that the app is missing:
I have already read the documentation multiple times and I can't seem to be missing any step. Am I missing something or is there anything I can do to find the root cause for this?
Thank you for your time!
EDIT:
As for Martin suggestion, I created a new OnRequest function and added the X-Firebase-AppCheck header. I received the token correctly and was able to validate it successfully with:
firebaseAdmin.appCheck().verifyToken(appCheckToken)
So, my guess is that Android is not adding the X-Firebase-AppCheck automatically to the OnCall function like it should.
I ran the code and made some breakpoints through the code and noticed the following. The call method from Firebase is adding the Firebase-Instance-ID-Token but I can't seem to find the X-Firebase-AppCheck header anywhere. Maybe I am not supposed to see this value, or maybe I just can't find where it is being added. Or maybe it is not added at all, thus I can't validate my context.app at all.
It may be required to obtain an AppCheckToken:
FirebaseAppCheck
.getInstance()
.getAppCheckToken(false)
.addOnSuccessListener(new OnSuccessListener<AppCheckToken>() {
#Override
public void onSuccess(#NonNull AppCheckToken tokenResponse) {
String appCheckToken = tokenResponse.getToken();
...
}
});
Which has to be passed along, with the HTTP request (the example shows Retrofit, which could also be used instead of HttpsCallable:
#GET("yourExampleEndpoint")
Call<List<String>> exampleData(
#Header("X-Firebase-AppCheck") String appCheckToken,
...
);
Where FirebaseFunctions.getInstance().getHttpsCallable().call() doesn't tell how or if one has to explicitly set that X-Firebase-AppCheck header. Cloud Functions should usually receive an X-Firebase-AppCheck header - with the previously retrieved AppCheck token:
const appCheckToken = req.header('X-Firebase-AppCheck');
if (!appCheckToken) {
res.status(401);
return next('Unauthorized');
}
try {
const appCheckClaims = await firebaseAdmin.appCheck().verifyToken(appCheckToken);
// If verifyToken() succeeds, continue with the next middleware function in the stack.
return next();
} catch (err) {
res.status(401);
return next('Unauthorized');
}
One can also issue own tokens ...and checking for context-app is also valid.
Update: The Protocol specification for https.onCall reads:
Optional: X-Firebase-AppCheck: <token>
The Firebase App Check token provided by the client app making the request. The backend automatically verifies this token and decodes it, injecting the appId in the handler's context. If the token cannot be verified, the request is rejected.Available for SDK >=3.14.0
To install the minimum required NodeJS dependencies:
npm install firebase-functions#">=3.14.0"
npm install firebase-admin#">=9.8.0"
And last, but not least ...there's even a debug helper for that:
dependencies {
debugImplementation "com.google.firebase:firebase-appcheck-debug:16.0.0-beta02"
}
After testing everything with no success, in a desperate move, I tried to look to unrelated files and found the solution.
In my build.gradle I was importing all the Firebase dependencies with the bom:
implementation platform("com.google.firebase:firebase-bom:27.0.0")
implementation "com.google.firebase:firebase-crashlytics-ktx"
implementation "com.google.firebase:firebase-analytics-ktx"
implementation "com.google.firebase:firebase-firestore-ktx"
implementation "com.google.firebase:firebase-functions-ktx"
implementation "com.google.firebase:firebase-auth-ktx"
So I thought: "Can the firebase-bom be importing an outdated functions-ktx dependency?"
And guess what? As soon as I imported the dependencies without the bom, I started to see the X-Firebase-AppCheck being added and the context.app being valid.
I ended up with the following build.gralde:
implementation "com.google.firebase:firebase-crashlytics-ktx:18.2.1"
implementation "com.google.firebase:firebase-analytics-ktx:19.0.0"
implementation "com.google.firebase:firebase-firestore-ktx:23.0.3"
implementation "com.google.firebase:firebase-functions-ktx:20.0.0"
implementation "com.google.firebase:firebase-auth-ktx:21.0.1"
After a little more investigation, I found out that I was using an outdated firebase-bom version. Android Studio used to let me know if there was a new version for the dependencies, but it did not notice me for the firebase-bom. Turns out, that I just need to update my firebase-bom dependency.
TL;DR
Check your Android Firebase libraries version!

whats the Kotlin Equivalent request.post() from python?

So I'm trying to use an API from a website but inorder to use it i'll have to send my login informaton. The documentation shows me a python example on how to login.
R = requests.post('http://nova.astrometry.net/api/login', data={'request-json':
json.dumps({"apikey": "XXXXXXXX"})})
print(R.text)
So what is the Kotlin equivalent of the above code ? In the websites documentation it states
"Note the request-json=VALUE: we’re not sending raw JSON, we’re sending the JSON-encoded string as though it were a text field called request-json."
I have attempted to use Android Volley but im not entirely sure how to use it.
private fun plateSolve(){
val json=JSONObject(map).toString()
Log.d(TAG,"URL:$url")
Log.d(TAG,"Hashmap:$json")
JSONObject(map).toString()
val jsonObjectRequest = JsonObjectRequest(
Request.Method.POST, url, null,
{ response ->
try {
Log.d(TAG,"POST Response: %s".format(response.toString()))
}catch (e:Exception){
Log.d(TAG,"Exception: $e")
}
},
{ error ->
// TODO: Handle error
Log.d(TAG,"There Was an Error")
error.stackTraceToString()
}
)
// Access the RequestQueue through your singleton class.
VolleySingleton.instance?.addToRequestQueue(jsonObjectRequest)
}
Thanks in advance
it's not recommended to use volley anymore for android please use retrofit as its google's recommended library,the answer for your question is too big so i will write some checkpoints to do and also i have shared a simple working example with retrofit one of my own projects on github , hopefully this helps you
retrofit link - https://square.github.io/retrofit/
Insert library files in gradle
create response classes
create retrofit api class
4.create interface class with api calls
Github project with app using retrofit for api calls
https://github.com/zaidzak9/NewsApp

Android kotlin debugging coroutines

Before coroutines I used callbacks and debugging always gave me a lot of information. I could get the url my API call is going to, Headers I put into Request, interceptors I used, etc..
Now I use coroutines. All I can get when debugging is the final result of request (fail/sucess) with the result data. Nothing of all these valuable info I need is not there.
shortened example of my code:
restService.getVersionInfo().getResult(
success = {
when {
checkIsMandatory(it.lastMandatoryVersion) -> status.postValue(
Status.NewVersionInfo(it.description, true, it.url)
)
else -> initialization()
}
},
error = {
initialization()
}
I pout breakpoints to error or sucess. Am I missing something or coroutines really have this disadvantage. Please inform me

How can I access the request parameters in the onResponse and the onFailure callbacks

I have a Retrofit call where I want to handle HTTP and Retrofit errors when calling an API.
So when a failure happens, I need to cache the request into a RoomDB/SQLite for when the API comes back online, or connection improves there is a routine that sends all those requests to the API.
x.enqueue(object : Callback<PayloadResponse> {
override fun onResponse(
call: Call<PayloadResponse>,
response: Response<PayloadResponse>
) {
...
val errorMessage = when {
response.code() != HttpURLConnection.HTTP_OK -> {
// Need the original Payload here so I can insert that data into RoomDB/SQLite
"An error occured duing API Call (NOT OK) &{response.code()}"
}
...
Same situation I need for the onFailure() callback.
Can I access the original request in these contexts? If so how?
Once I have encountered the same problem. My approach was debugging first parameter of retrofit callback's onResponse and onFailure.
call: Call<PayloadResponse>
call.request()
It contains all kind of information about your call / request e.g url, parameters, method. However, retrieving request data is not straightforward, consistent, and prone to bugs.
Then, I started using Kotlin coroutine which gives async process with sync nature of coding.
interface RetrofitApi{
#GET("your-route")
suspend fun sampleApiMethod(request:SampleRequest):Response<SampleCustomObject>
}
Your retrofit api methods may look like above.
CoroutineScope(Dispatchers.Main).launch {
val request = SampleRequest()
val deferred = async { apiService.sampleApiMethod(request) }
val result = deferred.await()
if(result.code() == 200){
// Do something
}else{
// Cache SampleRequest()
}
}
You can modify as much as to make it look better and utilize optimized light weight threads. It is one of the ways for me to handle such situations. It's just a hint, you may modify to have structurally and architecturally correct design in your project.

Categories

Resources