I tried to store a result from an async Kotlin method to a variable.
Amplify.Auth.fetchAuthSession(
{result ->
Log.i(TAG, "Amplify: Fetch Auth Session $result")
isAuthenticated = result.isSignedIn
},
{error ->
Log.e(TAG , "Amplify: Fetch Auth Session $error")
}
)
if (isAuthenticated == true) {
[...]
I really don't know how to set the result.isSignedIn to the isAuthenticated variable, that I can use it outside of the closure. I found a similar question on stackoverflow, but it did not help me out.
May someone can help me?!
What are you doing here (in terms of saving the value within the lambda) is technically correct, but functionally - not necessarily: the value is used before it is updated by the lambda.
Consider starting the further logic from the callback lambda, where you receive the result.
Please see the snippet below for a better overview:
Amplify.Auth.fetchAuthSession(
{result ->
Log.i(TAG, "Amplify: Fetch Auth Session $result")
handleResult(result.isSignedIn)
},
{error ->
Log.e(TAG , "Amplify: Fetch Auth Session $error")
}
)
} // end of the method with the async call
fun handleResult(isAuthenticated: Boolean) {
// here goes the code that consumes the result
if (isAuthenticated) {
// ...
}
}
I'm not sure whether this is the best practice, but it is something that worked out for me in terms of code readability and functionality when it comes to async functions.
Say you have an async function doSomethingAsync. If I need it to be completed before following through with other instructions (such as your if statement), I pass on 1 or more functions to the async function, with each function covering a certain scenario.
What I mean by this is the following:
fun doSomethingAsync() (
onSuccess : (fetchedObject : Any) -> Unit, // Do stuff with returned value
onFailure : (e : Exception) -> Unit, // Catch an exception
onComplete : () -> Unit // Do stuff after the value has been fetched
) {
// Your async function body, which could return a Task
.addOnSuccessListener { result ->
onSuccess(result)
onComplete()
}
.addOnFailureListener { exception ->
onFailure(exception)
}
}
In your case, you could fit your if statement and stuff that follows into a function, which you could pass onto the async function as a function parameter, to be called after the async task is complete.
Amplify.Auth.fetchAuthSession(onComplete : () -> Unit)
{result ->
Log.i(TAG, "Amplify: Fetch Auth Session $result")
isAuthenticated = result.isSignedIn
onComplete()
},
{error ->
Log.e(TAG , "Amplify: Fetch Auth Session $error")
}
)
fun onComplete() {
// Assume that ifAuthenticated == true, and call this after
// you get the Auth results.
}
Related
I am struggling with Firestore.
suspend fun writeClientMemo(userId: String, memo: Memo): MemoWriteResponse {
val response = MemoWriteResponse()
val docRef = Firebase.firestore
.collection(COLLECTION_USER)
.document(userId)
val result = Tasks.await(docRef.get())
if (result.exists() && result.data != null) {
Log.d("memo", "data exists ")
val res = docRef.update(FIELD_MEMO_LIST, FieldValue.arrayUnion(memo))
res.addOnSuccessListener {
Log.d("memo", "success")
response.isSuccessful = true
// What I want to do is something like these:
// 1. return#addOnSuccessListener response
// 2. return response
}.addOnFailureListener { e ->
Log.d("memo", "failed")
response.isSuccessful = false
response.errorMessage = "${e.toString}"
}
}
return response // At the moment this always get false(default is false)
}
I tried to make this synchronous but didn't work.
when {
res.isSuccessful -> {
response.isSuccessful = true
Log.d("memo", "true: ${response.toString()}")
}
res.isCanceled -> {
response.run {
isSuccessful = false
errorMessage = "ADD: Error writing document}"
}
Log.d("memo", "false: ${response.toString()}")
}
res.isComplete -> {
Log.d("memo", "false2: ${response.toString()}")
}
else->{
Log.d("memo", "false3: ${response.toString()}")
}
}
I always get false3 here.
Is there any way to return value and end a function in a callback?
No, you cannot simply return a value of an asynchronous operation as a result of a method. Firebase API, as most modern cloud/ APIs are asynchronous. This means that it takes some time until the operation completes. Since you are using the Kotlin programming language, I recommend you consider using Kotlin Coroutines and call .await() like this:
docRef.get().await();
This means that you suspend the operation until the data is available. This means that this value can be returned as a result of a method.
For more information you can check my answers from the following post:
How to return a list from Firestore database as a result of a function in Kotlin?
Or read the following article:
How to read data from Cloud Firestore using get()?
Where I have explained four ways in which you can deal with the Firestore using get().
P.S.
I tried to make this synchronous but didn't work
Never try to do that. It's best to get used to dealing with Firestore in such a way. Hopefully, my explanation from the above answer and article can help you a little bit.
You can achieve this using suspendCoroutine() utility function. It is a common practice to use it to convert asynchronous callback-based API to synchronous suspend API. You use it like this:
suspend fun loadSomeData(): String {
return suspendCoroutine { cont ->
loadSomeDataAsync(
onSuccess = {
cont.resume("ok!")
},
onFailure = {
cont.resume("failed!")
// OR:
cont.resume(null)
// OR:
cont.resumeWithException(Exception("failed"))
}
)
}
}
suspendCoroutine() suspends execution in outer scope until one of resume*() function is called. Then it resumes with the provided result.
I have created the following extension function :
fun <T> Flow<T>.handleErrors(showError: Boolean = false, retry: Boolean = false,
navigateBack: Boolean = true): Flow<T> =
catch { throwable ->
var message: String? = null
if (showError) {
when (throwable) {
is HttpException -> {
postEvent(EventType(retry))
}
}
}
The extension function then posts the throwable type to a Base Activity and based on the event type posted a relevant dialog is displayed.
If the event is a retry, I would like to retry the failed flow.
For example if the HTTP exception is a 400, and I would like to retry the failed call when retry is selected on the dialog.
Is it possible to add callback to a Kotlin Flow, that has failed and can be called, from a different activity or fragment?
I don't think you want to retry in a separate block, you can organize your code like this
fun presentDialog(onClick: (Boolean) -> Unit) {
// some code to compile you dialog / install callbacks / show it
onClick(true) // user-click-retry
}
suspend fun main() {
val source = flow {
while (true) {
emit(
if (Math.random() > 0.5) Result.success(100) else Result.failure(IllegalArgumentException("woo"))
)
}
}
source.collect { result ->
suspendCancellableCoroutine<Unit> { cont ->
result.onSuccess {
println("we are done")
}.onFailure {
presentDialog { choice ->
if (choice) {
cont.resumeWith(Result.success(Unit))
} else {
println("we are done")
}
}
}
}
}
}
now some explanations
as you already know, flow is cold,if you don't collect it, it will never produce, as a result, if your collect block does not return, the remaining thunk in flow builder after emit will not get executed,
you suspend the execution of flow builder by calling suspendCoroutine in collect block, if an HTTP error occurs, you show your dialog, and resume the execution according to user response, if no error happens or users just don't click retry, leave everything alone. the sample code above is somehow misleading, for a general case, you don't supply a retry mechanism when everything goes fine, so the while true block could change to
flow {
do {
val response = response()
emit(response)
} while(response.isFailure)
}.collect {
it.onSuccess { println("of-coz-we-are-done") }.onFailure {
// suspend execution and show dialog
// resume execution when user click retry
}
}
which may comfort you that the flow has an end, but actually it is basically the same
I am calling an API in an asynchronous function, and would like to store the response from the callback.
Specifically, I am using the API category of AWS Amplify in an Android app, and would like to assign the return value to a LiveData variable in a ViewModel.
fun getMuscleGroup(id: String): ExampleData {
var exampleData = ExampleData.builder().name("").build()
Amplify.API.query(
ModelQuery.get(ExampleData::class.java, id),
{ response ->
Log.d("AmplifyApi", response.data.name)
exampleData = response.data
},
{ error -> Log.e("AmplifyApi", "Query failure", error) }
)
return exampleData
}
I can receive the response and it gets logged correctly, but the response is not assigned to the exampleData variable, since the function returns early.
In Android Studio, the variable exampleData is highlighted with the text:
Wrapped into a reference object to be modified when captured in a closure
As I am not that familiar with the multithreading APIs in kotlin, I am not sure, how to block the function until the remote API returns its asynchronous response.
The most basic way of doing this would be with standard Java thread safety constructs.
fun getMuscleGroup(id: String): ExampleData {
var exampleData = ExampleData.builder().name("").build()
val latch = CountDownLatch(1)
Amplify.API.query(
ModelQuery.get(ExampleData::class.java, id),
{ response ->
Log.d("AmplifyApi", response.data.name)
exampleData = response.data
latch.countDown()
},
{ error ->
Log.e("AmplifyApi", "Query failure", error)
latch.countDown()
}
)
latch.await()
return exampleData
}
Since this is on Android, this is probably a bad solution. I'm going to guess that getMuscleGroup is being called on the UI thread, and you do not want this method to actually block. The UI would freeze until the network call completes.
The more Kotlin way of doing this would be to make the method a suspend method.
suspend fun getMuscleGroup(id: String): ExampleData {
return suspendCoroutine { continuation ->
Amplify.API.query(
ModelQuery.get(ExampleData::class.java, id),
{ response ->
Log.d("AmplifyApi", response.data.name)
continuation.resume(response.data)
},
{ error ->
Log.e("AmplifyApi", "Query failure", error)
// return default data
continuation.resume(ExampleData.builder().name("").build())
}
}
}
This use Kotlin coroutines to suspend the coroutine until an answer is ready and then return the results.
Other options would be to use callbacks instead of return values or an observable pattern like LiveData or RxJava.
You cannot block to wait for the asynchronous result. At worst it can cause an Application Not Responding (ANR) error message to appear for the user, and at best makes your app look stuttery and feel unresponsive.
You can instead add a callback for this function:
fun getMuscleGroup(id: String, callback: (ExampleData) -> Unit) {
var exampleData = ExampleData.builder().name("").build()
Amplify.API.query(
ModelQuery.get(ExampleData::class.java, id),
{ response ->
Log.d("AmplifyApi", response.data.name)
callback(response.data)
},
{ error -> Log.e("AmplifyApi", "Query failure", error) }
)
}
And then in the spot where you call the code, you put your subsequent actions in the callback:
fun onMuscleGroupClicked(id: String) {
getMuscleGroup(id) { exampleData ->
// do something with the data after it arrives
}
}
Coroutines are another option. They're nice because you don't have to nest your sequential actions in callbacks. To set it up, I would create a suspend extension function version of the API library query function by using suspendCancellableCoroutine. Then you can freely use it in other suspend functions in an orderly way. But you'll need to read the documentation on coroutines. It's to much to explain here from scratch.
In the below code, I am trying to add the body for the .subscribe(). I tried to add the lambda notation but it never worked. Would you please tell me how to implement the .subscribe() method?
Given that, the setupCommRequestService() returns Single<..>
code:
setupCommRequestService()?.
flatMap {
it.getAllPhotos()
.map {
Observable.fromIterable(it)
.map {
it
}
}
.toSortedList()
}
?.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(
)
There are 4 implementations for subscribe method according Single documentation. In a simple approach, you should implement a strategy for both onSucess and onError. therefor you should use the subscribe method either by passing a BiConsumer or 2 Consumer one for onSucess case and one for onError.
using BiConsumer in lambda:
val disposable = Single.just(1)
.subscribe { success, failure ->
/* whichever is not null */
}
or using 2 Consumer in lambda:
val disposable = Single.just(1)
.subscribe({ success ->
/* success */
}, { failure ->
/* failure */
})
I want to write a function that automatically subsbcribes to RxJava's Flowable<T> and get the resulting data. This data will then be passed as an argument to another method that does the processing. I am struggling with Kotlin's extension function syntax and generics.
I want to convert this call:
val scheduler = Schedulers.newThread()
disposable.add(
viewModel.getExams().subscribeOn(scheduler)
.observeOn(scheduler)
.subscribe({ exams ->
exams.forEach {
getSubjectOfExam(it, Schedulers.newThread())
}
}, { error ->
Log.e(
"OverviewFragment",
"Unable to fetch list, $error"
)
})
)
which is very lengthy in my Activity code, to a method that returns the data that I want to process.
In this case I'd like a list of exams (List<Exam>) passed into the argument of getSubjectOfExam(), which is the method for the list processing.
My function so far, which compiles but does not work at all:
/**
* General subscription of items in a Flowable list
* #param f method to be executed when list is loaded
* #param scheduler scheduling units
*/
private fun Flowable<out List<Any>>.listSubscribe(
f: (List<Any>) -> Unit,
scheduler: Scheduler
) {
disposable.add(
this.subscribeOn(scheduler)
.observeOn(scheduler)
.subscribe({
f(it)
}, { error ->
Log.e(
"OverviewFragment",
"Unable to fetch list, $error"
)
})
)
}
it will be called like so:
viewModel.getExams().listSubscribe({ resultData ->
resultData.forEach {
val exam = it as Exam
getSubjectOfExam(exam, Schedulers.newThread())
}
}, Schedulers.newThread())
So yeah, I tried to make an extension function and passing a function as one of its arguments (called a higher-order function I believe).
With my method, the getSubjectOfExam doesn't get called at all. Is there something I'm missing?
I'll be subscribing to Flowable's all the time in my Activity so this function will really help me.
I tried your code and it seems it is working okay. Is there any chance that viewModel.getExams() or getSubjectOfExam() is not working?
Also I could suggest few optimizations:
protected fun <T> Flowable<out List<T>>.listSubscribe(
f: (List<T>) -> Unit,
scheduler: Scheduler
) {
disposable.add(
this.subscribeOn(scheduler)
.observeOn(scheduler)
.subscribe(f, { error ->
Log.e(
"OverviewFragment",
"Unable to fetch list, $error"
)
})
)
}
Then you won't need type conversion:
viewModel.getExams().listSubscribe({ resultData ->
resultData.forEach {
getSubjectOfExam(exam, Schedulers.newThread())
}
}, Schedulers.newThread())
In fact you can replace List<T> with just T and make it work with any types. Also, observing and subscribing with same scheduler doesn't make a lot of sense to me. I think you can remove.observeOn completely and the code will still observe on the same scheduler you put in .subscribeOn