Adding a PreLoaded SQLite Db to ScoutTrail App

Been some time since my last post, but it doesn’t mean I haven’t been working on my app. Progress on ScoutTrail has been slow, but steady. The latest? While teaching myself about Core Data, I decided it would be better if my input data (rank and merit badge requirements, etc) was Core Data based rather than xml. Justification is that I want to track completion of each requirement and coming up with a data model that would stay coordinated with the XML input data would prove more difficult IMHO than creating a data model to handle both.

To start with, I had 2 choices (based on what I found on Ray Wenderlich’s site):

  1. Fill in Core Data on startup from external source. For this the app can start up, notice that the database hasn’t been imported yet, and start reading in data from an external source (such as an SQLite database or XML file) and then start inserting the data into Core Data.

  2. Provide pre-filled in SQLite database. For this we’ll let Core Data create the database structure for us based on the model, and then we populate the database with a utility app. The utility app could be a Mac or iPhone app that uses Core Data to populate the database via Core Data APIs, or some kind of program that fills in the SQLite database directly. Once the database is populated, just include it with the app and make the app use it as the default database if no database already exists.

I chose the latter. Ray’s example uses a Python script to preload a SQLite db and he admits that using the Core Data API interface to do this would be safer in case something in the Core Data to SQLite API changes in the future. But as he says

Note that by using a Python script to import the data rather than working with a utility app that uses Core Data APIs, it’s more likely to break in the future because we’re kind of going under the hood here… but for this tutorial I thought a) it’s a better learning experience since we just covered SQLite and it shows how things are working more clearly, and b) it is simpler!

My solution was to duplicate my current ScoutTrail project and rename it to ScoutTrailData.

  • I removed the UI code from ScoutTrailData (since I am only interested in parsing the XML and loading it into the db),

  • Create the data model,

    1. Add a New Group, Model, to the project
    2. Right click Model and Add New File. Select iPhone OS->Resource->Data Model (see Core Data Tutorial for iOS for more info on data model creation specifics),
  • Add the Core Data calls to load the parsed XML data into SQLite.

1
2
3
4
5
6
7
8
9
10
11
12
13
// To save the data, the basics are to create a NSEntityDescription for your model data:
self.award = (Award*)[NSEntityDescription
          insertNewObjectForEntityForName:@"Award" inManagedObjectContext:self.moc];

// Set the values:
[self.award setName:[attributeDict objectForKey:@"name"]];
[self.award setImageFileName:[attributeDict objectForKey:@"img"]];
      [self.award setEagleRequiredMB:[[NSNumber alloc]
  initWithBool:[[attributeDict objectForKey:@"eagleRequiredMB"] boolValue]]];

// And save it:
NSError *error;
if (![self.moc save:&error;]) NSLOG(@"Error: %@, %@", error, [error userInfo]);
  • Copy the created db into my ScoutTrail Resources group and added code to ScoutTrailAppDelegate to load that db.
    • iPhone Simulator creates the SQLite db in the user’s Library folder. Find it and copy to your project’s Resource directory (usually the project’s root level dir).
1
cp "/Users/scott/Library/Application Support/iPhone Simulator/4.0.2/Applications/5E9724E0-37ED-44CF-8954-FBAFD68FE662/Documents/ScoutTrailData.sqlite" /Users/scott/Apps/ScoutTrail
1
2
3
// To load the db into ScoutTrail, change the storeURL code in persistentStoreCoordinator from: 
NSURL *storeURL = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory]
  stringByAppendingPathComponent: @"ScoutTrailData.sqlite"]];

to:

1
2
3
4
5
6
7
8
9
10
NSString *storePath = [[self applicationDocumentsDirectory]
  stringByAppendingPathComponent: @"ScoutTrailData.sqlite"];
NSURL *storeURL = [NSURL fileURLWithPath:storePath];
NSFileManager *fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath:storePath]) {
  NSString *defaultStorePath = [[NSBundle mainBundle] pathForResource:@"ScoutTrailData" ofType:@"sqlite"];
  if (defaultStorePath) {
      fileManager copyItemAtPath:defaultStorePath toPath:storePath error:NULL];
  }
}
  • and fetch the data with Core Data calls.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
NSEntityDescription *entity = [NSEntityDescription
  entityForName:@"Award" inManagedObjectContext:moc];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entity];
  
NSPredicate *pred = [NSPredicate predicateWithFormat:@"type == 'rank'"];
[request setPredicate:pred];
  
NSSortDescriptor *sort = [[NSSortDescriptor alloc]
      initWithKey:@"displayOrder" ascending:YES];
[request setSortDescriptors:[NSArray arrayWithObject:sort]];
  
NSError *error;
self.data = [moc executeFetchRequest:request error:&error;];
if (self.data != nil) {
  int count = [self.data count];
  for (int i=0; i<count; i++) {
      NSLOG(@"rank[%d]: %@", i, [[self.data objectAtIndex:i] name]);
  }
} else {
  NSLOG(@"Error with fetch");
}
[request release];
[sort release];

Not all the aspects of adding Core Data has been covered in this post, such as adding the CoreData framework, the required Core Data methods in your AppDelegate, etc., but hopefully enough has been covered to help you along with using a preloaded db in your app.