My android application crashes when i call signout upon the fitebaseauth instance,
below is the logout call...
logout.setOnClickListener {
logoutAlertDialog.dismiss()
binding.msgLl.isVisible = true
binding.msgTv.isVisible = true
binding.msgTv.text = "Logging out"
binding.progressBar.isVisible = true
Handler(Looper.getMainLooper()).postDelayed({
FirebaseAuth.getInstance().signOut()
requireContext().startActivity(Intent(requireContext(),MainActivity::class.java))
requireActivity().finish()
},3000)
this is the code and
2022-07-13 13:17:50.446 5496-5496/com.aman.ache D/FirebaseAuth: Notifying id token listeners about a sign-out event.
2022-07-13 13:17:50.447 5496-5496/com.aman.ache D/FirebaseAuth: Notifying auth state listeners about a sign-out event.
2022-07-13 13:17:50.452 5496-5496/com.aman.ache E/Parcel: Reading a NULL string not supported here.
2022-07-13 13:17:50.502 5496-6482/com.aman.ache E/libEGL: Invalid file path for libcolorx-loader.so
2022-07-13 13:28:16.702 13149-13529/com.aman.ache W/Firestore: (23.0.3) [Firestore]: Listen for Query(target=Query(Users/GBVFmnu3zPTooz9nvmX6nBaQ6d02 order by name);limitType=LIMIT_TO_FIRST) failed: Status{code=PERMISSION_DENIED, description=Missing or insufficient permissions., cause=null}
highlighted part is the error in red.
From the error message it seems like you have an active listener on Firestore for which your security rules require that a user is signed in. When the user signs out that condition is no longer met, so the security rules remove the listener at that point, and raise an error to the SDK which you then log.
You didn't share the code of how you listen for data in Firestore, but you generally have two options:
Remove the listener in your application code before you sign the user out.
Change your code to ignore the error that you get.
The net result is the same, as the listener also is removed by the system automatically. I typically prefer the first approach though, as it's cleaner to have the application code clean up after itself.
Related
I'm working on a Flutter application & using the latest version of in_app_purchase to manage In-App Purchase Subscriptions.
I can make a successful subscription purchase & I can get the data needed to store a server-side receipt.
A scenario I am testing is if I were to make a successful purchase but if my server-side data was somehow set to null.
I am checking if my server-side receipt exists first to set the user's app status & if that does not exist I check if there is anything returned on the device level. If so, reset the server data.
This works perfectly on iOS.
On Android, nothing is returned. But I do get the popup that I have an active subscription. So I can't restore or attempt to make another purchase & no purchase detail data is available.
What should I be using to check for known Android subscription purchase data?
Update:
I have recently found this logic, but it is returning empty everytime. Even when I make a subscription, then set myself up to where I need to restore. It tells me there is nothing to restore, but if I try to purchase it tells me I already have a subscription.
_checkAndroidPastPurchases() async {
final InAppPurchaseAndroidPlatformAddition androidAddition = _inAppPurchase
.getPlatformAddition<InAppPurchaseAndroidPlatformAddition>();
final QueryPurchaseDetailsResponse oldpurchases =
await androidAddition.queryPastPurchases();
for (final oldP in oldpurchases.pastPurchases) {
// pastPurchases is empty
}
}
Update on 8/27/22:
I am getting stuck on this line:
Unhandled Exception: type 'PurchaseDetails' is not a subtype of type 'GooglePlayPurchaseDetails' in type cast
Seems I need to get my purchase details as GooglePlayPurchaseDetails but I have no idea how to accomplish this. Any advice is appreciated.
I found the solution. You can also use it to check for valid user subscriptions.
import below the line for InAppPurchaseAndroidPlatformAddition
Note: You need in_app_purchase plugin
import 'package:in_app_purchase_android/in_app_purchase_android.dart';
Add below code
List<PurchaseDetails> purchase = [];
Full code
import 'package:in_app_purchase/in_app_purchase.dart';
import 'package:in_app_purchase_android/in_app_purchase_android.dart';
List<PurchaseDetails> purchase = [];
purchases.addAll(oldpurchases.pastPurchases);
_checkAndroidPastPurchases() async {
final InAppPurchaseAndroidPlatformAddition androidAddition = _inAppPurchase
.getPlatformAddition<InAppPurchaseAndroidPlatformAddition>();
final QueryPurchaseDetailsResponse oldpurchases =
await androidAddition.queryPastPurchases();
//change oldpurchases.pastPurchases to purchases
for (final oldP in purchases) {
print(oldP.purchaseID);
print(oldP.productID);
print(oldP.pendingCompletePurchase);
}
}
The App performs a simple sign up (using FirebaseAuth, FirebaseUI & Google Sign In). When authenticated successfully, I take firebaseUser.userId and use it to fetch user profile from Realtime Database (example location /users/{userId}/someUserDataIsHere).
In case Realtime Database returns null object for that userId, it means the user with that userId does not exist in realtime DB, and is signing in for the first time (using Sign up with Google Account), hence a profile should be created (or in other words, user is about to register). In other case, if Firebase db returns user object, the app moves forward to the home screen.
The user profile contains some mandatory data like userId, email, and a name. But also contains some optional data like age, country etc, that could be empty.
The problem is that from time to time, when a user starts the app and the whole authentication process starts, after successful authentication, RealtimeDatabase tries to fetch user profile (for userId provided by FirebaseAuth), but error java.lang.Exception: Client is offline occurs, returns an empty object so the app "thinks" the user is new and must be inserted in the Realtime Database, and does that (even if it said "Client is offline" like 300ms before)
How it is offline when it authenticated user a few milliseconds before, failed to fetch data for that user from the realtime database (because it is offline??), and managed to write a new profile to the realtime database few ms after?
It does not make a huge problem, because it inserts data to the same userId key (it performs update technically), but remember that I have some optional fields, and those will be reset when this case happens. It is strange from the user's perspective because the user entered some optional fields (for example age) and it disappeared after some time.
I must point out the most usual use case for this:
User starts the app, sings in successfully and it is authenticated, provided with all the data for operating the app on a Home screen
Exit/kills the app
Starts the app after 2 hours
Authenticates successfully but fails to fetch user profile for that userId (which is valid and exists in the Realtime Database) due to Client is Offline error
Performs new user insertion successfully
Some of the dependencies that I use in the app ->
implementation platform('com.google.firebase:firebase-bom:26.4.0')
implementation 'com.google.firebase:firebase-analytics-ktx'
implementation 'com.google.firebase:firebase-auth-ktx'
implementation 'com.google.firebase:firebase-messaging-ktx'
implementation 'com.google.firebase:firebase-database-ktx'
implementation 'com.firebaseui:firebase-ui-auth:6.2.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.1.1'
implementation 'com.google.android.gms:play-services-auth:19.0.0'
Also, using this on app start:
FirebaseDatabase.getInstance().setPersistenceEnabled(false)
And this is the error I get (UPDATED with logs of some other GET request):
2021-01-30 16:12:12.210 9157-9599/com.fourexample.oab D/PersistentConnection: pc_0 - Connection interrupted for: connection_idle
2021-01-30 16:12:12.221 9157-9599/com.fourexample.oab D/Connection: conn_0 - closing realtime connection
2021-01-30 16:12:12.221 9157-9599/com.fourexample.oab D/WebSocket: ws_0 - websocket is being closed
2021-01-30 16:12:12.224 9157-9599/com.fourexample.oab D/PersistentConnection: pc_0 - Got on disconnect due to OTHER
2021-01-30 16:12:12.372 9157-9599/com.fourexample.oab D/WebSocket: ws_0 - closed
2021-01-30 16:13:07.094 9157-9166/com.fourexample.oab I/zygote64: Debugger is no longer active
2021-01-30 16:13:08.682 9157-9599/com.fourexample.oab D/Persistence: Starting transaction.
2021-01-30 16:13:08.687 9157-9599/com.fourexample.oab D/Persistence: Saved new tracked query in 3ms
2021-01-30 16:13:08.705 9157-9599/com.fourexample.oab D/Persistence: Transaction completed. Elapsed: 22ms
2021-01-30 16:13:11.708 9157-9599/com.fourexample.oab D/PersistentConnection: pc_0 - get 1 timed out waiting for connection
2021-01-30 16:13:11.713 9157-9157/com.fourexample.oab I/RepoOperation: get for query /requests/rs falling back to cache after error: Client is offline
2021-01-30 16:13:11.715 9157-9157/com.fourexample.oab D/Persistence: Starting transaction.
2021-01-30 16:13:11.718 9157-9157/com.fourexample.oab D/Persistence: Saved new tracked query in 2ms
2021-01-30 16:13:11.726 9157-9157/com.fourexample.oab D/Persistence: Transaction completed. Elapsed: 9ms
2021-01-30 16:13:11.741 9157-9157/com.fourexample.oab E/RequestService: java.lang.Exception: Client is offline
at com.google.firebase.database.connection.PersistentConnectionImpl$2.run(PersistentConnectionImpl.java:432)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:457)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:301)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
at java.lang.Thread.run(Thread.java:764)
For me, it ended up being because my google-services.json was out of date after I had updated some things in Firebase. Particularly it was because my code ended up referencing the default (US-East) region, when I had updated in Firebase to a different one. Try downloading the latest google-services.json, then making sure you clean and rebuild your app!
Okay, there is a bug in Firebase SDK.
Reported/opened issue on GitHub, and they are about to resolve it. Check more on this link
The main problem was usage of suspending functions with get().await() in the following query:
val dataSnapshot = firebaseRoutes.getRequestsReference(countryCode)
.orderByChild("isActive").equalTo(true)
.limitToFirst(20)
.get()
.await()
This would randomly close connection with Realtime Database.
I came up with a workaround using extensions until they solve it on their end.
So if you want to use queries and suspending functions, check this extension:
suspend inline fun <reified T> Query.awaitSingleValueEventList(): Flow<FlowDataState<List<T>>> =
callbackFlow {
val valueEventListener = object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
try {
val entityList = mutableListOf<T>()
snapshot.children.forEach { dataSnapshot ->
dataSnapshot.getValue<T>()?.let {
entityList.add(it)
}
}
offer(FlowDataState.Success(entityList))
} catch (e: DatabaseException) {
offer(FlowDataState.Error(e))
}
}
override fun onCancelled(error: DatabaseError) {
offer(FlowDataState.Error(error.toException()))
}
}
addListenerForSingleValueEvent(valueEventListener)
awaitClose { removeEventListener(valueEventListener) }
}
Usage:
suspend fun getActiveRequests(countryCode: String): Flow<FlowDataState<List<RequestEntity>>> {
return firebaseRoutes.getRequestsReference(countryCode)
.orderByChild("isActive").equalTo(true)
.limitToFirst(20)
.awaitSingleValueEventList()
}
FlowDataState is nothing but a wrapper that could be Data or Error
sealed class FlowDataState<out R> {
data class Success<out T>(val data: T) : FlowDataState<T>()
data class Error(val throwable: Throwable) : FlowDataState<Nothing>()
}
Calling this:
service.getActiveRequests(countryCode).collect {
when (it) {
is FlowDataState.Success -> {
// map from entity list to domain model list
// and emit to ViewModel
}
is FlowDataState.Error -> {
// emit error to viewModel
}
}
}
I was facing the same issue and replaced the get().addOnCompleteListener with addValueEventListener method. Below is completed code.
Code which throws client is offline issue:
databaseReference.child("pages").child("Help").get().addOnCompleteListener { task: Task<DataSnapshot> ->
if (!task.isSuccessful) {
Log.e("firebase", "Error getting data", task.exception)
} else {
Log.d("firebase",task.result.value.toString())
}
}
Code which solved the above issue:
databaseReference.child("pages").child("Help").addValueEventListener(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
Log.e("firebase", "onDataChange ${snapshot.value.toString()}" )
}
override fun onCancelled(error: DatabaseError) {
Log.e("firebase", "onCancelled ${error.message}" )
}
})
What worked for me is re-downloading the google-services.json file, then cleaning and re-building my Android Studio project
I had an issue with my rules. The message sometime comes up as "Permission denied" and sometimes as "Client is offline" when there is an issue with rules. I fixed my rules and things started working.
Message is misleading at times.
It wasn't working for me because I didn't specify server Url when getting database Instance.
Besides this error I was getting this in console :
Firebase Database connection was forcefully killed by the server. Will
not attempt reconnect. Reason: Database lives in a different region.
Please change your database URL to
To fix it I just specified server like that:
FirebaseDatabase.getInstance("YOUR_URL_HERE")
"YOUR_URL_HERE" - you can find this in your firebase console, here:
screenshot
The error is still occurring randomly (no logical explanation as to why) , it seems when I build the app (mobile flutter) and use the firebase emulator on the first launch the error would occur .
The Solution :
I had to manually uninstall the app and then build it again
I've been running into this same issue when using a coroutine with the IO dispatcher:
private fun performFirstReadOfEntities() {
viewModelScope.launch(ioDispatcher) {
mLoadingState.value = true
try {
Log.d(logTag, "Performing first read of $firebaseDBPath")
dbEntityRef.get()
.addOnSuccessListener { snapshot ->
val snapMap = snapshot!!.value as Map<*, *>
val tempEntityList = mutableListOf<FirebaseEntity>()
for (entry in snapMap) {
Log.d(logTag, "Adding entry with \n\tkey: ${entry.key}\n\tval: ${entry.value.toString()}")
tempEntityList.add(
entityFactory.createFirebaseEntity(
uid = entry.key as String,
map = entry.value as Map<String,Any>))
}
mEntities.value = tempEntityList.sortedBy { it.uid } // So that list and expected list match for emulation testing
Log.d(logTag, "Got ${tempEntityList.size} entities on initial read of database")
dbEntityRef.addChildEventListener(childEventListener) // Add child event listener after
dbListenersInitialized = true
mLoadingState.value = false
clearErrorState()
}
.addOnFailureListener {
setErrorState("Failed to get response from firebase", it)
}
I'm not sure if this is considered "proper" use, but it was working for me, until I got:
error: client is offline
The solution for me was uninstalling the app launcher from the emulated android device, cleaning/rebuilding, and then re-running the test.
Uninstalling the application was what finally did it. Even force closing it didn't do the trick. Maybe there's a firebase issue persisting in the cache and clearing it would have also done the trick? In any case, I'm hoping this is of use to anyone facing the same issue.
I'm building an app where I store app data in the app-specific-folder on Google Drive. I've been able to setup everything related to file storage and retrieval. The problem I'm facing is regarding permissions. The user has an option to disconnect the app from the Google Drive settings panel.
I use the DriveScopes.DRIVE_APPDATA meaning https://www.googleapis.com/auth/drive.appdata scope to save data.
I'm trying to figure out how to find out if this has happened on the app side. If I try to continue using the drive related apis, with the app being disconnected, then it crashes with a UserRecoverableAuthException.
com.google.api.client.googleapis.extensions.android.gms.auth.UserRecoverableAuthIOException
at com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential$RequestHandler.intercept(GoogleAccountCredential.java:297)
at com.google.api.client.http.HttpRequest.execute(HttpRequest.java:868)
at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.executeUnparsed(AbstractGoogleClientRequest.java:476)
at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.executeUnparsed(AbstractGoogleClientRequest.java:409)
at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.execute(AbstractGoogleClientRequest.java:526)
at abhiank.maplocs.ui.drivesync.DriveSyncService.onHandleIntent(DriveSyncService.kt:68)
at android.app.IntentService$ServiceHandler.handleMessage(IntentService.java:78)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:214)
at android.os.HandlerThread.run(HandlerThread.java:67)
Caused by: com.google.android.gms.auth.UserRecoverableAuthException: NeedPermission
at com.google.android.gms.auth.zze.zzb(Unknown Source:13)
at com.google.android.gms.auth.zzd.zza(Unknown Source:77)
at com.google.android.gms.auth.zzd.zzb(Unknown Source:20)
at com.google.android.gms.auth.zzd.getToken(Unknown Source:7)
at com.google.android.gms.auth.zzd.getToken(Unknown Source:5)
at com.google.android.gms.auth.zzd.getToken(Unknown Source:2)
at com.google.android.gms.auth.GoogleAuthUtil.getToken(Unknown Source:55)
at com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential.getToken(GoogleAccountCredential.java:267)
at com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential$RequestHandler.intercept(GoogleAccountCredential.java:292)
I tried the following to figure out if the app does not have the permissions or scopes.
Look at data inside GoogleSignInAccount instance received from GoogleSignIn.getLastSignedInAccount(this). This had the following scopes available in account.grantedScopes() after app had been disconnected. drive.appdata is shown even though app is disconnected.
[https://www.googleapis.com/auth/drive.appdata, https://www.googleapis.com/auth/userinfo.profile, https://www.googleapis.com/auth/userinfo.email, openid, profile, email]
Last thing I tried was hasPermissions method available in GoogleSignIn. I checked if the APP_DATA scope was available with this call and it returned true. So no help there either.
GoogleSignIn.hasPermissions(account, Scope(DriveScopes.DRIVE_APPDATA))
I'm really stuck now. Any help will be really appreciated. Thanks.
I ended up using a try-catch around my drive related code and catching the UserRecoverableAuthIOException. According to the documentation, this is called when -
UserRecoverableAuthExceptions signal Google authentication errors that
can be recovered with user action, such as a user login.
This has worked decently well for me. Considering the fact that this question has not received any other answers in 2 years, there doesn't seem to be any method to fetch the information about whether the app is disconnected or not via an API or SDK call.
Here's the code I use
fun getGoogleDriveService(context: Context): Drive {
val credential = GoogleAccountCredential.usingOAuth2(context, setOf(DriveScopes.DRIVE_APPDATA))
credential.selectedAccount = GoogleSignIn.getLastSignedInAccount(context)!!.account
return Drive.Builder(NetHttpTransport(), GsonFactory(), credential)
.setApplicationName(DriveSyncService.APP_NAME)
.build()
}
try {
val driveService = getGoogleDriveService(this)
var fileList = driveService.files().list().execute()
//...
//more code
} catch (e: UserRecoverableAuthIOException) {
/*
Doing a sign-out on the googleSignInClient so that there is no mismatch
in sign-in state and so that when I start sign-in process again, it
starts afresh
*/
googleSignInClient.signOut()
/*
Then I show a pop up telling user that app was disconnected and
to sign in again. And then on click I start the sign-in flow again.
*/
} catch (e: GoogleJsonResponseException) {
//https://googleapis.dev/java/google-api-client/latest/index.html?com/google/api/client/googleapis/json/GoogleJsonResponseException.html
//404 is file being updated/deleted was not found
if (e.message != null && e.message!!.contains("storageQuotaExceeded")) {
//todo handle storage exceeded error. Inform user
}
} catch (e: IOException) {
//todo handle network error
}
I have an issue with react-native-firebase (or firebase) in which my app does not receive a trigger after the auth token refreshes. It's pretty much the same issue as [1], but they never posted a solution.
So, what happens is that both on an Android phone and on the Android emulator (no idea about iOS), signing up, logging in and logging out works perfectly, meaning the listeners correctly see when I do a logout() etc. But the listeners never fire when the token refreshes.
My first question is: Am I correct to assume that the onIdTokenChanged-listener should automatically fire after 60 minutes without having to do anything else, e.g. call any firebase function, such that the app just sits there doing nothing for 60 minutes and then receiving the event and replacing the token?
My main component which contains the listeners looks like this:
class ReduxAppWrapper extends Component {
componentDidMount() {
firebase.auth().onAuthStateChanged((user) => {
console.log('COMP DID MOUNT: AUTH STATE CHANGED! ' + JSON.stringify(user));
});
firebase.auth().onIdTokenChanged((user) => {
console.log('COMP DID MOUNT: TOKEN CHANGED! ' + JSON.stringify(user));
});
firebase.auth().onUserChanged((user) => {
console.log('COMP DID MOUNT: USER CHANGED! ' + JSON.stringify(user));
});
};
render() {
return (
<ReduxProvider store={store}>
<MenuProvider>
<PaperProvider>
<AppContainer />
</PaperProvider>
</MenuProvider>
</ReduxProvider>);
}
}
Normally inside the listener I have a function that dispatches a redux-action such that the authentication information is broadcast across my components. Inside those components I use the jwt token for http-requests to my backend.
Now the backend of course uses firebase to validate that token (and this is where the problem occurs after the 60 minutes since it retrieves an outdated jwt), but I think I am right to assume that the problem lies within the app since the refresh does not happen.
I'd be really glad if someone could point me to where to look, I also tried to find out in the firebase console whether a token refresh event was sent, but I could not find anything about that.
So basically:
1) Am I right to assume that the firebase.auth().onIdTokenChanged() function should be called without me doing anything else? Or is it not enough to define the listener once in the main component (also regarding the fact that other screens will be rendered on top of that due to the stack-nvigation).
2) If the code is fine, do you have any hints for where to look?
Thanks so much!
[1] https://github.com/invertase/react-native-firebase/issues/531
For anyone with the same issue, I ended up asking firebase asking for the token everytime I needed it. I still think this should not be necessary but I did not want to spend any more time analyzing why the refresh did not work automatically. So what I am doing in the app is
firebase.auth().currentUser.getIdToken().then((token) => {
fetch(url, {
method: 'GET',
headers: { Authorization: token }
})
}
¯\_(ツ)_/¯
Apparently, with getIdToken, Firebase only makes a call to its server to get a new token if the current token has expired; it does not create unnecessary requests if it does not have to.
Quite a crucial detail which can be confusing if you are not aware of it and makes you (rightfully) assume that onIdTokenChanged is a listener which you would need to use to automatically update the token ...
https://firebase.google.com/docs/reference/js/firebase.User.html#getidtoken
Returns the current token if it has not expired. Otherwise, this will refresh the token and return a new one.
My firebase facebook popup login on ionic was working correctly but suddenly it stop working (well, i was modifiying the app but i didnt touch the service). The process still works on browser (when i use ionic serve) but not running on ionic app.
var auth = $fAuth(ref);
// login with Facebook
auth.$onAuth(function(authData){ // authData -> null
console.log("Auth..", authData); // Auth.. null
/* refresh user data on firebase and registering push */
});
auth.$authWithOAuthPopup("facebook").catch(function(error) {
console.log("Authentication failed:", error);
});
When it was working, $onAuth cb function was recieving an object in authData variable with properties like uid and facebook.
when i run:
ref.authWithOAuthPopup("facebook",function(){console.log(arguments)})
or:
ref.authWithOAuthPopup("twitter",function(){console.log(arguments)})
the callback function never fires.
I run the auth process on $ionicPlatform.ready event.
I already uninstalled and reinstalled the app, and cleaned app data.
thanks!
Are you sure it doesn't fire?
A callback function that will be called when authentication has completed. On failure, the first argument will be an Error object indicating the failure, with a machine-readable code attribute. On success, the first argument will be null and the second will be an object containing the fields uid (the unique user id), provider (string identifying the provider), auth (the auth. token payload), and expires (expiration time in seconds since the Unix epoch) - and more, depending upon the provider used to authenticate.
You didn't put the arguments there.
I think it should work if you try it again.
Example code from firebase:
ref.authWithOAuthPopup("facebook", function(error, authData) {
// ^^^^^ ^^^^^^^^
if (error) {
console.log("Login Failed!", error);
} else {
console.log("Authenticated successfully with payload:", authData);
}
});