Updating app that includes pre-populated database - iphone

I have an application that is using a quite large database with approximately 3.000 records. Previously i have loaded the database when starting up the app for the first time but with the number of records i have now i am implementing a pre-populated database to save time.
My question is what is happening when updating the app, from appstore, to the device will the app know that there is an updated version of the database and load the new database or continue to use the database that is already active with the app?
I am using this code in my app to use the pre-populated Core Data DB:
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator != nil) {
return persistentStoreCoordinator;
}
NSString *storePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent: #"database.sqlite"];
/*
Set up the store.
For the sake of illustration, provide a pre-populated default store.
*/
NSFileManager *fileManager = [NSFileManager defaultManager];
// If the expected store doesn't exist, copy the default store.
if (![fileManager fileExistsAtPath:storePath]) {
NSString *defaultStorePath = [[NSBundle mainBundle] pathForResource:#"database" ofType:#"sqlite"];
if (defaultStorePath) {
[fileManager copyItemAtPath:defaultStorePath toPath:storePath error:NULL];
}
}
NSURL *storeUrl = [NSURL fileURLWithPath:storePath];
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];
NSError *error;
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:options error:&error]) {
// Update to handle the error appropriately.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1); // Fail
}
return persistentStoreCoordinator;
}

Let's say that you app currently in the app store is version 1.0. This app downloaded a DB on its first launch.
Now, you are going to publish a new version 1.1, which will bundle the DB within the app itself.
When a user of the existing app (1.0) will upgrade to 1.1, what happens is fully under your control. Indeed, version 1.1 could check, on its first launch, for the existence of the DB in the user directory where version 1.0 installed it. In case, it is there, then version 1.1 knows it should upgrade the DB by copying it over from the resource directory.
Actually, the DB must be copied over to the user directory in any case, so by checking the you could make sure that no user data will be wiped out.
In general, you might store a version number in NSUserDefaults, so that each future version of your app has a way to know whether it is an upgrade os a new install (if the version number is there, then it is an upgrade from that specific version, otherwise it is a new install).

When you update the app, the files already created will still be there, so older versions of your app will still use the old database that you created in your old version

Related

new file is created instead of copying file

EDIT: Doesn't look like it's working with the simulator either now.
Some more info, It seems if I install an archived version that did work, then install the one that wont work, right over it, everything works great. but when I delete the archived version, and install the new one, thats when everything stops working.
I was just testing my app on my iPhone, and it was working perfectly, and when I archived it, and installed the ipa on my phone, the database stopped working. no errors occur when you load the database, and like I said before, it was JUST working. I didn't change ANY code. it still works on the simulator, so I know it has to do with the copying of the database. here's the relevant code:
dbPath=[NSString stringWithFormat:#"%#/Documents/database.sql", NSHomeDirectory()];
// Get the documents directory
NSFileManager *fmngr = [[NSFileManager alloc] init];
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"database.sql" ofType:nil];
NSError *error;
if(![[NSUserDefaults standardUserDefaults] boolForKey:#"didLaunchFirstTime"])
{
[[NSUserDefaults standardUserDefaults] setBool:TRUE forKey:#"didLaunchFirstTime"];
[fmngr removeItemAtPath:[NSString stringWithFormat:#"%#/Documents/database.sql", NSHomeDirectory()] error:&error];
if(![fmngr copyItemAtPath:filePath toPath:[NSString stringWithFormat:#"%#/Documents/database.sql", NSHomeDirectory()] error:&error])
{
// handle the error
NSLog(#"Error creating the database: %#", [error description]);
}
}
My query looks like this, because I'm using FMDB to query the database. It's in a separate method called when the user presses the search button.
FMResultSet *s = [db executeQueryWithFormat:#"SELECT Gurmukhi, ShabadID, FirstLetterStr FROM Shabad WHERE FirstLetterStr LIKE %#", searchString];
I also unzipped the ipa to check if the database wasn't blank, and it isn't. I have no idea what's going on.
You should not be hardcoding directory paths in your app - Apple provides functions to get them:
When you want to find the file in your bundle, you get it this way:
NSString *dbPathOld = [[NSBundle mainBundle] pathForResource:#"database" ofType:#"sql"];
Now you have a path to the sql file you provided with your app.
When you want to copy it, you use this code:
NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *dbPathNew = [docDir stringByAppendingPathComponent:#"database.sql"];
Other comments:
1) Why not use the NSFileManager *fm = [NSFileManager defaultManager];?
2) With your standard defaults, you are registering them in an initialize method in your app delegate? You are synchronizing them after you update values (so in fact "didLaunchFirstTime" is set the second time)? You might want to add a log message so you can know for sure.

Preparation for app release with coredata

i am coming to an end with creating version 1.0 of my new project. for the first time i am using coredata.
the application only uses 1 model, all data will be supplied by the user (so i do not load any data with the application).
of course i already working on updates for the application on different branches and see some changes in the datamodel in the future. the changes on the model will only consist of:
addition of entities
addition of attributes to existing entities
the entities do not have any relation with each other.
i have read through: iPhone app with CoreData
from there i went on to: Lightweight Migration, where i read about coredatas ability to update its model automatically if changes are minor (if i read correctly my changes are included there).
in the apple migration doc i found the code for automatic migration:
NSError *error = nil;
NSURL *storeURL = <#The URL of a persistent store#>;
NSPersistentStoreCoordinator *psc = <#The coordinator#>;
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
BOOL success = [psc addPersistentStoreWithType:<#Store type#>
configuration:<#Configuration or nil#> URL:storeURL
options:options error:&error];
if (!success) {
// Handle the error.
}
my questions are the following:
where would i put this code? i found now additional information on it
do i assume that this code will only be necessary in the updated version of the app?
do i need any other preparations on my version 1.0 app to allow later motifications & updates to coredata, or do i not have to think about this in the first release?
I have this code in the method
-(NSPersistentStoreCoordinator *)persistentStoreCoordinator
There should already be code like
if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error])
if you have let Xcode create the core data methods.
This code is only necessary in the update which introduces a new model.
As far as I know, no. This is all.

coredata not working on xcode 4.3?

just wondering if someone else came across this.
I got this piece of code that used to work brilliant in previous xcode versions.
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator != nil) {
return persistentStoreCoordinator;
}
NSString *storePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent:#"mydb.sqlite"];
/*
Set up the store.
For the sake of illustration, provide a pre-populated default store.
*/
NSFileManager *fileManager = [NSFileManager defaultManager];
// If the expected store doesn't exist, copy the default store.
if (![fileManager fileExistsAtPath:storePath]) {
NSString *defaultStorePath = [[NSBundle mainBundle] pathForResource:#"mydb" ofType:#"sqlite"];
if (defaultStorePath) {
[fileManager copyItemAtPath:defaultStorePath toPath:storePath error:NULL];
}
}
NSURL *storeUrl = [NSURL fileURLWithPath:storePath];
[self addSkipBackupAttributeToItemAtURL:storeUrl];
NSError *error;
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:nil error:&error]) {
/*
Replace this implementation with code to handle the error appropriately.
abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.
Typical reasons for an error here include:
* The persistent store is not accessible
* The schema for the persistent store is incompatible with current managed object model
Check the error message to determine what the actual problem was.
*/
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return persistentStoreCoordinator;
}
I would expect the following from this:
an empty "mydb" to be created from scratch if there is no "mydb.sqlite" in my bundle.
if a "mydb.sqlite" exists in my main bundle then i would expect it to be copied in the specified diretory.
if the "mydb.sqlite" is not compatible with my xcdatamodel the app must crash.
however this works only with already created db's previously.
If for example i try to put a random db named "mydb.sqlite" to my bundle and delete the original one then,
the app doesnt crash!!! a blank db is created and the new db is ignored.
This is completely wrong as it goes against my code.
In addition if I add back the original db nothing happens and the app just creates a blank db.
(and yes I do clean my project, delete the sim app, and even delete the build folder before any change occurs!)
any ideas??
There is a difference between your code and your expectation as you've stated it.
Here is your expectation:
an empty "mydb" to be created from scratch if there is no "mydb.sqlite" in my bundle.
if a "mydb.sqlite" exists in my main bundle then i would expect it to be copied in the specified directory.
if the "mydb.sqlite" is not compatible with my xcdatamodel the app must crash.
Here is what your code does:
Look for mydb.sqlite in the Documents directory
If mydb.sqlite does not exist, copy it from the main bundle (if it exists in the main bundle)
Create a new persistent store at ~/Documents/mydb.sqlite
The main problem I think you're experiencing is because of Step 3. addPersistentStoreWithType:... creates a new store at the URL provided. You instead need to requery the existence of the file (to check that there is now a file existing which may have been copied from the MainBundle) and if it exists, then use [persistentStoreCoordinator persistentStoreForURL:storeURL] instead of creating a new persistent store.
Once you have fixed this, the other problems should be more easily traceable. I suggest adding many more NSLog's in to your code, so you can see whether the code is following the execution path you expect. E.g. log each new object you create so you can easily see if any of them are nil. When you copy your bundle db, add an NSError pointer rather than NULL, and then if the error is non-nil, log it.
the answer to the question/ observation is simple.
downgrade to 4.2.1
I have restored my xcode to 4.2.1 (thank God for Time Machine) and now ALL is as expected.
I will file a bug report to Apple later on today.
Check that your default db is actually being included in the bundle - i.e. it is checked as included in the project, and is in the copy files build phase. I rearranged a project and an SQLite file, whilst bring copied into the project structure, was not included in my project - this would cause the behaviour you are seeing.

Core Data: Pre-populate SQLite Entity with Image

I am already pre-populating data for my application by first creating the database through core data, then populating that initialized file with SQLite Manager. Is it possible to pre-populate images in a SQLite table for use in core data as well?
My initial thought is to insert the images as a blob through SQLite Manager. Then based on this post, it looks like I would need to set the type to binary and import with UIImage initWithData:.
Is this doable, and if so, is this the appropriate method?
Pre-populating images to a SQLite database for use with Core Data turns out to be fairly trivial.
First configure your Core Data application, implementing your attribute to contain images as a "Binary" type. Build the application and navigate to your view utilizing Core Data within the application simulator. This will initialize the SQLite database as required for use with Core Data (assuming you've implemeted the persistentStoreCoordinator as follows).
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator != nil) {
return persistentStoreCoordinator;
}
NSString *storePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent: #"YourDBName.sqlite"];
// Set up the store.
// For the sake of illustration, provide a pre-populated default store.
NSFileManager *fileManager = [NSFileManager defaultManager];
// If the expected store doesn’t exist, copy the default store.
if (![fileManager fileExistsAtPath:storePath]) {
NSString *defaultStorePath = [[NSBundle mainBundle] pathForResource:#"YourDBName" ofType:#"sqlite"];
if (defaultStorePath) {
[fileManager copyItemAtPath:defaultStorePath toPath:storePath error:NULL];
}
}
NSURL *storeUrl = [NSURL fileURLWithPath:storePath];
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
nil];
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];
NSError *error;
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:options error:&error]) {
// Update to handle the error appropriately.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1); // Fail
}
return persistentStoreCoordinator;
}
Navigate to the application data at "Users//Library/Application Support/iPhone Simulator/User/Applications/". If you sort the folder by "Date Modified," your application will have the latest date (assuming you haven't built any other applications in the mean time). Enter the application folder and the initialized < YourDBName.sqlite > will reside in the Documents folder. Copy the SQLite database to another location (like your desktop) and delete the original file (this is necessary to allow Core Data to reload the pre-populated SQLite database you are about to create).
Open < YourDBName.sqlite > with your favorite SQLite editor (the SQLite Manager plugin for Firefox is an adequate, and free, option). Add entries to your table, inserting any images as a "BLOB."
Within XCode add < YourDBName.sqlite > as an existing file. Core Data will copy this file to the application data folder the next time you launch your application if it does not already exist there (you deleted the original right?).
Access your pre-populated images within your code with [UIImage imageWithData:< DataObject >.< ImageAttributeName >
How big are your images going to be? If they are fairly large you may be better served by storing the image in the file system and keeping a reference to its location in core data.
If the images will always exist in your app then you can package them with your bundle. If not (e.g. a user can remove unwanted images) you may have to rely on pulling the images in on first use.

how to make an iPhone app tolerant of a CoreData schema change

I have an app that uses the CoreData APIs extensively. I'm developing an updated version that adds a few fields to an entity. My app crashes upon load (unless if I blow away the private storage and start again) because of the schema changes.
The problem is when customers upgrade to the new version, I wouldn't mind running an upgrade procedure on their data the first time the app loads, but I can't even get it to load because CoreData doesn't like that the schema changes.
Is there any way to sort of tell CoreData "Its ok.. don't worry about the schema change"? Because I have only added fields and haven't renamed or deleted anything.
You should probably get a copy of Marcus Zarra's Core Data book and read up on migration (Ch. 5). But, failing that, there are some basics that are good to know. First, you need both your old model (schema) and your new model in your updated app. Second, you need to make sure that the new model is tagged as being the "current model". Third, you need to make sure that you create your NSPersistentStoreCoordinator in such a way that it automatically maps from existing model (as loaded from disk) to new model.
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (persistentStoreCoordinator)
return persistentStoreCoordinator;
NSURL *storeUrl = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory] stringByAppendingPathComponent: #"MyDataStore.sqlite"]];
NSError *error = nil;
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];
// Use mapping model
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, nil];
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:storeUrl
options:options
error:&error])
{
[NSApp presentError:error];
}
return persistentStoreCoordinator;
}
Update
Your old model in your new app needs to be exactly the same as the model in your old app. If you are unsure that this is the case, then there are some steps that you can take to make sure. The way I do it is a bit involved - but I will outline it if/when you think that that would be helpful.