Determine if you own app is currently installed or not programmatically - ios

I am creating a new feature in one of my apps to show a "What's New" page. I have this all working as expected and it compares the last version (stored) to the current version, then updates the current version once the feature list is shown.
This all works great and as expected. The issue I have now is determining if a user already has the app installed.
Case 1) If the user is a completely new user and it is the first time they have downloaded the app, then the new feature list should not be shown.
Case 2) If they already had the app installed and are updating their app to a new version, then we need to show the new feature list.
The part I need help with is Case 2. How can you determine if the app is already installed by the user, without having to release a minor update just to set a currentInstalledVersion variable beforehand?

if(currentInstalledVersion)
{
if(currentInstalledVersion < actualCurrentVersion)
{
// user upgraded
}
}
else
{
if(someOtherDataSavedOnDiskExists)
{
// user had the app with a version before you implemented currentInstalledVersion and is updating
}
else
{
// fresh install
}
}
Update based on below comment
Okay in that case, there is no built-in mechanism to help you with this. Until you implement a way yourself (with this flag), you can't check. However, if you have any kind of other flags or data you've written to NSUserDefaults for instance, you can check that immediately on startup to see if those values are set which would tell you it is an existing user that just installed the app (only check the other flags if currentInstalledVersion is not set). If you have not written any data to disk or to NSUserDefaults then you are out of luck for your initial release of this feature.
I updated the code above to reflect this.

You could use NSUserDefault and set a bool to check if it's a new user.
ex:
if(![[NSUserDefaults standardUserDefaults] boolForKey:#"user"]){
// it's a new user
[[NSUserDefaults standardUserDefaults] setBool:YES ForKey:#"user"];
}
The boolForKey: method would return NO if the default was not set.

Related

How to force delete NSUserDefaults when app is updated?

If a new version is not backward compatible with previous version's data, what is the best way to ensure NSUserDefaults is deleted upon upgrade?
A simple way would be to keep a default that tracks whether the upgrade process has been performed. The new version would check for it, and if it hasn't been set, perform the upgrade procedure and set the value to make sure the process isn't performed twice. Like this:
static NSString * const kUserDefaultsDidUpgradeKey = #"didUpgrade";
NSUserDefaults *ud = NSUserDefaults.standardUserDefaults;
if (![ud boolForKey:kUserDefaultsDidUpgradeKey]) {
// Delete the keys here
[ud setBool:YES forKey:kUserDefaultsDidUpgradeKey];
}
However, assuming that you would want to handle other version migrations in the future, a more robust way would be to write the app version to user defaults so that when the app is updated, the new version can check the version key to see what the previous version was, and run the upgrade process accordingly.

Determine if previously installed app

I have an app on the App Store, and I want to make some changes that will not effect users that previously downloaded my app.
Is there a way to determine if the user has previously downloaded my app?
Incase anyone is still wondering, a great solution to this problem (assuming you don't already have it) is using the Keychain, which persists through app installation/uninstalls. This library allows you to access the Keychain using NSDictionary-like syntax.
https://github.com/nicklockwood/FXKeychain
So you could implement a function like this:
-(BOOL)alreadyInstalled
{
NSString *installDate = [[FXKeychain defaultKeychain] objectForKey:#"InstallDate"];
if (!installDate)
{
NSString *newInstallDate = [NSString stringWithFormat:#"%i", [[NSDate date] timeIntervalSince1970]];
[[FXKeychain defaultKeychain] setObject:newInstallDate forKey:#"InstallDate"]
return NO;
}
return YES;
}
I don't know a great way to do this but there are some tricks you can do, e.g.:
Look for some data that your application generates. If the data already exists then it's not an update (or an update that completed previously);
Prepare yourself for this, even if this means issuing an intermediate update to your application, then go back to #1. See: How to tell if an iOS application has been newly installed or updated?

How can I check whether an application has already been installed or is being installed for the first time

What will be the best way to check that application is already installed or being installed for the first time.
Bundle version and saving it in user defaults.
EDIT:
There are three things to note here.
Bundle version: This is the version of the your application that you want to release.
Old version: This will indicate previous version of your application. We will store this in user defaults so that we will know what was the old version when updating our application. This will obviously be nil if your bundle version is 1.0.
Target version: This indicates the version the user is targeting. We will discuss this later.
So, condition such as
bundleVersion > oldVersion or
if(isVersionBetter:myBundleVersion thanVersion:oldVersion)
would either mean we want to create our database (in this case bundle version would be 1.0 and old version will be nil) or update our database (in this case bundle version would be something greater than 1.0 and hence old version would not be nil).
Thus, as we can see, creation of database means user is installing app for the first time. Updating database means user has already installed the app and is updating the database.
But, there might also be a case when you want to update your app and want to keep the database as it is. That is, only UI updating.
Here, target version comes into picture.
As mentioned above, target version is the version the user is targeting. All would work same as above if user is targeting the bundle version. But if user is targeting some other version than bundle version, we would skip database updating part, thus allowing only the UI to change.
So, the final statement would be something like this:
if( bundleVersion == targetVersion AND bundleVersion > oldVersion ) {
// Either create or update the database.
}else {
// Do nothing. Skips database updating and allows UI update.
}
Thus, your database function would look something like this
-(void) initWithTargetVersion:(NSString *) targetVersion {
NSString *oldDatabaseVersion = [[NSUserDefaults standardUserDefaults] stringForKey:#"OldDatabaseVersion"];
NSString *bundleDatabaseVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleVersion"];
if([bundleDatabaseVersion isEqualToString:targetVersion] && [self isVersionBetter:oldDatabaseVersion new:targetVersion]) {
// Create or update the database.
}else {
// Do nothing.
}
}
where user would pass the target version as follows:
[[DatabaseManager sharedManager] initWithTargetVersion:#"1.0"];

Delete keychain items when an app is uninstalled

I am using idandersen's scifihifi-iphone code for keychain and save password using
[SFHFKeychainUtils storeUsername:#"User" andPassword:#"123"
forServiceName:#"TestService" updateExisting:YES error:&error];
When I delete the application from the device, the password remains in the keychain.
I want to remove the password from the keychain when the user deletes the application from the device. How can I do this?
You can take advantage of the fact that NSUserDefaults are cleared by uninstallation of an app. For example:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//Clear keychain on first run in case of reinstallation
if (![[NSUserDefaults standardUserDefaults] objectForKey:#"FirstRun"]) {
// Delete values from keychain here
[[NSUserDefaults standardUserDefaults] setValue:#"1strun" forKey:#"FirstRun"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
//...Other stuff that usually happens in didFinishLaunching
}
This checks for and sets a "FirstRun" key/value in NSUserDefaults on the first run of your app if it's not already set. There's a comment where you should put code to delete values from the keychain. Synchronize can be called to make sure the "FirstRun" key/value is immediately persisted in case the user kills the app manually before the system persists it.
For users looking for a Swift 3.0 version of #amro's answer:
let userDefaults = UserDefaults.standard
if !userDefaults.bool(forKey: "hasRunBefore") {
// Remove Keychain items here
// Update the flag indicator
userDefaults.set(true, forKey: "hasRunBefore")
}
*note that synchronize() function is deprecated
There is no trigger to perform code when the app is deleted from the device. Access to the keychain is dependant on the provisioning profile that is used to sign the application. Therefore no other applications would be able to access this information in the keychain.
It does not help with you aim to remove the password in the keychain when the user deletes application from the device but it should give you some comfort that the password is not accessible (only from a re-install of the original application).
For those looking for a Swift version of #amro's answer:
let userDefaults = NSUserDefaults.standardUserDefaults()
if userDefaults.boolForKey("hasRunBefore") == false {
// remove keychain items here
// update the flag indicator
userDefaults.setBool(true, forKey: "hasRunBefore")
userDefaults.synchronize() // forces the app to update the NSUserDefaults
return
}
C# Xamarin version
const string FIRST_RUN = "hasRunBefore";
var userDefaults = NSUserDefaults.StandardUserDefaults;
if (!userDefaults.BoolForKey(FIRST_RUN))
{
//TODO: remove keychain items
userDefaults.SetBool(true, FIRST_RUN);
userDefaults.Synchronize();
}
... and to clear records from the keychain (TODO comment above)
var securityRecords = new[] { SecKind.GenericPassword,
SecKind.Certificate,
SecKind.Identity,
SecKind.InternetPassword,
SecKind.Key
};
foreach (var recordKind in securityRecords)
{
SecRecord query = new SecRecord(recordKind);
SecKeyChain.Remove(query);
}
Files will be deleted from your app's document directory when the user uninstalls the app. Knowing this, all you have to do is check whether a file exists as the first thing that happens in application:didFinishLaunchingWithOptions:. Afterwards, unconditionally create the file (even if it's just a dummy file).
If the file did not exist at time of check, you know this is the first run since the latest install. If you need to know later in the app, save the boolean result to your app delegate member.
#amro's answer translated to Swift 4.0:
if UserDefaults.standard.object(forKey: "FirstInstall") == nil {
UserDefaults.standard.set(false, forKey: "FirstInstall")
UserDefaults.standard.synchronize()
}
This seems to be the default behavior on iOS 10.3 based on behavior people have been witnessing in beta #2. Haven't found any official documentation about this yet so please comment if you have.
Just add an app setting bundle and implement a toggle to reset the keychain on app restart or something based on the value selected through settings (available through userDefaults)

Storing accessToken in Facebook iOS SDK

I have the Facebook iOS SDK set up in my app. However, I have trouble determining when my session is finished. How to check if it's finished, and where (how) to store the access token received by the login?
I need to determine whether I have the access token or not right from the beginning so I know whether to log in again, or go forward in the app.
Are you familiar with NSUserDefaults ? It is for storing preferences for your app.
They are extremely easy to use, and probably what you are looking for. So it's just something like ..
NSUserDefaults *factoids;
NSString *whateverIDstring; // make this a property for convenience
factoids = [NSUserDefaults standardUserDefaults];
self.whateverIDstring = [factoids stringForKey:#"storeTheStringHere"];
if ( ( whateverIDstring == Nil ) || ( [whateverIDstring isEqualToString:#""] ) )
// it does not yet exist, so try to make a new one...
else
// we already made one last time the program ran
// when you make a new one, save it in the prefs file like this...
[factoids setObject:yourNewString forKey:#"storeTheStringHere"];
[factoids synchronize];
Hope it helps!
ONE get the preferences in to 'factoids' as in the example above
TWO decide on a name for your preference. 'storeTheStringHere' in the example.
THREE get your string 'whateverIDstring' in the example using stringForKey:
FOUR check if it is either nil or blank. if so, start fresh.
FIVE once you get the value, save it as shown!
Hope it helps!

Resources