Log user out after app has been uninstalled - Firebase - ios

My app won't log out the current user after the app has been uninstalled from the phone. I don't want the user to uninstall the app, and when they reinstall it they are already logged in.
I think it has something to do with its keychain access maybe? Not sure. I was thinking maybe I needed to just un-authenticate the user once the app was deleted, but there is no way of checking that condition. The closest thing to that would be running the applicationWillTerminate function, but if I put my FIRAuth.auth()?.signOut() in there, it would sign my user out every time the app was killed. I don't want that.
How would I go about making this work?

While there is no function or handler for checking when the app has been uninstalled from the phone, we can check if it is the apps first launch. More than likely when an app is first launched, that also means it has just been installed and nothing has been configured within the app. This process will be executed in didfinishLaunchingWithOptions above the return true line.
First, we have to set up the User Defaults:
let userDefaults = UserDefaults.standard
After this, we need to check if the app has launched before or has been run before:
if (!userDefaults.bool(forKey: "hasRunBefore")) {
print("The app is launching for the first time. Setting UserDefaults...")
// Update the flag indicator
userDefaults.set(true, forKey: "hasRunBefore")
userDefaults.synchronize() // This forces the app to update userDefaults
// Run code here for the first launch
} else {
print("The app has been launched before. Loading UserDefaults...")
// Run code here for every other launch but the first
}
We have now checked if it is the apps first launch or not. Now we can try to log out our user. Here is how the updated conditional should look:
if (!userDefaults.bool(forKey: "hasRunBefore")) {
print("The app is launching for the first time. Setting UserDefaults...")
do {
try FIRAuth.auth()?.signOut()
} catch {
}
// Update the flag indicator
userDefaults.set(true, forKey: "hasRunBefore")
userDefaults.synchronize() // This forces the app to update userDefaults
// Run code here for the first launch
} else {
print("The app has been launched before. Loading UserDefaults...")
// Run code here for every other launch but the first
}
We have now checked if the user is launching the app for the first time, and if so, log out a user if one was previously signed in. All the code put together should look like the following:
let userDefaults = UserDefaults.standard
if (!userDefaults.bool(forKey: "hasRunBefore")) {
print("The app is launching for the first time. Setting UserDefaults...")
do {
try FIRAuth.auth()?.signOut()
} catch {
}
// Update the flag indicator
userDefaults.set(true, forKey: "hasRunBefore")
userDefaults.synchronize() // This forces the app to update userDefaults
// Run code here for the first launch
} else {
print("The app has been launched before. Loading UserDefaults...")
// Run code here for every other launch but the first
}

Related

SwiftUI #AppStorage not always persisting value

I'm using #AppStorage with a String property. When changing the value of the property, the view automatically updates to reflect the change as expected. However, the majority of the time it hasn't persisted in UserDefaults. I'm feeling daft with the idea i've missed something here. Is anyone else seeing this?
Steps:
Launch app
Change value
Kill and re-launch app
Change value to something else
Kill and re-launch app
Environment:
Xcode 13.4 (13F17a)
iOS 15.5 - iPhone 13 Pro Simulator
Very simple example:
struct ContentView: View {
#AppStorage("token") var token: String = ""
var body: some View {
Form {
Section("Token") {
Text(token)
}
Section("Debug") {
Button("Update 01") {
token = "token-01"
}
Button("Update 02") {
token = "token-02"
}
}
}
}
}
This typical scenario can happen in development phase but not in production phase because there user doesn't have stop button like Xcode and UserDefault class write changes to disk before the app terminated by system.
So we know that user's default database is written to disk asynchronously, so may be the changes we made to default doesn't written to database when we stop the app from stop button on Xcode, you can try it own on your own by stopping app by swiping, your data will be stored when launch next time but when you stop from Xcode your data will not be saved
The answers above are correct, but I don't see the solution anywhere so I'll add it here:
Just always background your app before you terminate it from Xcode
PS: the answer is not, nor has never been, to call .synchronize on user defaults

GIDSignIn.sharedInstance.currentUser is always nil when app starts

I'm comming accross an issue similar to this one. Basically every time my app starts, I have to login with my Google Account.
Then, I have this property:
var isGoogleSessionOpen: Bool {
return GIDSignIn.sharedInstance().currentUser != nil
}
which is called as soon as the app starts to check if I have to show the LoginViewController or not.
My problem is that this call is always nil in first place, so I have to login every time my app starts.
also, as it's mentioned here, I'm configuring the scope like this:
if let signIn = GIDSignIn.sharedInstance() {
signIn.scopes = ["https://www.googleapis.com/auth/plus.login","https://www.googleapis.com/auth/plus.me"]
}
Any idea pls?
Regards
You need to sign in when the app starts.
GIDSignIn.sharedInstance().signInSilently()

How can I open my second App in a specific VC if the application is already running?

So I have 2 targets , I managed to open the SecondTarget from the FristTarget with:
if UIApplication.shared.canOpenURL(aUrl!){
if #available(iOS 10.0, *) {
UIApplication.shared.open(aUrl!)
} else {
// Fallback on earlier versions
}
I'm also navigating to a specific VC when the first time the SecondTarget opens because i now that a specific VC is called the first time and I did some modifications in there.
But i'm getting stuck when , let's say the SecondTarget is already running and I'm doing the specific task to open it from the FirstTarget...now it only swap to it since it was already open..not executing anymore the flow I wrote. It's also frustrating because the breakpoints aren't triggered in the SecondApp...Any ideas? thanks

CloudKit App to handle different iCloud accounts

I have an app that keeps user data in a private database using CloudKit and iCloud. I have written code to handle when the user logs out of iCloud and back in as a different user via Settings -> iCloud on their device. I am currently in Beta Testing mode and when I try this as an internal tester, the app crashes when I change accounts. When I test this in development mode using either the Xcode simulator or a real device, my code works great. My question is: should CloudKit apps be able to handle when the iCloud account changes on the device? If so, is this case able to be tested with internal testing via TestFlight. My code to handle account changes is below...
func isCloudAccountAvailableASync() {
container.accountStatusWithCompletionHandler { (accountStatus, error) in
switch accountStatus {
case .Available:
print("INFO: iCloud Available!")
// begin sync process by finding the current iCloud user
self.fetchUserID()
return
case .NoAccount:
print("INFO: No iCloud account")
case .Restricted:
print("WARNING: iCloud restricted")
case .CouldNotDetermine:
print("WARNING: Unable to determine iCloud status")
}
// if you get here, no sync happened so make sure to exec the callback
self.syncEnded(false)
}
}
func fetchUserID(numTries: Int = 0) {
container.fetchUserRecordIDWithCompletionHandler( { recordID, error in
if let error = error {
// error handling code
// reach here then fetchUser was a failure
self.syncEnded(false)
}
else {
// fetchUserID success
if self.lastiCloudUser == nil {
print("INFO: our first iCloud user! \(recordID)")
self.saveUserID()
}
else if !recordID!.isEqual(self.lastiCloudUser) {
// User has changed!!!!!!!!!!!!!
self.saveUserID()
// delete all local data
// reset the saved server token
self.saveServerToken(nil)
// try again with reset fields
}
// else user never changed, then just create a zone
// continue the sync process
self.initZone()
}
})
}
---EDIT/UPDATE:---
Here is a screenshot of my crash log
---EDIT/UPDATE 2:---
I was able to generate another crash with a different crash log. This crash log still doesn't point to my code, but at least describes a function...
I kept making it crash and somehow got a crash log that included a line of code I could link to a line in my Xcode project. My issue was with NSOrderedSet and iOS 9. That issue can be found here. I do not know why I only got hex in my crash log before, if anyone knows how to deal with hex crash logs I would love to hear it.
Here are the answers to my original question for anyone out there:
Should CloudKit apps be able to handle when the iCloud account changes on the device?
Answer: Yes
If so, is this case able to be tested with internal testing via TestFlight?
Answer: Yes
It's hard to say were your app crashes exactly.
You have to be aware that after an account change you should reset the state of your app (clearing local user data and navigating away from the screen that has user specific data)
On startup of your app you should call a function like this:
func reactToiCloudloginChanges() {
NotificationCenter.default.addObserver(forName: NSNotification.Name.NSUbiquityIdentityDidChange, object: nil, queue: nil) { _ in
// The user’s iCloud login changed: should refresh all local user data here.
Async.main {
self.viewController?.removeFromParentViewController()
// Or some other code to go back to your start screen.
}
return
}
}

Dimiss Touch Id action in Xcode 7 UI Automation

I am writing UI test case in Xcode 7 with new automation framework but I am not getting the method name to verify whether a touch prompt came or not and then dismiss a touch id prompt which is displayed in my app.
I could not simulate finger touch but i was able to cancel the touch id prompt using addUIInterruptionMonitorWithDescription api available in the test framework
I used the below code to dismiss the dialog
addUIInterruptionMonitorWithDescription("Touch ID") { (alert) -> Bool in
alert.buttons["Cancel"].tap()
return true
}
app.tap()
You can dismiss a Touch ID prompt by invalidating authentication LAContext. Dismissing TouchID prompt is introduced in iOS9:-
func invalidateAuthenticationAlert(authContextObjext: LAContext){
print("Dismiss current prompt")
authContextObjext.invalidate()
}
Remember:-
The context is invalidated automatically when it is (auto)released. This method allows invalidating it manually while it is still in scope.
Invalidation terminates any existing policy evaluation and the respective call will fail with LAErrorAppCancel. After the context has been invalidated, it can not be used for policy evaluation and an attempt to do so will fail with LAErrorInvalidContext.
Invalidating a context that has been already invalidated has no effect.
In Xcode 9 you can access the Springboard to dismiss the TouchID prompt:
func testExample() {
let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
let app = XCUIApplication()
app.launch()
// this causes the TouchID prompt to be displayed
app.buttons["Press Me!"].tap()
if springboard.alerts.buttons["Cancel"].waitForExistence(timeout: 10) {
springboard.alerts.buttons["Cancel"].tap()
}
// continue test
}
I was able to implement a solution for our project using this Biometrics testing repo here:
https://github.com/KaneCheshire/BiometricAutomationDemo
After the event that kicks off the biometrics alert is completed, you will be able to handle that alert in a number of ways. Using the Obj-C files provided in this repo (and implementing a bridging header) you can provide an invalid or valid biometric for the alert.
Here is a cheat sheet for checking for the existence of an element and dismissing alerts: http://masilotti.com/ui-testing-cheat-sheet/
If you wish to tap "cancel" on the alert, you can do this a number of ways (as mentioned in the cheat sheet I posted) including using the Springboard API.
func cancelBiometricAlert() {
let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
let biometricCancelButton = springboard.alerts.buttons["Cancel"].firstMatch
if biometricCancelButton.exists {
biometricCancelButton.tap()
}
}

Resources