I have an app with version 1.0 on app store which uses sqlite database for reading the data.Now I want to update my version to 1.1 with update in database file.While using developer certificate when I install app on device it did not update the database as the database file already exist in documents folder so i have to manually delete the app and install it again.My question is, when any user update the app, will the database also get updated according the current version.Any suggestions are welcome.Thanks
I am sure there are many ways to do this (and many ways better then mine as well), but the way that I handle such problems is as follows:
First I define a constant in the first .h file of the app (the one that will load first) to indicate First Time load and set it to 0:
#define FirstTime 0
Now you have to know that I have the intention to save the value of this constant in the Documents folder for future references, therefore I use a Shared Data Instance. In the viewDidLoad I do the following test:
//if first time run of this version
if( [MyDataModel sharedInstance].count < (FirstTime + 1) )
{
//do what you need to do as the first time load for this version
[MyDataModel sharedInstance].count++
//save the count value to disk so on next run you are not first time
//this means count = 1
}
Now the trick is on your new app version (say 1.1). I change the FirstTime to 2:
#define FirstTime 2
Since the saved First Time value on disc is 1 this means you will be caught by the if statement above, therefore inside it you can do anything you want like delete the old tables and recreate them again with the new formation.
Again not that brilliant, but solves the case!
This approach relies on NSUserDefaults. The idea is to get the previous app version number(if exists) from NSUserDefaults and compare it with the current version.
The code performs db upgrade if the previous app version < than current version or if the previous version is nil. It means that this approach can be used even though the app was already published on the AppStore. It will upgrade database to the new version during the app update.
This is a plist file:
There is an array which is composed of the version number and a set of sql queries for the corresponding upgrade version.
Suppose that a previous version is 1.2 and the actual version is 1.4 the code perform the upgrade only from the version 1.2 to 1.4. If the previous version is 1.3 and the current 1.4 the code performs upgrade only from 1.3 to 1.4.
If the previous version is nil the code performs upgrade to 1.1 then to 1.2 then to 1.3 and finally to 1.4.
NSString * const VERSION_KEY = #"version";
-(void)upgradeDatabaseIfRequired{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *previousVersion=[defaults objectForKey:VERSION_KEY];
NSString *currentVersion=[self versionNumberString];
if (previousVersion==nil || [previousVersion compare: currentVersion options: NSNumericSearch] == NSOrderedAscending) {
// previous < current
//read upgrade sqls from file
NSString *plistPath = [[NSBundle mainBundle] pathForResource:#"UpgradeDatabase" ofType:#"plist"];
NSArray *plist = [NSArray arrayWithContentsOfFile:plistPath];
if (previousVersion==nil) {//perform all upgrades
for (NSDictionary *dictionary in plist) {
NSString *version=[dictionary objectForKey:#"version"];
NSLog(#"Upgrading to v. %#", version);
NSArray *sqlQueries=[dictionary objectForKey:#"sql"];
while (![DB executeMultipleSql:sqlQueries]) {
NSLog(#"Failed to upgrade database to v. %#, Retrying...", version);
};
}
}else{
for (NSDictionary *dictionary in plist) {
NSString *version=[dictionary objectForKey:#"version"];
if ([previousVersion compare: version options: NSNumericSearch] == NSOrderedAscending) {
//previous < version
NSLog(#"Upgrading to v. %#", version);
NSArray *sqlQueries=[dictionary objectForKey:#"sql"];
while (![DB executeMultipleSql:sqlQueries]) {
NSLog(#"Failed to upgrade database to v. %#, Retrying...", version);
};
}
}
}
[defaults setObject:currentVersion forKey:VERSION_KEY];
[defaults synchronize];
}
}
- (NSString *)versionNumberString {
NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary];
NSString *majorVersion = [infoDictionary objectForKey:#"CFBundleShortVersionString"];
return majorVersion;
}
You can use .plist as well:
- (void)isItTheFirstTimeAfterUpdate {
NSString *versionnum;
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask,
YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"yourplist.plist"];
NSFileManager *fileManager = [NSFileManager defaultManager];
if(![fileManager fileExistsAtPath:path]) {
NSString *bundle = [[NSBundle mainBundle] pathForResource:#"yourplist" ofType:#"plist"];
[fileManager copyItemAtPath:bundle toPath:path error:&error];
}
NSMutableDictionary *savedStock = [[NSMutableDictionary alloc] initWithContentsOfFile:path];
versionnum = #"";
//it can be installed by user (for ex. it is 1.3 but first installed), no plist value is set before
if(([savedStock objectForKey:#"versionnum"]) && (![[savedStock objectForKey:#"versionnum"] isEqualToString:#""])){
versionnum = [savedStock objectForKey:#"versionnum"];
}
//to get the version of installed/updated-current app
NSString *myversion = [NSString stringWithFormat:#"%#",[[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleVersion"]];
//if no version has been set-first install- or my version is the latest version no need to do sth.
if ([versionnum isEqualToString:myversion] || [versionnum isEqualToString:#""]) {
NSLog(#"Nothing has to be done");
}
else {
[self cleanDB];//i have clean tables and create my new db tables maybe logout the user etc.
[savedStock setObject:[NSString stringWithString:myversion] forKey:#"versionnum"];//setting the new version
[savedStock writeToFile:path atomically:YES];
}
}
And you can call the function in application launch or in your main view controller's view controller.. your choice.
hope it helps.
Related
I am tasked to migrate an IOS game made with Cocos2d-x into Unity. One of the issues I have is that I don't know where Cocos2d-x writes the user's saved data. The Unity version of the app needs to access that data so that the user doesn't lose their progress.
The Cocos2d-x application saves it's data using something like this: userDefault->setIntegerForKey("coins", 35);
Would anybody know what path/location that user's saved data is stored? Are there ways I can find that out? I've already tried to view it on xcode via Window > Devices and Simulators > Installed Apps but the app isn't listed.
Any help would be appreciated. Thanks.
In cocos2d-x v4:
// write string
[[NSUserDefaults standardUserDefaults] setObject:[NSString stringWithUTF8String:value.c_str()] forKey:[NSString stringWithUTF8String:pKey]];
// read string
NSString *str = [[NSUserDefaults standardUserDefaults] stringForKey:[NSString stringWithUTF8String:pKey]];
// read integer
NSNumber *value = [[NSUserDefaults standardUserDefaults] objectForKey:[NSString stringWithUTF8String:pKey]];
Before V 2.1.2 the info was stored in UserDefault.xml
#define XML_FILE_NAME "UserDefault.xml"
#ifdef KEEP_COMPATABILITY
if (! _isFilePathInitialized)
{
// xml file is stored in cache directory before 2.1.2
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
_filePath = [documentsDirectory UTF8String];
_filePath.append("/");
_filePath += XML_FILE_NAME;
_isFilePathInitialized = true;
}
#endif
In my app, i used the sqlite DB. I submitted the 1.0 version of the app to app store. But after that i made some changes into DB like Inserting some new rows.
Then submitted 1.1 version of the app. But i will not able to get updated DB because the old DB already exists in device.
How can i solve this?
If i delete the app & then install the new version of app, then i got the updated DB.
//My code for copy of DB is as follow:
- (void)createEditableCopyOfDatabaseIfNeeded
{
// First, test for existence.
BOOL success;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *writableDBPath = [documentsDirectory stringByAppendingPathComponent:#"xxx.sqlite"];
success = [fileManager fileExistsAtPath:writableDBPath];
if (success) return;
// The writable database does not exist, so copy the default to the appropriate location.
NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"xxx.sqlite"];
success = [fileManager copyItemAtPath:defaultDBPath toPath:writableDBPath error:&error];
if (!success) {
NSAssert1(0, #"Failed to create writable database file with message '%#'.", [error localizedDescription]);
}
}
I called this method in appDidFinishLaunching method.
How can i get updated DB without deleting the app when new version is available
You can use the NSUserDefaults for doing this.
In your new version add this code:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
if(![[NSUserDefaults standardUserDefaults] boolForKey:#"1.1"]])
{
[self removeDatabase];
[self createEditableCopyOfDatabaseIfNeeded];
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:#"1.1"];
}
//Other stuffs
}
Method for removing the old database file:
- (void)removeDatabase
{
BOOL success;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *removeDBPath = [documentsDirectory stringByAppendingPathComponent:#"xxx.sqlite"];
success = [fileManager fileExistsAtPath:writableDBPath];
if(success)
{
[fileManager removeItemAtPath:writableDBPath error:&error]
}
}
You will need to perform a database migration so that old users of your app do not lose their data.
This means checking which version of the database already exists, and executing the necessary SQL statements to update the schema and data.
The MTMigration framework (MIT license) can help you execute code once per version of your app, for example:
[MTMigration migrateToVersion:#"1.1" block:^{
// migrate the SQL database here
}];
But you may wish to detect the version of your database differently.
It would be a good idea to write some unit tests for this procedure. Screwing up user data is the last thing you want to do.
You have to write Data migration script:-
In case when you need to make changes to an exiting table which contains some data then you need to do some steps as follows:
1) Rename the existing table
Say TableName was 'TestTable', So Rename the table to 'TestTableOld'
2) Create a new table called 'TestTable' which has the new columns you want and other altercations made.
3) Copy data from 'TestTableOld' to 'TestTable' with the query like:
Insert into TestTable('col1',col2'...'coln') Values Select col1, col2, col3,...'coln' From TestTableOld;
And newly added column set value as blank, as for new entries will use as such.
4) Drop the table 'TestTableOld'
Follow the above four step procedure as this would work as an alter statement and also it would preserve all your data from your original table.
If you are using User_Version, then would helpfull to identify old DB with new DB
Hi everybody I have an issue in my app. I need to determine if my app has has been updated to some point. I found out with this method how I can get the current version
NSString *first = [[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleShortVersionString"];
NSString *second = [[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleVersion"];
If the app is updated from itunes, Who var is changed to determine my app has been updated because I prove by modification from xcode in the Targets>Summary>Version to prove .The first var changes but the second does not change.
What I have to select the first or the second, and how I can prove this?
I'm not sure if this is what you are looking for but this code can tell you if your app has been updated by storing version information in NSUserDefaults.
- (BOOL)wasUpdated {
// access NSUserDefaults
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
// get previous version number from defaults
NSString *previousVersion = [defaults stringForKey:#"previousVersion"];
// get current version number from info plist
NSString *currentVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleVersion"];
// check against previousVersion being nil
if (!previousVersion) {
// set current version to previous version
[defaults setObject:currentVersion forKey:#"previousVersion"];
// previousVersion didn't exist and can't be checked, return NO
return NO;
}
// return YES if the versions are not the same
if (NSOrderedSame != [previousVersion compare:currentVersion]) return YES;
// return NO if they are the same
return NO;
}
Hopefully that's what you needed. Tell me if I didn't understand what you meant.
I am writing an iPhone app – a client for some social network. The app support multiple accounts. Info about accounts are stored in a keyed archive.
A method used for saving:
- (void) saveAccounts {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *path = [paths objectAtIndex:0];
path = [path stringByAppendingPathComponent:#"accounts.bin"];
// NSMutableArray *accounts = ...;
[NSKeyedArchiver archiveRootObject:accounts toFile:path];
}
A method uses for reading:
- (NSMutableArray *) loadAccounts {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *path = [paths objectAtIndex:0];
path = [path stringByAppendingPathComponent:#"accounts.bin"];
NSMutableArray *restoredAccounts = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
return [restoredAccounts retain];
}
The method saveAccounts is used only if some account is added/modified/deleted. The method loadAccounts is used every time the app starts. There isn't any other code that access this file.
I and one of my testers get an issue. At some moment the starts to act like accounts.bin is missing. If loadAccounts returns nil, the app offers to add an account. After I enter an account (the app must call saveAccounts), the app works normally, but when I launch it again, it asks me to add account again. The only solutions is too reinstall the app to iPhone, after reinstall it works for some time with any troubles.
We (I and my tester who get an issue) use iPhone 3G with 3.1.2.
An another tester who didn't experience this issue on his iPhone 3GS with 3.1.2.
Any ideas why this file disappears?
update
I found bug in my code. There was a code that deletes whole Document directory. Because this part of a code is a remote server related, it was hard to trace this code. Bug appeared under very rare conditions only.
Now the bug is found, and the code is corrected. wkw's answer didn't solved my problem, but it forced me to dig deeper. Thank you!
How about -- as a debugging device --verifying the contents of your Documents directory in loadAccounts or at least whenever the unarchiver returns nil. There's some code below to get the names of files in a directory. Set a breakpoint and just dump the NSArray to view the items.
If you can see that the file exists in the Docs dir, then your problem is elsewhere. Perhaps it did not successfully archive. check the return value on the call to archive the data:
if( ! [NSKeyedArchiver archiveRootObject:accounts toFile:path] ){
NSLog(#"Oooops! account data failed to write to disk");
}
Get names of files in directory:
- (NSArray*) directoryContentsNames:(NSString*) directoryPath {
NSArray* result;
{
result = [[NSFileManager defaultManager]
directoryContentsAtPath: directoryPath];
}
if( result && [result count] > 0 ){
NSMutableArray *items = [[[NSMutableArray alloc] init] autorelease];
for( NSString *name in result ){
if( ! [name isEqualToString:#".DS_Store"] )
[items addObject: name];
}
result = items;
}
return result;
}
Perhaps NSUserDefaults might be easier to use?
Also, is there a reason to use Keyed(Un)Archiver instead of NSArray's writeToFile?
if( ! [accounts writeToFile:path atomically:YES] )
; // do something since write failed
and to read the file in:
NSMutableArray *accounts = [[NSArray arrayWithContentsOfFile:path] mutableCopy];
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
How can I display the application version revision in my application's settings bundle?
I have an iPhone application that displays the current version as a Settings constant (just like Skype does).
When I released the first version of the application, I use this code to set the app Settings:
- (void)registerDefaultsFromSettingsBundle {
NSString *settingsBundle = [[NSBundle mainBundle] pathForResource:#"Settings" ofType:#"bundle"];
if(!settingsBundle) {
NSLog(#"Could not find Settings.bundle");
return;
}
NSDictionary *settings = [NSDictionary dictionaryWithContentsOfFile:[settingsBundle stringByAppendingPathComponent:#"Root.plist"]];
NSArray *preferences = [settings objectForKey:#"PreferenceSpecifiers"];
NSMutableDictionary *defaultsToRegister = [[NSMutableDictionary alloc] initWithCapacity:[preferences count]];
for(NSDictionary *prefSpecification in preferences) {
NSString *key = [prefSpecification objectForKey:#"Key"];
if(key) {
[defaultsToRegister setObject:[prefSpecification objectForKey:#"DefaultValue"] forKey:key];
}
}
[[NSUserDefaults standardUserDefaults] registerDefaults:defaultsToRegister];
[defaultsToRegister release];
}
And this worked fine and dandy.
The problem I face now is that if you update the application, these defaults (Settings) are nor re-written, so the application version is not updated.
How can I force that an specific Settings is set on every install?
Thanks
Gonso
You can use the following 'Run Script' Build Phase:
CFBundleShortVersionString=`/usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" ${SRCROOT}/YourApp/YourApp-Info.plist`
CFBundleVersion=`/usr/libexec/PlistBuddy -c "Print :CFBundleVersion" ${SRCROOT}/YourApp/YourApp-Info.plist`
/usr/libexec/PlistBuddy -c "Set :PreferenceSpecifiers:3:DefaultValue '${CFBundleShortVersionString} (${CFBundleVersion})'" ${SRCROOT}/YourApp/Settings.bundle/Root.plist
All you need to do, is to replace YourApp with your app's name and set the appropriate keypath.
In my case, I have 4 items in the PreferenceSpecifiers array, and I need to set the value for the last item from the array and that's why I used ':PreferenceSpecifiers:3:DefaultValue'
I have this code in my application delegate's -applicationDidFinishLaunching:
#if DDEBUG // debugging/testing
NSString *versionString = [NSString stringWithFormat:#"v%#",[[NSBundle mainBundle] objectForInfoDictionaryKey:#"CFBundleVersion"]];
#else
NSString *versionString = [NSString stringWithFormat:#"Version %#",[[NSBundle mainBundle] objectForInfoDictionaryKey:#"CFBundleShortVersionString"]];
#endif // DDEBUG
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setValue:versionString forKey:#"version"];
printf("Version: = %s\n", [versionString cStringUsingEncoding:NSMacOSRomanStringEncoding]);
[defaults synchronize]; // force immediate saving of defaults.
But app has to be run before Settings 'version' updates to reflect the change.
Why wouldn't you set the version number from the mainBundle?
NSString *version = [[NSBundle mainBundle] objectForInfoDictionaryKey:#"CFBundleVersion"]];
This way you don't have to update the settings file for every version. If you want to compare existing versus new install version. You could write out the version number to a file on launch and compare the directory version with the launch version.