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
Related
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.
I am writing a toy Android app using Kotlin flow and Android Paging 3 library. The app calls some remote API to get a list of photos, and display them using a RecyclerView with a PagingDataAdapter.
I find that the code after pagingAdapter.submitData() is not executed.
Here is the code snippet (this function is in a Fragment):
fun refreshList() {
lifecycleScope.launch {
photosViewModel.listPhotos().collect {
// `it` is PagingData<Photo>
pagingAdapter.submitData(it)
Log.e(TAG, "After submitData")
}
}
}
The log After submitData is not printed.
However, if I put the logging in front of the pagingAdapter.submitData() line, it is printed, like this:
fun refreshList() {
lifecycleScope.launch {
photosViewModel.listPhotos().collect {
// `it` is PagingData<Photo>
Log.e(TAG, "Before submitData")
pagingAdapter.submitData(it)
}
}
}
The log Before submitData is printed with no problem.
Why does this happen, please?
.submitData is a suspending function which does not return until invalidation or refresh. As long as Paging is actively loading (collecting) from the PagingData you provided, it will not finish. This is why it must be done in a launched job.
For the same reason, make sure to use collectLatest instead of collect to make sure you cancel and start displaying new generations as soon as possible.
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))
})
I got the following code using Mosby.
Fragment:
#Override
public Observable<CardInfo> loadCardIntent() {
return Observable.just(new CardInfo(cardselected, PreferenceManager.getDefaultSharedPreferences(getContext())
.getBoolean(PreferencesVariables.SHOW_BACK_CARD.toString(), false)))
//.delay(500, TimeUnit.MILLISECONDS)
.doOnNext(showBack -> Log.d(TAG, "Show card back: " + showBack));
}
#Override
public Observable<CardInfo> loadFrontIntent() {
return RxView.clicks(cardBackImageView)
.map(showFront -> new CardInfo(cardselected, false))
.doOnNext(showFront -> Log.d(TAG, "Show card front"));
}
#Override
public Observable<Boolean> hideCardIntent() {
return clicks(cardFrontImageView)
.map(ignored -> true)
.doOnNext(close -> Log.d(TAG, "Close card activity"));
}
Presenter:
#Override
protected void bindIntents() {
Observable<CardViewState> showSelectedCard = intent(CardView::loadCardIntent)
.switchMap(cardInfo -> interactor.getCard(cardInfo))
.doOnError(error -> System.out.print(error.getMessage()));
Observable<CardViewState> showFront = intent(CardView::loadFrontIntent)
.switchMap(cardInfo -> interactor.getCard(cardInfo))
.doOnError(error -> System.out.print(error.getMessage()));
Observable<CardViewState> hideCard = intent(CardView::hideCardIntent)
.switchMap(ignored -> interactor.hideCard());
Observable<CardViewState> intents = Observable.merge(showSelectedCard, showFront, hideCard)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
subscribeViewState(intents, CardView::render);
}
Fragment:
#Override
public void render(CardViewState viewState) {
if (viewState instanceof CardViewState.CardBackState) {
renderCard(R.raw.planningpoker_rueckseite, cardBackImageView);
renderCard(((CardViewState.CardBackState) viewState).card, cardFrontImageView);
showCardBack();
} else if (viewState instanceof CardViewState.CardFrontState) {
renderCard(R.raw.planningpoker_rueckseite, cardBackImageView);
renderCard(((CardViewState.CardFrontState) viewState).card, cardFrontImageView);
showCardFront();
} else if (viewState instanceof CardViewState.CardCloseState) {
getActivity().finish();
}
}
Interactor:
Observable<CardViewState> getCard(CardInfo cardInfo) {
return cardInfo.showBack ? Observable.just(new CardViewState.CardBackState(CARDS[cardInfo.card])) :
Observable.just(new CardViewState.CardFrontState(CARDS[cardInfo.card]));
}
Observable<CardViewState> hideCard() {
return Observable.just(new CardViewState.CardCloseState());
}
Without the delay in loadCardIntent() the render()-method does not get triggered with the CardBackState. But I don't want to use a arbitrary delay to ensure the right methods get triggered.
Is there any other way to ensure that all events get emitted?
Thanks for the help.
Hm, is your code available on github somewhere? So far everything seems to be ok. Maybe it is an internal mosby bug. Is it working if you add subscribeOn(schdulers.io()) to loadCardIntent() in presenters bind() method.
The only difference I see with or without delay() is that your code runs sync (on main UI thread) whereas delay() switches the execution of your code to a background thread. Are you sure your interactor.getCardInfo() is meant to run on androids main UI thread? I.e. if it runs on main thread, but you are doing a http request (on main UI thread) an exception is thrown. Do you catch exceptions in interactor?
This was a mosby internal issue and has been fixed now.
See https://github.com/sockeqwe/mosby/issues/242
Please use latest snapshot:
com.hannesdorfmann.mosby3:mvi:3.0.4-SNAPSHOT
(see README) to verify everything works now as intended.
Please comment on the linked github issue if it fixes your problem or not.
Thanks.
My solution for now is to use Schedulers.trampoline(). It is not ideal and in no way sufficient but it allows me to get rid of the delay that is more of a hassle.
The problem that Schedulers.trampoline() seem to be solving is that the change onto another thread takes a short amount of time. And that causes the event to get lost. So staying on the same thread fixes this.
I have a chain of Rx Completables that I want to run one after another. I am using concat() to do this since I do not want them all to start at the same time.
view.welcome_message_edittext.verifyNotEmpty(getString(R.string.enter_your_email_address))
.concatWith(view.welcome_message_edittext.verifyEmailAddress())
.concatWith(sendMessageToBot())
.subscribe({
// The user has successfully entered data into the edittext, entered an email into the edittext, and sent message to bot.
}, { error -> })
The code above is saying this, "Assert the user has entered text into the EditText. If that is true, assert the user has entered an email into the EditText. If both of those are true, send a message to the bot." If the user enters text into the EditText but it is not an email, I expect the chain of Completables to break and onError() gets called.
This is what I want to happen ^^^. When any of the Completables calls onError() (as verifyNotEmpty() and verifyEmailAddress() do if user leaves EditText empty or does not enter email address) then I expect the entire chain to terminate and call the .subscribe() onError() function.
But, looking at the docs for .concat() this is the actual behavior of it:
concat() will simply move onto the next Completable when onError is called. The chain continues.
So my question is, what do I need to use in order to break the chain when any of the Completables call onError()?
Thanks to #Buckstabue in the comments for helping me debug this issue. His comment:
Let me guess. It's absolutely normal that the method verifyEmailAddress() is called and I suspect you are doing some business logic right there outside of an observable. You can put that logic inside the observable and it will be calculated lazily It's similar to difference between Observable.just(getMyInteger()) and Observable.fromCallable(() -> getMyInteger()). In the second case getMyInteger() will be lazily called after subscribing while the first one is called immediately
Went back to my code and viewed my verifyEmailAddress() and sendMessageToBot() functions:
private fun sendMessageToBot(): Completable {
insertChatMessageIntoConversation(ChatMessage(view!!. welcome_message_edittext.text.toString()))
return Completable.complete()
}
fun EditText.verifyEmailAddress(): Completable {
if (!android.util.Patterns.EMAIL_ADDRESS.matcher(text.trim()).matches()) {
return Completable.error(RuntimeException("Enter a valid email"))
} else {
return Completable.complete()
}
}
The logic of the functions were not inside of a Completable block. I did not think that this mattered when I wrote the code because I thought that Rx's behavior was that it executed each Completable and waited for them to complete or error completely before moving onto the next Completeable. Therefore, skipping the sendMessageToBot() and verifyEmailAddress() functions entirely. Not the case.
This works:
fun EditText.verifyEmailAddress(): Completable {
return Completable.fromCallable({
if (!android.util.Patterns.EMAIL_ADDRESS.matcher(text.trim()).matches()) {
val errorMessage = context.getString(R.string.enter_email_address)
error = errorMessage
throw RuntimeException(errorMessage)
}
})
}
private fun sendMessageToBot(): Completable {
return Completable.fromCallable {
insertChatMessageIntoConversation(sage(view!!. welcome_message_edittext.text.toString()))
}
}