I am using Firebase 12.0.1. Somewhat occasionally, but not too occasionally, one in 500-1000 users, my app experiences NPE while executing FirebaseAuth.getInstance().getCurrentUser. I have attached a screenshot of the crash report at the bottom.
My setupPage1 shown in the stack trace, is executing this line:
val provdata = FirebaseAuth.getInstance().currentUser?.providerData
Is there some precaution I need to take before getting the user? The user is logged in to FA via the following before the above code runs
this.authMgr.signInWithCredential(credential).addOnCompleteListener(this) { task: Task<AuthResult> ->
if (task.isSuccessful) {
val user = task.result.user
user?.getIdToken(false)?.addOnCompleteListener { task: Task<GetTokenResult> ->
if (task.isSuccessful) {
...stuff is done in here that leads to getCurrentUser
It's hard to be certain without an MCVE, but my first guess is that this might be while Firebase is still determining if the user is signed in. You should always check if there is a current user:
val user = FirebaseAuth.getInstance().currentUser
if (user != null) {
val provdata = user!.providerData
...
}
According to the support response I received, this was a known issue and was fixed in v15.0.1. I have not upgraded yet to verify.
Related
Everything works fine, for instance, on Samsung (Android 11), Huawei (Android 10), with the exception of at least Google Pixel 2 (Android 11), Google Pixel 5 (Android 11). There is also no problem with Wi-Fi on these devices.
There is a registration screen. The user enters the data and clicks on the "sign up" button.
Everything is fine until the user performs the following actions:
Enable the mobile network -> Click on the "sign up" button -> For example, the message "email is already in use" -> Disable the mobile network -> Click on the "sign up" button -> The suspended coroutine never continues (FirebaseNetwork exception is expected)
However, it works:
Enable the mobile network -> Disable the mobile network -> Click on the "sign up" button -> For example, the message "email is already in use" (everything is fine because the suspended coroutine has woken up)
Bottom line: Firebase does not throw a FirebaseNetwork or any exception and, as a result, the user interface "freezes" (I disable the form when the request is being processed) when the user submits the form with the mobile network enabled and then submits the form with the mobile network turned off.
private suspend fun handleResult(task: Task<AuthResult>) =
suspendCoroutine<AuthResult> { continuation ->
task.addOnSuccessListener { continuation.resume(it) }
.addOnFailureListener { continuation.resumeWithException(it) }
}
I solved the problem with this answer.
The code now looks like:
private suspend fun handleResult(task: Task<AuthResult>) =
withTimeout(5000L) {
suspendCancellableCoroutine<AuthResult> { continuation ->
task.addOnSuccessListener { continuation.resume(it) }
.addOnFailureListener { continuation.resumeWithException(it); }
}
}
Do I need to use the suspendCancellableCoroutine with timeout instead of suspendCoroutine always with Firebase to avoid these bugs on another devices?
I don't know if this is the cause, but it might help. There is already a suspend function that handles Google Tasks without you having to implement suspendCancellableCoroutine yourself. Their implementation is quite a bit more thorough than yours (it's about 30 lines of code) and maybe handles some edge cases that yours doesn't. It also optimizes results when the task is already finished by the time you call it, and it handles cancellation correctly, which yours does not.
The function is Task.await(). If it's not available to you, then add this dependency in your build.gradle:
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.6.0"
The problem is solved by combining await() (thank you, #Tenfour04) + withTimeout(). It looks like Firebase doesn't have a timeout for network authentication calls.
build.gradle to get suspending await() (replace "x.x.x" with the latest version):
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-play-services:x.x.x'
For example:
private val firebaseAuth: FirebaseAuth
suspend fun create(userInitial: UserInitial): AuthResult = withTimeout(5000L) {
firebaseAuth.createUserWithEmailAndPassword(
userInitial.email,
userInitial.password
)
.await()
}
withTimeout() throws a TimeoutCancellationException if the timeout was exceeded
Similar question: Android workmanager scheduled worker lost after task killed
The answer here suggests that this is a problem with specific manufacturers, but I am using an Android One phone which has the unmodified OS.
For the most part my PeriodicWorkRequest works as expected, but then for a straight 5-8 hours window, it doesn't get executed at all. I log whenever the request is executed in a log file, and this is how it looks:
As visible in the image, the red box indicates the interval where WorkManager did not run the request. I can assure that the app was never force killed since install. It was cleared from recents some times, but that did not seem to have any immediate effects.
This is how I initialized the request:
PeriodicWorkRequest checkLastSeenRequest =
new PeriodicWorkRequest.Builder(LastSeenWorker.class,
PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS, TimeUnit.MILLISECONDS,
PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS, TimeUnit.MILLISECONDS)
.setBackoffCriteria(
BackoffPolicy.LINEAR,
PeriodicWorkRequest.MIN_BACKOFF_MILLIS,
TimeUnit.MILLISECONDS)
.build();
WorkManager.getInstance(this).enqueueUniquePeriodicWork(
"checkLastSeen",
ExistingPeriodicWorkPolicy.KEEP,
checkLastSeenRequest);
And this is the createWork() method of RxWorker:
#NonNull
#Override
public Single<Result> createWork() {
return Single.create(subscriber -> {
FirebaseFirestore firestore = FirebaseFirestore.getInstance();
firestore.collection("users").document("doc").get()
.addOnCompleteListener(task -> {
if (task.isSuccessful()) {
DocumentSnapshot doc = task.getResult();
if (doc != null && doc.exists() && doc.getData() != null
&& doc.getData().get("lastSeen") != null) {
--> // this is where I log
subscriber.onSuccess(Result.success());
} else {
--> XLog.e("No document found to read from");
subscriber.onError(new Error("Document not found"));
}
} else {
--> XLog.e(task.getException());
subscriber.onError(task.getException());
}
});
});
}
From the documentation here my understanding is that in doze mode as well there is a window where all the requests are allowed to execute even as these become less frequent. So why is there a complete blackout for more than 6 hours? Do I need to get battery optimization permissions for it to work as expected? Any help appreciated!
try to check working your code in the Doze mode as described it here
Maintenance windows in Doze won’t happen frequently. First window occurs an hour after last activity, the next after two, the next after four and so on. But it doesn’t matter that much since user isn’t even moving her device anyway.
Some useful information
Maybe you have another choice: Schedule tasks with WorkManager
I have a singleton in my Android app, started at startup of the app, that listens to auth state changes like so:
fun listenForAuthChanges() {
if (authStateListener != null) {
FirebaseAuth.getInstance().removeAuthStateListener(authStateListener!!)
}
authStateListener = FirebaseAuth.AuthStateListener { auth ->
val user = auth.currentUser
writeStatusToFirebase()
if (user != null) {
Log.debug("User : ${user.uid} -> ${user.loginType}")
} else {
Log.debug("User : signed out")
loginAnonymously()
}
}
FirebaseAuth.getInstance().addAuthStateListener(authStateListener!!)
}
This works perfectly, detecting if a use is logged out, or logged in. As you can see in the code above using the loginAnonymously(), when there is no user logged-in then I automatically login anonymously. This al works like a charm, however.... when I call the Firebase-UI to login and the user logs in via Facebook the Auth state listener is not called.
I figured out that FirebaseUI actually does not create a new user, instead the anonymous user is upgraded to Facebook user (checked in the Firebase Auth console and by using breakpoints in the Android studio console). This is actually the behaviour that I want.
So I guess, the conclusion is that the Auth state listener is not called because the user's uid does not change?
However, I do need a reliable way to detect also this event (meaning user upgraded from anonymous to e.g. Facebook).
What would be the best way to do this?
Note: I know its possible to detect what auth provider (id) a user is using to authenticate.
Here is a bit of code I use for this:
val FirebaseUser.loginType: LoginType
get() {
if (isAnonymous) {
return LoginType.Anonymous
}
loop# for (userInfo in providerData) {
when (userInfo.providerId) {
EmailAuthProvider.PROVIDER_ID -> return LoginType.Email
GoogleAuthProvider.PROVIDER_ID -> return LoginType.Google
FacebookAuthProvider.PROVIDER_ID -> return LoginType.Facebook
else -> continue#loop
}
}
return LoginType.Unknown
}
That is not my issue, my questions is: how to detect that a user has been upgraded from anonymous to e.g. Facebook?
how to detect that a user has been upgraded from anonymous to e.g. Facebook?
When an anonymous user signs in for the first time, save the authentication type in the database:
Firestore-root
|
--- users (collection)
|
--- uid
|
--- type: "anonymous"
When a user is changing the authentication type, you should simply change the type field in the database to hold "Facebook" instead of "anonymous". This is possible because the uid will always be the same, no matter what the provider id. To be notified when the operation takes place, simply attach a listener on the type property and you'll be notified in real-time.
I am trying to implement Sign in with Apple using Firebase Authentication. I am following the firebase/quickstart-android sample.
My sign-in fragment overrides onStart() to check for any pending results:
override fun onStart() {
super.onStart()
val pending = auth.pendingAuthResult
pending?.addOnSuccessListener { authResult ->
Timber.d("Successful login, pending")
}?.addOnFailureListener { e ->
Timber.d("Failed login, pending")
}
}
And a button that initiates the sign-in flow:
btnApple.onClick {
viewModel.appleLogin(requireActivity())
}
The viewModel calls the following method from a repository:
// Initiate sign-in flow only if there are no pending results
if (auth.pendingAuthResult != null) {
return
}
val scopes = listOf("email", "name")
val provider = OAuthProvider.newBuilder("apple.com", auth)
.setScopes(scopes)
.build()
auth.startActivityForSignInWithProvider(activity, provider)
.addOnSuccessListener { authResult ->
Timber.d("Successful login, normal")
}
.addOnFailureListener { e ->
Timber.e(e, "Failed login, normal")
}
The official manual states:
Signing in with this method puts your Activity in the background, which means that it can be reclaimed by the system during the sign in flow.
So I started testing the pending result by terminating the app in Android Studio while completing the sign-in flow in Chrome. Once I returned back to the app, the onStart() was called, but the pendingAuthResult was always null.
To make it more interesting, when I restart the app, I am logged in. Then if I log out and enter the sign-in fragment again, there is a pending result now and I receive Successful login, pending. On top of that, the pending result does not disappear. If I leave the sign-in fragment and go back, the pending result is still there and I receive yet another Successful login, pending.
I even tested the firebase/quickstart-android sample itself and it has exactly the same issue.
What could be the possible cause of this issue? I am using firebase-auth:19.2.0.
if you are using the firebase for signing with apple and if it's handled manually then you should use a string decoding mechanism, Please find the firebase guide for the same and hope it'll works. let me know if any issue!
Firebase guild signing with apple
I'm looking to catch the errors of the new methods to change email and password for Android, but I can't seem to find anything. Can somebody point me in the right direction?
The official documentation is not clear about this.
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
String newPassword = "SOME-SECURE-PASSWORD";
user.updatePassword(newPassword)
.addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
if (task.isSuccessful()) {
Log.d(TAG, "User password updated.");
}
}
});
https://firebase.google.com/docs/auth/android/manage-users#set_a_users_password
The callbacks use the new Task class in Google Play services. There are two ways of catching errors with this:
Use addOnCompleteListener, check for isSuccessful and inspect the return of getException if it failed.
Use addOnSuccessListener and addOnFailureListener. The latter callback will include an Exception with the error.
The actual exceptions returned are documented in the reference documentation for each method. For example, updatePassword could fail with:
FirebaseAuthWeakPasswordException thrown if the password is not strong enough
FirebaseAuthInvalidUserException thrown if the current user's account has been disabled, deleted, or its credentials are no longer valid
FirebaseAuthRecentLoginRequiredException thrown if the user's last sign-in time does not meet the security threshold. Use reauthenticate(AuthCredential) to resolve. This does not apply if the user is anonymous.