How to remember state with retry operators in RxJava2 - android

I have a network client that is able to resume from interruptions, but needs the last message for doing so when there is a retry.
Example in Kotlin:
fun requestOrResume(last: Message? = null): Flowable<Message> =
Flowable.create({ emitter ->
val connection = if (last != null)
client.start()
else
client.resumeFrom(last.id)
while (!emitter.isDisposed) {
val msg = connection.nextMessage()
emitter.onNext(msg)
}
}, BackpressureStrategy.MISSING)
requestOrResume()
.retryWhen { it.flatMap { Flowable.timer(5, SECONDS) } }
// how to pass the resume data when there is a retry?
Question: as you can see, I need the last received message in order to prepare the resume call. How can I keep track of it so that when there is a retry it is available to make the resume request?
One possible solution may be to create a holder class that just holds a reference to the last message and is updated when a new message is received. This way when there is a retry the last message can be obtained from the holder. Example:
class MsgHolder(var last: Message? = null)
fun request(): Flowable<Message> {
val holder = MsgHolder()
return Flowable.create({ emitter ->
val connection = if (holder.last != null)
client.start()
else
client.resumeFrom(holder.last.id)
while (!emitter.isDisposed) {
val msg = connection.nextMessage()
holder.last = msg // <-- update holder reference
emitter.onNext(msg)
}
}, BackpressureStrategy.MISSING)
}
I think this might work, but it feels like a hack (thread synchronization issues?).
Is there a better way to keep track of the state so it is available for retries?

Note that, unless you rethrow a wrapper around your last element (not too functionally different from your existing "hack"-ish solution but way uglier imo), no error handling operators can recover the last element without some outside help because they only get access to streams of Throwable. Instead, see if the following recursive approach suits your needs:
fun retryWithLast(seed: Flowable<Message>): Flowable<Message> {
val last$ = seed.last().cache();
return seed.onErrorResumeNext {
it.flatMap {
retryWithLast(last$.flatMap {
requestOrResume(it)
})
}
};
}
retryWithLast(requestOrResume());
The biggest distinction is caching the trailing value from the last attempt in an Observable with cache rather than doing so manually in a value. Note also that the recursion in the error handler means retryWithLast will continue to extend the stream if subsequent attempts continue failing.

Take a close look to buffer() operator: link
You could use it like this:
requestOrResume()
.buffer(2)
From now, your Flowable will return List<Message> with two latests objects

Related

How to return values only after observing LiveData in Android [Kotlin]

I have been facing this issue for quite sometime and would like to know a better approach to solve this problem. If you are aware of anything about how to solve it then please let me know.
I am building a project which takes data from an API and then following MVVM architecture I take the Retrofit instance to Repository, and then to ViewModel and further observe it from my fragment.
Now what I am working on currently is Login feature. I will send a number to the API and in response I will receive if the number is registered or not. If it is registered then I would move to the next screen.
Now the problem is that using one of the function in ViewModel I send that number to the API to get the response. And using a variable I observe that response.
Now I create a function which checks if the response was true or false and based on that I am using the logic to move to the next screen, but the issue is the returned value from the function. As LiveData works asynchronously in background it takes some time to return the value and in meantime the function returns the initial value which is false.
Function to verify response
private fun checkNumber(): Boolean {
var valid = false
authRiderViewModel.response.observe(viewLifecycleOwner, Observer {
Timber.d("Response: $it")
if (it.success == true) {
valid = true
}
})
Timber.d("Boolean: $valid")
return valid
}
Moving to next screen code:
binding.btnContinue.setOnClickListener {
val number = binding.etMobileNumber.text.toString().toLong()
Timber.d("Number: $number")
authRiderViewModel.authDriver(number)
if (checkNumber()) {
val action = LoginFragmentDirections.actionLoginFragmentToOtpFragment()
findNavController().navigate(action)
} else {
Toast.makeText(requireContext(), "Number not registered", Toast.LENGTH_SHORT).show()
}
}
So in case I received the true response from the server even then I would not move to the next screen because the initial value I received is false. I have spent few hours trying to fix it and any help would be appreciated. If you need any code let me know in comments. Thanks.
You have four distinct states:
The server returned a positive response
The server returned a negative response
The server failed (e.g., returned a 500, timed out)
You are waiting on the server
You are attempting to model that with two states: true and false. This will not work.
Instead, model it with four states. One approach is called "loading-content-error" and uses a sealed class to represent those states:
sealed class LoginState {
object Loading : LoginState()
data class Content(val isSuccess: Boolean) : LoginState()
object Error : LoginState()
}
Your LiveData (or your StateFlow, once you migrate to coroutines) would be a LiveData<LoginState>. Your observer can then use a when to handle Loading, Content, and Error as needed, such as:
For Loading, display a progress indicator
For Content, do whatever you are doing now with your boolean
For Error, display an error message
Actually, live data observation is an asynchronous operation. You have to code accordingly.
Just calling checkNumber() won't return since is asynchronous instead I give you some ideas to implement in a better way.
Just call the checkNumber when button click inside the check number do this instead of return valid
authRiderViewModel.response.observe(viewLifecycleOwner, Observer {
Timber.d("Response: $it")
if (it.success == true) {
val action = LoginFragmentDirections.actionLoginFragmentToOtpFragment()
findNavController().navigate(action)
} else {
Toast.makeText(requireContext(), "Number not registered", Toast.LENGTH_SHORT).show()
}
})

Best way to handle network response in jetpack compose when using SRP

When using the Single Responsibility pattern, I wonder what would be the best approach to show network response (Success, error, progress). storing state for every request would create so many states in viewModel and will have to pass so many states to a component. Any other way, worth a try?
I use this in my main Activity's onCreate function to capture all unhandled exceptions but only when the app is released for production. During debug, I want the app to crash so that the exception stack is printed to LogCat:
if (!BuildConfig.DEBUG) {
Thread.setDefaultUncaughtExceptionHandler { paramThread, paramThrowable ->
val message: String
if (paramThrowable is HttpException) {
message = R.string.problem_processing_request.stringRes()
} else {
message = R.string.unknown_problem.stringRes()
}
App.mainViewModel.displaySnackbar(sbInfo = SnackbarInfo(message = message, isCritical = true))
}
}

LiveMutableData and copies update to same value?

I have a MutableLiveData variable in my AppRepository which is updated and contains my data. This I have no issues with. I also have the following observable to trigger a UI update with the data it holds in my onCreateView function:
viewModel.projectWithContent.observe(viewLifecycleOwner, {
pwc = it
counterList = it.counterList
})
When I tap either to increase or decrease the counter count and then try to push the update to my Room database, it skips it. I have the following check currently:
if(counterList != null) {
try {
for(counter: Counter in counterList!!) {
if(counter.counter_count != pwc?.counterList!![
pwc?.counterList!!.indexOf(counter)
].counter_count) {
Log.i(LOG_TAG, "Hello")
} else {
Log.i(LOG_TAG, "Goodbye")
}
}
} catch(e: IndexOutOfBoundsException) {
e.printStackTrace()
}
}
It'll always go to Goodbye.
Now. If I put the following just below try
Log.i(LOG_TAG, "PWC: ${pwc?.counterList!![0].counter_count}, " +
"CPWC: ${counterList!![0].counter_count}," +
"VMPWC: ${viewModel.projectWithContent.value?.counterList!![0].counter_count}")
It provides the following output:
PWC: 70, CPWC: 70,VMPWC: 70
Is this a side effect of what I'm doing or?
Thanks
Like #Tenfour04 says, your condition is actually checking they don't match, so "Goodbye" is the output when they do match.
If you don't mind (this is a little long), I just want to recommend some stuff because I feel like you're making life hard for yourself with all the null-checking that's going on - the logic of the code was really hard to read, and I'm guessing that's why you didn't notice the flipped logic too!
First: the ? null safety stuff (and !! which is the opposite of safe, never use it unless you know you have good reason) is there because you have nullable variable types. Normally the IDE would smart cast them to non-null once you've done a null check (like on your first line) - but because they're vars, they can be changed at any time.
That means that a variable that wasn't null before could be now, so you're forced to null-check every single time you access it. But even if the types weren't nullable, because they're vars, they can still change, and the thing you were looking at a moment ago is something different now.
The simple solution is to just make a new variable:
val counters = counterList
if (counters != null) {
...
}
// or if you want to use one of kotlin's scope functions
counterList?.let { counters ->
...
}
Because that new one is a val, it's not going to change what it's pointing at! Once it's null-checked, it's always going to be non-null, so you don't need to use ? anymore.
You have a couple of variables to make - you want to make sure pwc isn't null, and also their counterLists. A quick way to do that is with pwc?.counterList - if pwc is null, it will return null. Otherwise it will move to the next step, and return counterList, which may be null. (Using !! is saying that it definitely never will be null, in which case it shouldn't be nullable at all!)
And you don't actually care about pwc anyway - you're just comparing its counterList to the other, so why don't we pare it back to just those?
val counters = counterList
val pwcCounters = pwc?.counterList
if (counters != null && pwcCounters != null) {
try {
for(counter: Counter in counters) {
if(counter.counter_count != pwcCounters[
pwcCounters.indexOf(counter)
].counter_count) {
Log.i(LOG_TAG, "Hello")
} else {
Log.i(LOG_TAG, "Goodbye")
}
}
} catch(e: IndexOutOfBoundsException) {
e.printStackTrace()
}
}
There's more we could do here, but just by cleaning up those nulls and using the specific variables we want to work with, does that feel easier to read? And more importantly, easier to understand what's happening and what could happen?
Might be worth throwing it in a function too, stops the call site getting cluttered with these temp variables:
fun doThing(counters: List<Counter>?, pwcCounters: List<Counter>?) {
if (counters == null || pwcCounters == null) return
// do the stuff
}
// when you want to do the thing:
doThing(counterList, pwc?.counterList)
So all your null checking is out of the way, your "temp variables" are the fixed parameters passed to the function, it's all nice and neat.
I know this is a long post for such a short bit of code, but it's a good habit to get into - if you're writing code where you're working with nullable vars and you're wrestling with the null safety system, or you keep repeating yourself to access a particular variable nested inside another object, you can make things a lot easier for yourself! You can imagine how wild this could all get for more complex code.
Also if you care, this is how I'd personally write it, if it helps!
fun doThing(counters: List<Counter>?, pwcCounters: List<Counter>?) {
if (counters == null || pwcCounters == null) return
// for (counter in Counters) is fine too I just like this version
counters.forEach { counter ->
// find returns the first item that matches the condition, or null if nothing matches,
// so no need to handle any exceptions, just handle the potential null!
// (this is a really common Kotlin pattern, lots of functions have a "returns null on failure" version)
val pwcCounter = pwcCounters.find { it == counter }
// remember pwcCounter can be null, so we have to use ? to access its count safely.
// If it evaluates to null, the match just fails
if (counter.count == pwcCounter?.count) Log.i(LOG_TAG, "Hello")
else Log.i(LOG_TAG, "Goodbye")
}
}
I also renamed counter_count to just count since it's a property on a Counter anyway. I feel like counter.count is easier to read than counter.counter_count, y'know? It's the little things

Does offer make the coroutines close or cancelled?

I was looking at the flow documentation on the Android Developer site and I have a question.
https://developer.android.com/kotlin/flow#callback
If you look at the above link, you will see code like this.
class FirestoreUserEventsDataSource(
private val firestore: FirebaseFirestore
) {
// Method to get user events from the Firestore database
fun getUserEvents(): Flow<UserEvents> = callbackFlow {
// Reference to use in Firestore
var eventsCollection: CollectionReference? = null
try {
eventsCollection = FirebaseFirestore.getInstance()
.collection("collection")
.document("app")
} catch (e: Throwable) {
// If Firebase cannot be initialized, close the stream of data
// flow consumers will stop collecting and the coroutine will resume
close(e)
}
// Registers callback to firestore, which will be called on new events
val subscription = eventsCollection?.addSnapshotListener { snapshot, _ ->
if (snapshot == null) { return#addSnapshotListener }
// Sends events to the flow! Consumers will get the new events
try {
offer(snapshot.getEvents())
} catch (e: Throwable) {
// Event couldn't be sent to the flow
}
}
// The callback inside awaitClose will be executed when the flow is
// either closed or cancelled.
// In this case, remove the callback from Firestore
awaitClose { subscription?.remove() }
}
}
In the code above, awaitClose is explained to be executed when the coroutine is closed or cancelled.
But, there is no close() in the code except for the try-catch statement that initializes the eventsCollection.
Additionally, says offer does not add the element to the channel and **returns false** immediately at the bottom of the Android Developer page.
My question is, in the code above, when offer(snapshot.getEvents()) is executed, does the coroutine cancel with return false, so awaitClose is executed?
Expectation:
As the documentation says:
When you try to add a new element to a full channel, send suspends the
producer until there's space for the new element, whereas offer does
not add the element to the channel and returns false immediately.
Ergo:
It Immediately adds the specified element to this channel, if this doesn’t violate its capacity restrictions, and returns the successful result. Otherwise, returns failed or closed result. This is synchronous variant of send, which backs off in situations when send suspends or throws.
So when trySend call returns a non-successful result, it guarantees that the element was not delivered to the consumer, and it does not call onUndeliveredElement that was installed for this channel. See “Undelivered elements” section in Channel documentation for details on handling undelivered elements.
Conclusion:
A typical usage for onDeliveredElement is to close a resource that is being transferred via the channel. The following code pattern guarantees that opened resources are closed even if producer, consumer, and/or channel are cancelled. Resources are never lost. So no it doesn't return false.

How to Implement "Railway Pattern" in Kotlin/Arrow.kt for producer Channel

I'm investigating Kotlin Coroutines & Channels in my current Android application.
I have the following code that manages remote Api calls and controls UI Side effects
private val historical: CompletableDeferred<List<Any>> = CompletableDeferred()
private val mutex = Mutex()
#ExperimentalCoroutinesApi
fun perform(action: Action): ReceiveChannel<List<Any>> =
produce {
mutex.withLock {
if (historical.isCompleted) {
send(historical.getCompleted())
return#produce
}
send(action.sideEffects)
val networkResponse = repository.perform(action)
send(networkResponse.sideEffects)
send(listOf(networkResponse)).also {
historical.complete(listOf(response))
}
}
}
The above code gives me the desired result, however I would like to refactor it to something resembling
the Functional Programming "Railway Pattern" https://android.jlelse.eu/real-world-functional-programming-with-kotlin-arrow-b5a98e72f5e3
where my process flow is
stepOne(Historical.completed)
.stepTwo(action.sideEffects)
.stepThree(getReaction())
.stepFour(reaction.sideEffects)
.finalStep(reaction)
which will "short circuit" on either failures of any step or when Historical "isCompleted"
is it possible to achieve this style of call in Kotlin? and/or Kotlin & Arrow.kt?
You can use Arrow-kt's Either.
You can use Either's mapLeft(), map(), flatMap().
In case result is Exception use mapLeft(). Returned value from mapLeft() going to be new Left in result Either, for example return is String, result going to be Either<String, List<Any>>. In case result is Right, ie List<Any>, mapLeft() going to be skipped, but result type changes anyway, so you will have type of Either<String, List<Any>> with value of Right<List<Any>>. You can also return same Exception from mapLeft() if you choose so
I case you have no need in handling specific errors, you can just chain map() and flatMap(). map() is basically mapRight() and flatMap() is useful when you want to chain calls, ie somewhere in chain there is a receiver of List<Any> which can fail and you want to handle Exception the same way with Either for that call, you can just return new Either from flatMap()
Code gonna look something like this
fun perform(action: Either<Exception, Action>): ReceiveChannel<List<Any>> =
produce {
// if action is always right, you can start it as Right(action) but then first mapLeft does not make any sense
if (historical.completed) action
.mapLeft {
// handle actions exception here
// transform it to something else or just return it
send(action.sideEffects)
it
}.flatMap {
// handle right side of action either
// assume here that repository may fail and returns Either<Exception, NetworkResponse>
repository.perform(it)
}.mapLeft {
// handle repositorys exception here
// transform it to something else or just return it
send(it)
it
}.map {
// handle network response
send(listOf(networkResponse))
historical.complete(listOf(networkResponse))
}
}

Categories

Resources