Compose Snackbar not appearing on repeated error - android

I am new to jetpack compose and is trying to show an error snackbar whenever the error message I am observing is not null.
Scaffold(scaffoldState = scaffoldState) {
LaunchedEffect(errorMessage) {
if (errorMessage != null) {
scope.launch {
scaffoldState.snackbarHostState.showSnackbar(errorMessage)
}
}
}
Column(horizontalAlignment = Alignment.CenterHorizontally) {
//some ui components inside here
}
}
The issue in the above code is that, the first time the error message changes from null to a particular message it appears fine. However on a repeated user action that produces the same error message it's not coming again.
P.S - I know this is happening due to placing the errorMessage as key inside the LaunchedEffect. My doubt is that, is there a different approach to achieve what I want?

This is happening because the LaunchedEffect will run again just in case the errorMessage has changed.
What you can do is:
LaunchedEffect(errorMessage) {
if (errorMessage != null) {
resetErrorMessage() // reset errorMessage
scope.launch {
scaffoldState.snackbarHostState.showSnackbar(errorMessage)
}
}
}
The resetErrorMessage must set the errorMessage to null, so the LaunchedEffect will run again, but since you're checking if it is not null, nothing will happen. But as soon you receive a new error message, the LaunchedEffect will be executed again.

Related

Firebase authorisation

mAuth?.createUserWithEmailAndPassword(email, password)
?.addOnCompleteListener { task ->
if (task.isSuccessful) {
val user = User(fullName, age, email)
FirebaseDatabase.getInstance().getReference("user")
.child(FirebaseAuth.getInstance().currentUser!!.uid)
.setValue(user).addOnCompleteListener { childTask ->
if (childTask.isSuccessful) {
findNavController().navigate(R.id.action_registrationWindow_to_loginWindow)
showToast("User has been registered successfully")
} else {
showToast("Failed to register! Try again!")
}
progressBar.visibility = View.GONE
}
} else {
showToast("Failed to register! Try again!")
}
}
progressBar.visibility = View.GONE
I'm trying to check in the second addOnCompleteListener the condition of operation, but the second block of addOnCompleteListener isn't called. And I don't have any mistakes, the user is added to database, but block isn't called.
Is it redirecting to different fragment though? according to your logic, it may not show if it is redirecting to different screen before displaying toast, I would also suggest you try use Log instead of Toast and check in the Logcat for any errors you might have missed out,or in general debug using the Logcat as toasts can mess up when there is threads and may not show, also if you are testing on an emulator sometimes Toasts don't show, check on a real device and see if you still face the problem

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))
}
}

Why is my null check unreachable?

In the following example i have an nullable property userId. I would like throw an Exception if it null. Android studio is telling me the code block inside if(userId == null) is unreachable. Can anyone explain why this is unreachable?
return Observable.create<Optional<UserEntity>> { it ->
val userId: String? = firebaseAuth.currentUser?.uid
if(userId == null){
it.onError(throw RuntimeException("Unauthorised"))
it.onComplete()
}else{
//Do something
}
}
Ok... I see... in fact it is the following line that contains the unreachable code:
it.onError(throw RuntimeException("Unauthorised"))
The reason: you throw your exception immediately and not when there occurs an error in processing. In fact the onError itself becomes unreachable.
onError however, needs the exception to throw as passed argument, so what you rather want is something like:
it.onError(RuntimException("Unauthorised"))
i.e. omit the throw.

Firebase AuthInstance.currentUser is null after successfully reload

I'm currently having an issue during some of my users who log-in with email and password. The case is that after send the verification email, I'm calling reload in my current FirebaseUser to update the currentUser an know if the email have been verified or not.
Everything works fine at this point. The thing is that sometimes after call reload over an instance of FirebaseAuth.currentUser.reload(), on the successful callback of reload I'm trying to access again to the already updated FirebaseAuth.currentUser instance, and the funny thing is that this comes to be null and I'm getting a NPE, when the user have been successfully reloaded and the instance should be updated, not deleted.
This is my code right now:
override fun verifyUser() {
if (authInstance.currentUser == null) {
dispatcher.dispatchOnUi(VerifyUserEmailCompleteAction(requestState = requestFailure(FirebaseUserNotFound()),
verified = false))
return
}
RxFirebaseUser.reload(authInstance.currentUser!!)
.subscribeOn(Schedulers.io())
.subscribe({
dispatcher.dispatchOnUi(VerifyUserEmailCompleteAction(requestState = requestSuccess(),
verified = authInstance.currentUser!!.isEmailVerified))
}, { error ->
dispatcher.dispatchOnUi(VerifyUserEmailCompleteAction(requestState = requestFailure(error),
verified = false))
})
}
The RxFirebase call is a Reactive wrapper over Firebase, but I have tried this code also using the normal Promise of the library and this error is happening too.
My NPE is coming on verified= authInstance.currentUser!!.isEmailVerified which should not be possible because I already did a check before start this call and the reload call have been successful.
Someone have suffer this issue and know why this could be happening? How can be the workaround for it? I could control the NPE there, but if the reload have been successful I want to update my user in my app data, not send a null instance.
The authInstance.currentUser sometimes takes some time to be updated. You can move your reload call to the main thread on the observeOn to give authInstance more time to be updated, and also just in case add a retry clause to your code. It would be something like this:
RxFirebaseUser.reload(authInstance.currentUser!!)
.subscribeOn(Schedulers.io())
.repeatUntil { authInstance.currentUser != null }
.defaultTimeout()
.subscribe({
val user = authInstance.currentUser
val emailVerified = user?.isEmailVerified ?: false
dispatcher.dispatchOnUi(VerifyUserEmailCompleteAction(requestState = if (user == null) requestRunning() else requestSuccess(),
verified = emailVerified))
}, { error ->
dispatcher.dispatchOnUi(VerifyUserEmailCompleteAction(requestState = requestFailure(error),
verified = false))
})

How to remember state with retry operators in RxJava2

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

Categories

Resources