Pass String value from Escaping Swift function to Kotlin Multiplatform - android

I need to pass String token from FirebaseAppCheck to Kotlin Multiplatform.
I can get access to Kotlin class and assign values, but it always return null because of fact that Kotlin compiles first. Below is my swift function that should send token to KMM.
func sendAppCheckTokenToKMM() {
// Kotlin class
let factory = FirebaseAppCheckFactory()
AppCheck.appCheck().token(forcingRefresh: false) { token, error in
guard let token = token else {
print("Unable to retrieve App Check token.")
return
}
// Get the raw App Check token string.
let tokenString = token.token
factory.tokenId = tokenString
}
}
Next my kotlin FirebaseAppCheckFactory looks like this.
class FirebaseAppCheckFactory {
lateinit var tokenId: String
fun isTokenInitialised() = ::tokenId.isInitialized
}
And last I need to send this token using suspended function in another class.
// FirebaseAppCheckService.kt
override suspend fun fetchToken(): String? {
if (firebaseAppCheckFactory.isTokenInitialised()){
return firebaseAppCheckFactory.tokenId
}
return null
}
Every time functions returns null. For now I cannot change fetchTokenFunction.
Main class is expected/actual to be using on Android and iOS as well. Implementation on Android is done, but on iOS I have to do this on platform first to initialize FirebaseAppCheck and then send app check token.
Is there another/better way to to this?

Related

returning a list of data to viewModel using Repository pattern from different data sources (network and room)

I'm developing a Kotlin app using MVVM with repository pattern.
I have a main fragment that displays list of asteroids.
Here is the flow I want to achieve.
When user opens main fragment , I will check if there's asteroids data stored in the local database (room).
If yes I will displays the stored data
If no I will call API to get the asteroids then I will store the data from API to local database
So based on my understating of the repository pattern the viewModel should not be concerned about the data source wether it's from API or local database.
So I defined this function in repository that the viewModel will call
override suspend fun getAsteroid(): List<Asteroid> {
var result : List<Asteroid>
try {
var isDataAvailable = getAnyAsteroidFromDb()
if (isDataAvailable == null) {
result = getAsteroidApi().asDomainModel()
} else {
result = getAsteroidFromDb()
}
} catch (e : Exception) {
}
return result
}
getAnyAsteroidFromDb checks the data availability in room
getAsteroidApi gets the data from netwrok call
getAsteroidFromDb gets the data from room
The problem is getAsteroidFromDb returns Livedata from Dao
#Query("SELECT * FROM asteroid_tbl")
fun getAsteroidsFromDb () : LiveData<List<AsteroidEntity>>
And the function itself getAsteroid returns List of Asteroids.
Let's say I changed the return type to Livedata , This will cause another problem because the api doesn't return a Livedata.
I'm kind of stuck here and I think I'm doing something wrong or maybe my understating of the MVVM and repertory pattern still not good enough.
Any thoughts or idea will be appreciated!
There are more than one solutions to this, but since you are using the MVVM pattern. So I would like to suggest you a pattern for such situations.
Whenever there is a situation where have to check our Local DB or call our backend API. The usual structure of calling is to have one point of data source only to avoid ambiguity and such issues.
So while you can easily get the value from LiveData or convert the API response to LiveData. I would suggest your structure to be as follows.
Check Local DB for data.
If data is there in Local DB, fetch it.
If data is not there in Local DB, call the API, fetch the results, store it in Local DB.
On success of API result, you can then query the local DB again and get results.
This ensures your single source of truth remains your DB and you can easily make calls.
You should be able to return the LiveData value:
override suspend fun getAsteroid(): List<Asteroid> {
var result : List<Asteroid>
try {
var isDataAvailable = getAnyAsteroidFromDb()
if (isDataAvailable == null) {
result = getAsteroidApi().asDomainModel()
} else {
result = getAsteroidFromDb().value //<==List<Asteroid>
}
} catch (e : Exception) {
}
return result
}
If you don't want to return Livedata from the repository, you can unwrap the Livedata you get from Room and use the value from it. That should give you the list of Asteroids object. So, use something like this:
override suspend fun getAsteroid(): List<Asteroid> {
var result : List<Asteroid>
try {
var isDataAvailable = getAnyAsteroidFromDb()
if (isDataAvailable == null) {
result = getAsteroidApi().asDomainModel()
} else {
val data = getAsteroidFromDb()
result = data.value
}
} catch (e : Exception) {
}
return result
}

How do I return a result from a callable cloud function?

I want to set up a test function to return values from my backend, but I can't figure out how to grab and parse the result in my app. I found an answer on here that says to use the code I've marked below, but I'm getting a bunch of errors. How do I return N number of values from a cloud function?
Client code:
//Database Kotlin file
fun test(data: Map<*,*>): Map<*,*> {
val functions = Firebase.functions
return functions.getHttpsCallable("test")
.call(data)
.continueWith { task ->
// This continuation runs on either success or failure, but if the task
// has failed then result will throw an Exception which will be
// propagated down.
val result = task.result
//Other answer showed this JSON solution
val json = Json(JsonConfiguration.Stable)
val jsonString = org.json.JSONObject(result?.data as Map<*, *>).toString()
json.parse(Person.serializer(), jsonString)
}
}
//On the Activity
testbutton.setOnClickListener() {
var data = hashMapOf<String, Any>()
data["input1"] = "input1"
data["input2"] = "input2"
db.test(data)
//Interpret results here
}
Backend:
exports.test = functions.https.onCall(async (data, context) => {
return {returnVal: 777, returnMsg: 'test return message'}
});
Errors:
Right now the error is saying that there is a Type Mismatch Required: Map<*,*> Found: Task<???,???>. However, I think this is probably because I'm way off in setting up test().
Callables do not yield JSON via the Android SDK. They yield a Java object whose form matches that of the object returned by the function. Remove everything that has to do with JSON and just inspect the object you get from the task. Also, the call to call() is asynchronous and returns a Task. You can't simply cast that Task to a Map and expect that to work. You have to handle the result inside the continuation callback. If you want the Task result to be easier to use in kotlin, you should look into using coroutines and covert that Task to something that can be used in a coroutine.

Get return value from a function that relies on an asynchronous task

I'm doing a REST API call with Ion in order to log in a user. In order to do that, I'm getting the accessToken that Ion returns me (and this comes asynchronously). Initially, I was doing everything in the activity and inside the callback (I was getting the accessToken and then starting a new activity intent while sending this token to the next activity). It was working fine like that.
Now, I'm refactoring my project to an MVP architecture, so I've split the Ion network call to a different class (in the model layer level of MVP). However, I have a problem. It always returns me null and I suspect it's because the function returns before Ion finishes its async task. Any idea?
Here is the function that I want to get the loginSession from:
override fun userAuth(username: String,
password: String,
context: Context): LoginSession? {
var loginSession: LoginSession? = null
Ion.with(context)
.load("https://myURL")
.setBodyParameter("UserName", username)
.setBodyParameter("Password", password)
.asString()
.setCallback { e, result ->
try {
val json = JSONObject(result)
val expiresIn = json.getInt("expires_in")
val tokenType = json.getString("token_type")
val refreshToken = json.getString("refresh_token")
val accessToken = json.getString("access_token")
loginSession = LoginSession(expiresIn, tokenType, refreshToken, accessToken)
} catch (jsonException: JSONException) {
jsonException.printStackTrace()
}
}
return loginSession
}
creates an interface, make the class where you wanna callback implements it.once you are done with the network call inside your model.
Use the method of that interface to get a callback in your activity/fragment.
You must have a reference of that interface in your model which must have been initialized with your activity/fragment object before your network calls start to process.
Note:- Your network call must be in Presenter in MVP and not in model.Retrofit already does all this more easily what i explained.You may use Retrofit instead of Ion.

How To Use Async/Await/Coroutines In OnCompleteListener Firebase

I am building a client application which uses Firebase for two things:
User Authentication
Using a realtime database
I have managed to set up everything correctly on my client and on my backend server (using Firebase's Admin SDK) and am able to correctly authenticate users and allow them to read/write to the database.
I am also using Retrofit2 to send requests from the client to the backend.
As part of allowing users access to the database, it is needed to send the user's token to the backend so the user can be verified.
To do this, I have the following logic:
val user = FirebaseAuth.getInstance().currentUser
if (user != null) {
user.getIdToken(false).addOnCompleteListener {
if (it.isSuccessful) {
val token = it.result?.token
//retrofit logic to send request happens from here
}
}
As you can see, getting the Id token of the user is an asynchronous call and in the current code base that I have, I have this code block for each one of my calls to the backend (duplication).
I want to know how I can export this snippet to a function (maybe a suspend method?) so that it can be reused for every call to the backend
I have searched online and have seen many SO questions, but none that fit this scenario.
I have thought about passing in a callback, but I have several methods that communicate to the backend, and each of them will require a different callback method.
The solution I am looking for looks something like this:
fun fetchDataFromDB() {
getIdTokenForUser()
//wait till it finishes and then
//perform request to DB
}
fun updateDataInDB() {
getIdTokenForUser()
//wait till it finishes and then
//perform request to DB
}
//......
I have tried reading about and implementing coroutines, but I lack the knowledge to do so correctly.
EDIT
Thanks to #Doug Stevenson for his answer and direction, I have managed to construct the following:
private suspend fun getUserIdToken(user: FirebaseUser) = coroutineScope {
val job = async {
user.getIdToken(false).result?.token
}
job.await()
}
And I use it in this fashion:
fun updateDB(context: Context) = runBlocking {
val user = FirebaseAuth.getInstance().currentUser
if (user != null) {
val token = getUserIdToken(user)
}
}
Is this the correct approach? Since the answers given below present a different implementation.
getIdToken is asynchronous returns a Task object. If you want to use a Task object in a Kotlin coroutine, you can use the library kotlinx-coroutines-play-services to add an extension method await() to the Task that makes it usable in a coroutine. With that, you can write something like this:
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.3.9"
import kotlinx.coroutines.tasks.await
suspend fun getIdTokenForUser(user: FirebaseUser): GetTokenResult {
return try {
user.getIdToken(false).await()
}
catch (e: Exception) {
// handle error
}
}
You might have to update the types here - I didn't try to compile or test this.
See also:
Android kotlin task to be executed using coroutines
Coroutines And Firebase: How to Implement Javascript-like Promise.all()
Using Firebase with Kotlin coroutines
In order to go from a callback based API like the following one:
val myCallback = object : ServiceCallback() {
override fun onResult(theobject: Something) {
// your callback code here
}
override fun onFailure(ex: Throwable) {
// error handling
}
}
theService.enqueue(callback)
You can use suspendCoroutine
What it does is that it suspends execution until the continuation is satified by the callback. So you can write a KTX like the following:
suspend fun Service.getSomething(): Something = suspendCoroutine{ cont ->
val callback = object : ServiceCallback(){
override fun onSuccess(data: Something): Unit = cont.resume(data)
override fun onFailure(ex: Throwable): Unit = cont.resume(ex)
}
this.enqueue(callback)
}

How to access the application data on Google Drive on Android with its REST API

Google is putting its Android API for accessing Google services (i.E. Google Drive) to rest and is replacing it with REST.
And while there is a 'migration guides', it fails to build a APK package ready for installation, because of 'Duplicate Class definition' or something.
For some reason it is incredibly hard to find some comprehensive information about how to access a Google Service using REST via Android (preferably using methods natively available to the OS).
After a lot of searching, puzzling, scratching my head, occasional swearing and a lot of learning about things I really didn't want to care about, I'd like to share a few pieces of code, that are actually working for me.
Disclaimer: I'm a rookie Android programmer (who really doesn't how to pick his battles), so if there are things in here, that have the real Android wizards shaking their heads, I hope you'll forgive me.
All code samples are written in Kotlin and Android Studio.
Worth noting: Only the 'application data folder' is queried in this little tutorial, you will need to adjust the requested scopes if you want to do something else.
Necessary preparations
Create a project and an OAuth key for your application as described here. Many of the information I gathered for authorization came from that place, so expect to find some similarities.
The Dashboard for your project may be found at https://console.developers.google.com/apis/dashboard
Add implementation "com.google.android.gms:play-services-auth:16.0.1" to your applications gradle file. This dependency will be used for authentication purposes.
Add 'internet' support to your applications manifest
<uses-permission android:name="android.permission.INTERNET"/>
Authenticating
The beginning of our journey is the authentication.
For this purpose, I used the GoogleSignIn Framework.
Create an activity (or use your main activity, your choice) and override the onActivityResult method there.
Add a block like this:
if (requestCode == RC_SIGN_IN) {
GoogleSignIn.getSignedInAccountFromIntent(data)
.addOnSuccessListener(::evaluateResponse)
.addOnFailureListener { e ->
Log.w(RecipeList.TAG, "signInResult:failed =" + e.toString())
evaluateResponse(null)
}
}
RC_REQUEST_CODE is an arbitrarily chosen ID value defined in the companion object as constant.
Once you want to perform authentication (i.E. by clicking of a button), you will need to start the activity we have just declared the callback for.
For this purpose, you need to prepare the authentication request first.
GoogleSignIn.getClient(this, GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestIdToken("YourClientIDGoesHere.apps.googleusercontent.com")
.requestScopes(Scope(Scopes.DRIVE_APPFOLDER))
.build())
This request gives you a client object you can start using straight away by calling.
startActivityForResult(client.signInIntent, RC_SIGN_IN)
This call will cause the authorization screen to pop up (if necessary), allow the user to select an account and then close itself again, passing the data to onActivityResult
To fetch the previously signed in user (without starting a new activity), you can also use the GoogleSignIn.getLastSignedInAccount(this); method in the background.
On failure either of these methods return null, so be ready to deal with that.
Now that we have an authenticated user, what do we do with it?
We ask for an auth token.
Right now, we only have an idToken in our account object, which is absolutely useless for what we want to do, because it doesn't allow us to call the API.
But Google comes to the rescue once more and supplies us with the GoogleAuthUtil.getToken(this, account.account, "oauth2:https://www.googleapis.com/auth/drive.appdata") call.
This call will forward the account information and return a String if all goes right: The auth token we need.
To be noted: This method performs a network request, meaning that it will throw up in your face, if you attempt to execute it in your UI thread.
I created a helper class which mimics the behavior (and API) of Googles 'Task' object, which takes care of the nitty gritty of calling a method on a thread and notifying the calling thread that it is done.
Save the auth token somewhere you can find it again, authorization is (finally) done with.
Querying the API
This part is far more straightforward than the previous one and goes hand in hand with the Google Drive REST API
All network requests need to be executed on a 'non-UI' thread, which is why I wrapped them up in my helper class to notify me once there is data to display.
private fun performNet(url: String, method: String, onSuccess: (JSONObject) -> Unit)
{
ThreadedTask<String>()
.addOnSuccess { onSuccess(JSONObject(it)) }
.addOnFailure { Log.w("DriveSync", "Sync failure $it") }
.execute(executor) {
val url = URL(url)
with (url.openConnection() as HttpURLConnection)
{
requestMethod = method
useCaches = false
doInput = true
doOutput = false
setRequestProperty("Authorization", "Bearer $authToken")
processNetResponse(responseCode, this)
}
}
}
private fun processNetResponse(responseCode: Int, connection: HttpURLConnection) : String
{
var responseData = "No Data"
val requestOK = (responseCode == HttpURLConnection.HTTP_OK)
BufferedReader(InputStreamReader(if (requestOK) connection.inputStream else connection.errorStream))
.use {
val response = StringBuffer()
var inputLine = it.readLine()
while (inputLine != null) {
response.append(inputLine)
inputLine = it.readLine()
}
responseData = response.toString()
}
if (!requestOK)
throw Exception("Bad request: $responseCode ($responseData)")
return responseData
}
This block of code is a rather generic helper function I put together from various sources and essentially just takes the URL to query, the method to perform (GET, POST, PATCH, DELETE) and constructs a HTTP request from it.
The auth token we got earlier during the authorization is passed as a header to the request to authenticate and identify ourselves as 'the user' to Google.
Google will, if everything is OK, reply with HTTP_OK (200) and onSuccess will be called, which will translate the JSON reply to a JSONObject, which will then be passed to the evaluation function we registered earlier.
Fetching the list of files
performNet("https://www.googleapis.com/drive/v3/files?spaces=appDataFolder", "GET")
The spaces parameter serves to tell Google, that we don't want to see the root folder but the application data folder. Without this parameter, the request would fail, because we only requested access to the appDataFolder.
The response should contain a JSONArray under the files key, which you then can parse and draw whatever information you want.
The ThreadTask class
This helper class encapsulates the steps necessary to perform an operation on a different context and perform a callback on the instantiating thread upon completion.
I am not claiming that this is THE way to this, it's just my 'Simply doesn't know any better'-way.
import android.os.Handler
import android.os.Looper
import android.os.Message
import java.lang.Exception
import java.util.concurrent.Executor
class ThreadedTask<T> {
private val onSuccess = mutableListOf<(T) -> Unit>()
private val onFailure = mutableListOf<(String) -> Unit>()
private val onComplete = mutableListOf<() -> Unit>()
fun addOnSuccess(handler: (T) -> Unit) : ThreadedTask<T> { onSuccess.add(handler); return this; }
fun addOnFailure(handler: (String) -> Unit) : ThreadedTask<T> { onFailure.add(handler); return this; }
fun addOnComplete(handler: () -> Unit) : ThreadedTask<T> { onComplete.add(handler);return this; }
/**
* Performs the passed code in a threaded context and executes Success/Failure/Complete handler respectively on the calling thread.
* If any (uncaught) exception is triggered, the task is considered 'failed'.
* Call this method last in the chain to avoid race conditions while adding the handlers.
*
*/
fun execute(executor: Executor, code: () -> T)
{
val handler = object : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
publishResult(msg.what, msg.obj)
}
}
executor.execute {
try {
handler.obtainMessage(TASK_SUCCESS, code()).sendToTarget()
} catch (exception: Exception) {
handler.obtainMessage(TASK_FAILED, exception.toString()).sendToTarget()
}
}
}
private fun publishResult(returnCode: Int, returnValue: Any)
{
if (returnCode == TASK_FAILED)
onFailure.forEach { it(returnValue as String) }
else
onSuccess.forEach { it(returnValue as T) }
onComplete.forEach { it() }
// Removes all handlers, cleaning up potential retain cycles.
onFailure.clear()
onSuccess.clear()
onComplete.clear()
}
companion object {
private const val TASK_SUCCESS = 0
private const val TASK_FAILED = 1
}
}
The order of execution is important in this case.
You first need to add the callbacks to the class object and at the end you need to call execute and supply it with the executor you want to run the thread with and of course the code you want to execute.
It is not everything you can do with Google Drive, but it's a start and I hope this little compilation will save someone else some grief in the future.

Categories

Resources