Implement retry logic with Mutiny - kotlin

I'm just learning Mutiny and I need to implement retry logic.
I have this code:
fun main() {
getResult()
.onFailure().invoke { t -> println("Got error: $t") }
.onFailure().retry().atMost(2)
.subscribe().with(
{ result -> println(result) },
{ t -> t.printStackTrace() }
)
}
fun getResult(): Uni<String?> {
println("Preparing result...")
return Uni.createFrom().failure(Exception("Some error happened"))
}
So, the getResult() is a function that may misbehave and needs to be called multiple times on failure.
When I run this program, this is what's happening:
Preparing result...
Got error: java.lang.Exception: Some error happened
Got error: java.lang.Exception: Some error happened
Got error: java.lang.Exception: Some error happened
java.lang.Exception: Some error happened
at MainKt.getResult(Main.kt:16)
at MainKt.main(Main.kt:4)
Obiously, the getResult() function is called only once, while the onFailure() stages actually executed three times.
Is there anything that Mutiny could help me to execute getResult() function on each failure? I sure can implement this with a simple loop, but I feel like Mutiny should already have something like this.
Unfortunately, I didn't find anything suitable in the docs.

Your Uni in getResult is created with an "immediate" item, which is cached and never computed again.
Use Uni.createFrom().failure(() -> Exception("Some error happened"))
In this case, it's a supplier, so it won't be cached but called on every attempt.

So, the right solution for this is actually using the Uni.deferred() method like this:
fun main() {
Uni.createFrom().deferred { getResult() }
.onFailure().invoke { t -> println("Got error: $t") }
.onFailure().retry().atMost(2)
.subscribe().with(
{ result -> println(result) },
{ t -> t.printStackTrace() }
)
}
Thanks to Boris the Spider, who suggested to use the deferred(), and to Clement, who clarified its use with null values.
Initially, I misinterpreted the deferred() documentation thinking it's not allowed to return a null value, but actually it's OK for a Supplier to return a Uni of null:
Uni.createFrom.deferred { Uni.createFrom().nullItem() }
What the docs really are prohibiting is returning a null instead of a Uni:
Uni.createFrom().deferred { null }

Related

How to handle simple error in Kotlin function Android

I'm having trouble handling error in the following function. I'm basically new to Kotlin. Here's my RevenueCat Login Code and I want to handle ::error in this code:
Purchases.sharedInstance.logInWith(
myUserID,
::error // <- How to handle this? I want to retrieve error Code and Error Message.
)
{ customerInfo, created ->
// Handle Successful login here
}
Here's the code behind the function (within RevenueCat SDK)
#Suppress("unused")
fun Purchases.logInWith(
appUserID: String,
onError: (error: PurchasesError) -> Unit = ON_ERROR_STUB,
onSuccess: (customerInfo: CustomerInfo, created: Boolean) -> Unit
) {
logIn(appUserID, logInSuccessListener(onSuccess, onError))
}
The double colon in ::error is a function reference. It is basically a reference to the function error().
And from your logInWith() function, we have onError: (error: PurchasesError) -> Unit = ON_ERROR_STUB, meaning that the function should take PurchasesError as input parameter and does not need to return.
So we can derive a function as the following:
fun error(error: PurchasesError) {
// And you can do something with the error here
}
I solved it like this:
Purchases.sharedInstance.logInWith(
myUserID,
onError = { error ->
// Handle error here
}

Why is the value not entering the list?

At 'urichecking2' log, I can see there is value. But in 'uriChecking' the uriList is null.
why the uriList.add not work??
private fun getPhotoList() {
val fileName = intent.getStringExtra("fileName")
Log.d("fileNameChecking", "$fileName")
val listRef = FirebaseStorage.getInstance().reference.child("image").child(fileName!!)
var tmpUrl:Uri = Uri.parse(fileName)
Log.d("firstTmpUri","$tmpUrl")
listRef.listAll()
.addOnSuccessListener { listResult ->
for (item in listResult.items) {
item.downloadUrl.addOnCompleteListener { task ->
if (task.isSuccessful) {
tmpUrl = task.result
Log.d("secondTmpUri","$tmpUrl")
Log.d("urichecking2","$task.result")
uriList.add(task.result)
} else {
}
}.addOnFailureListener {
// Uh-oh, an error occurred!
}
}
}
Log.d("thirdTmpUri","$tmpUrl")
Log.d("urichecking", "$uriList")
}
If I do this, the log is output in the order of first, third, and second, and the desired value is in second, but when third comes out, it returns to the value of first.
The listAll method (like most cloud APIs these days, including downloadUrl which you also use) is asynchronous, since it needs to make a call to the server - which may take time. This means the code executes in a different order than you may expect, which is easiest to see if you add some logging:
Log.d("Firebase","Before starting listAll")
listRef.listAll()
.addOnSuccessListener { listResult ->
Log.d("Firebase","Got listResult")
}
Log.d("Firebase","After starting listAll")
When you run this code it outputs:
Before starting listAll
After starting listAll
Got listResult
This is probably not the order you expected, but it perfectly explains why you can't see the list result. By the time your Log.d("urichecking", "$uriList") runs, none of the uriList.add(task.result) has been called yet.
The solution for this is always the same: any code that needs the list result, has to be inside the addOnCompleteListener callback, be called from there, or be otherwise synchronized.
So in its simplest way:
listRef.listAll()
.addOnSuccessListener { listResult ->
for (item in listResult.items) {
item.downloadUrl.addOnCompleteListener { task ->
if (task.isSuccessful) {
uriList.add(task.result)
Log.d("urichecking", "$uriList")
}
}
}
}
This is an incredibly common mistake to make if you're new to programming with asynchronous APIs, so I recommend checking out
Asynchronous programming techniques in the Kotlin language guide
How to get URL from Firebase Storage getDownloadURL
Can someone help me with logic of the firebase on success listener
Why does my function that calls an API or launches a coroutine return an empty or null value?

Why can't use continue in let or run

Why it is not allowed to continue from let function?
This code:
fun foo(elements: List<String?>) {
for (element in elements) {
element?.let {
continue // error: 'break' or 'continue' jumps across a function or a class boundary
}
}
}
And even this code:
fun foo(elements: List<String?>) {
loop# for (element in elements) {
element?.let {
continue#loop // error: 'break' or 'continue' jumps across a function or a class boundary
}
}
}
Does not compile with error:
'break' or 'continue' jumps across a function or a class boundary
I know that in this particular case I can use filterNotNull or manual check with smart cast, but my question is why it is not allowed to use continue here?
Please vote for this feature here: https://youtrack.jetbrains.com/issue/KT-1436
These would be called "non-local" breaks and continues. According to the documentation:
break and continue are not yet available in inlined lambdas, but we are planning to support them too.
Using a bare (e.g. non-local) return inside a lambda is only supported if it is an inlined lambda (because otherwise it doesn't have awareness of the context it is called from). So break and continue should be able to be supported. I don't know the reason for the functionality to be delayed.
Note, there are work-arounds for both of them by run either inside or outside the loop, and taking advantage of the fact that at least non-local returns are supported for inline functions.
fun foo(elements: List<String?>) {
run {
for (element in elements) {
element?.let {
println("Non-null value found in list.")
return#run // breaks the loop
}
}
}
println("Finished checking list")
}
fun bar(elements: List<String?>) {
for (element in elements) {
run {
element?.let {
return#run // continues the loop
}
println("Element is a null value.")
}
}
}

Kotlin: lambda run alternative scenario

I have userDto, contains programs, which contains actual field. Actual program can be only one. I need to get it. Than, I run this:
userDto.programs.sortedBy { it.created }.findLast { it.actual }?
Okay, but I want to foresee case, when findLast returns null, & throw exception. Please, advice, how to do it?
UPD:
ProgramType.valueOf(userDto.programs
.sortedBy { it.created }
.findLast { it.actual }
//check here
!!.programType!!).percentage
You are pretty close actually :)! What you could do is:
userDto.programs.sortedBy { it.created }.findLast { it.actual } ?: throw RuntimeException()
Or if you're trying to actually avoid throwing an error(couldn't really tell with the way question is asked), you could just do an error check like this:
userDto.programs.sortedBy { it.created }.findLast { it.actual }?.let{
//rest of your code goes here
}
Hope this helps, cheers!

Return from `buildSequence` in Kotlin

I'm using the buildSequence function in Kotlin. How do I end the iteration in the middle of the function? I'm looking for something similar to C#'s yield break statement.
My code looks something like the following. I'm stuck at the TODO.
fun foo(list:List<Number>): Sequence<Number> = buildSequence {
if (someCondition) {
// TODO: Bail out early with an empty sequence
// return doesn't seem to work....
}
list.forEach {
yield(someProcessing(it))
}
}
EDIT
Apparently, I misdiagnosed the source. The issue is not returning from the buildSequence function. The following works for me:
fun foo(list:List<Number>): Sequence<Number> = buildSequence {
return#buildSequence
list.forEach {
yield(someProcessing(it))
}
}
EDIT 2
The issue is that I put the return in a local helper function that validates data at multiple points in the buildSequence (Hence the helper function). Apparently I'm not able to return from buildSequence within the helper function. The error message was not terribly helpful...
Just use return#buildSequence, which is a labeled return from lambda, while an unlabeled return would mean 'return from the function foo'.
See also: Whats does “return#” mean?
Since Kotlin v 1.3.x preferred sequence syntax changed. (buildSequence is replaced by kotlin.sequences.sequence)
Updated "early return from generator" code snippet (includes try-catch and == null early return examples) for post 1.3.x Kotlin:
// gen# is just a subjective name i gave to the code block.
// could be `anything#` you want
// Use of named returns prevents "'return' is not allowed here" errors.
private fun getItems() = sequence<Item> gen# {
val cursor: Cursor?
try {
cursor = contentResolver.query(uri,*args)
} catch (e: SecurityException) {
Log.w(APP_NAME, "Permission is not granted.")
return#gen
}
if (cursor == null) {
Log.w(APP_NAME, "Query returned nothing.")
return#gen
}
// `.use` auto-closes Closeable. recommend.
// https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.io/use.html
cursor.use {
// iterate over cursor to step through the yielded records
while (cursor.moveToNext()) {
yield(Item.Factory.fromCursor(cursor))
}
}
}
(Thx for all the prior posts that helped me get on "named return" track.)