When I initially create an SQLite database file with pre-inserted datasets for my application, I would have to place this file somewhere in my Xcode project so that it goes to my iPhone application. I guess "ressources" is the right place for that.
What are the basic "steps" for deployment of an SQLite database file in an iPhone application?
creating the database manually
adding the database file to the project (where?)
I'm currently reading the whole SQLite documentation, although that's not much iPhone related.
You need to add the SQLite file to your Xcode project first - the most appropriate place is in the resources folder.
Then in your application delegate code file, in the appDidFinishLaunching method, you need to first check if a writable copy of the the SQLite file has already been created - ie: a copy of the SQLite file has been created in the users document folder on the iPhone's file system. If yes, you don't do anything (else you would overwrite it with the default Xcode SQLite copy)
If no, then you copy the SQLite file there - to make it writable.
See the below code example to do this: this has been taken from Apple's SQLite books code sample where this method is called from the application delegates appDidFinishLaunching method.
// Creates a writable copy of the bundled default database in the application Documents directory.
- (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:#"bookdb.sql"];
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:#"bookdb.sql"];
success = [fileManager copyItemAtPath:defaultDBPath toPath:writableDBPath error:&error];
if (!success) {
NSAssert1(0, #"Failed to create writable database file with message '%#'.", [error localizedDescription]);
}
}
============
Here's the above code in Swift 2.0+
// Creates a writable copy of the bundled default database in the application Documents directory.
private func createEditableCopyOfDatabaseIfNeeded() -> Void
{
// First, test for existence.
let fileManager: NSFileManager = NSFileManager.defaultManager();
let paths:NSArray = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
let documentsDirectory:NSString = paths.objectAtIndex(0) as! NSString;
let writableDBPath:String = documentsDirectory.stringByAppendingPathComponent("bookdb.sql");
if (fileManager.fileExistsAtPath(writableDBPath) == true)
{
return
}
else // The writable database does not exist, so copy the default to the appropriate location.
{
let defaultDBPath = NSBundle.mainBundle().pathForResource("bookdb", ofType: "sql")!
do
{
try fileManager.copyItemAtPath(defaultDBPath, toPath: writableDBPath)
}
catch let unknownError
{
print("Failed to create writable database file with unknown error: \(unknownError)")
}
}
}
If you're just going to be querying for data, you should be able to leave it in the main bundle.
This, however, is probably not a good practice. If you were to extend your application in the future to allow for database writing, you'd have to figure everything out again...
Related
i have created an App in where i am using a SQLlite Database...I copy that Database, if needed, in the NSCaches Directory at the first start of the app with the following method:
- (void) copyDatabaseIfNeeded {
//Using NSFileManager we can perform many file system operations.
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSString *dbPath = [self getDBPath];
BOOL success = [fileManager fileExistsAtPath:dbPath];
if(!success) {
NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"datenbankSpeed"];
success = [fileManager copyItemAtPath:defaultDBPath toPath:dbPath error:&error];
if (!success)
NSAssert1(0, #"Failed to create writable database file with message '%#'.", [error localizedDescription]);
}
}
- (NSString *) getDBPath {
//Search for standard documents using NSSearchPathForDirectoriesInDomains
//First Param = Searching the documents directory
//Second Param = Searching the Users directory and not the System
//Expand any tildes and identify home directories.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory , NSUserDomainMask, YES);
NSString *documentsDir = [paths lastObject];
return [documentsDir stringByAppendingPathComponent:#"datenbankSpeed"];
}
My problem is, if i change sth in the Database- file and create a new App File for my Customers, they install the new App over the old App but the old Database is still in use, which will result in a crash!
I'm assuming the problem is only when you change your db schema. Create a version table in the sqlite database and add a column schema_version and populate it with a value from your code. Now, whenever you change your sql schema, update schema_version number in your code. In copyDatabaseIfNeeded, check if you have existing db file, open it and read schema_version. If this version is the same as your current version, then you're fine. Else, you need to migrate the schema. You'll probably also want to migrate the data into the new schema as well.
EDIT: To clarify - in copyDatabaseIfNeeded, do the following:
int version = ... // read schema_version from db
if (version != kCurrentSchemaVersion)
{
// convert the schema into new version preserving data if required.
// this can be a multi-step process. Eg. if user directly upgrades to schema_version 3
// after schema_version 1, first you'll convert from 1->2, then 2->3.
}
You might also want to take a look at PRAGMA user_version as mentioned by #Martin in the comments.
I created an sql database using "SQLite Database Browser", dragged and dropped it into my Xcode project, and built the app. It works perfectly well on the Simulator but crashes on the iPhone, with this error:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException',
reason: 'Failed to create writable database file with message 'The operation could‚
not be completed. (Cocoa error 260.)'.'
Here's my code:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Creates a writable copy of the bundled default database in the application Documents directory:
NSLog(#"AppDelegate...Looking for embedded Database file...");
BOOL success;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
// Grab the path to the Documents folder:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *writableDBPath = [documentsDirectory stringByAppendingPathComponent:#"users.sql"];
success = [fileManager fileExistsAtPath:writableDBPath];
if (success) {
NSLog(#"Database File Exists in Documents folder!");
NSLog(#"Its path is: %#", writableDBPath);
return YES;
}
else {
// But if the writable database does not exist, copy the default to the appropriate location.
NSLog(#"!!NO Database File Exists in Documents folder!");
NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"users.sql"];
success = [fileManager copyItemAtPath:defaultDBPath toPath:writableDBPath error:&error];
if (!success) {
NSAssert1(0, #"Failed to create writable database file with message '%#'.", [error localizedDescription]);
}
else
NSLog(#"WROTE THE DATABASE FILE!!!");
}
return YES;
}
Again, this works on the Simulator, but not on the iPhone. (This couldn't possible have anything to do with the file have a ".sql" extension as opposed to a ".sqlite" extension, could it? Cause that's the extensions that "SQLite Database Browser" gives the files it creates...)
The answer has to do with making sure the "Target Membership" of the sql file is set properly, so that the project "sees" it:
1) click on the sql file in the left-pane of Xcode
2) open/show the File Inspector (right pane)
3) Under "Target Membership", make sure the "check" is "checked"
that's it.
This solution resolves my problem i hope it will helps you out.
in my issue:
check this row:
NSString *writableDBPath = [documentsDirectory stringByAppendingPathComponent:#"users.sql"]
Check the fileName in your bundle and in code - the error also is cased of different filenames.
I will try describe my problem as best as I can. I have created a sqlite database using the console on the mac. I've added the file to my project, by right clicking and hitting "add files to (projectname)" and it adds the database. I am able to read the database, however when I try and update the database, although my debugging has confirmed the statements (update statements) succeed, the data in the database is not updating. I am gathering this is because the pointer to the database file is pointing to the .sql file in my documents (which is where the original file that I added is located), however I thought by adding the file to the project, a copy of that file would go within the project folder.
My question is how can I add a .sql file to my project where it is not referenced from the desktop, and be able to update the information that is located within the database.
No the file is not the one on your desktop, the file be bundled in the .app bundle.
The database is located in the app bundle which is readonly, you will need to move the database to the document directory on first startup.
place something like this in the - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
NSString *documentDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask,YES) objectAtIndex:0];
NSString *sqlFile =[documentDirectory stringByAppendingPathComponent:#"yourdb.sql"];
if (![[NSFileManager defaultManager] fileExistsAtPath:sqlFile]) {
NSString *bundleSql = [[NSBundle mainBundle] pathForResource:#"yourdb" ofType:#"sql"];
NSError *error = nil;
[[NSFileManager defaultManager] copyItemAtPath:bundleSql toPath:sqlFile error:&error];
if (error) {
NSLog(#"Could not copy json file: %#", error);
}
}
Get your SQL file's directory path on your bundle by :
NSString *sqlFilePath = [[NSBundle mainBundle] pathForResource:#"YourSQLFileName" ofType:#"sql"];
Next copy the sql file to your sandbox directory using this command :
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *sandboxDirectoryPath = [documentPaths objectAtIndex:0];
NSString *newSQLFilePath = [NSString stringWithFormat:#"%#/YourSQLFileName.sql", sandboxDirectoryPath];
[[NSFileManager defaultManager] copyItemAtPath:sqlFilePath toPath:newSQLFilePath error:error]
So I can access a read-only SQLite database inside an iPhone / iPod app (Objective-C), but I'm writing a new app that will have a writable database. Obviously, the r/w file has to be in the user-writable directory. My question is, should I ship an empty database with the application and copy it over to the r/w location, or create the r/w database on the fly the first time the app launches?
You can do either, but it will be much easier (and less error prone) for you to create an empty database, put it in your bundle and then do this in your AppDelegate.m file:
- (void)prepareDatabase
{
//add Database Versioning check to see if the resources database is newer
// generally as simple as naming your database with a version on the end
NSFileManager *filemanager = [NSFileManager defaultManager];
NSString *databasePath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingString:#"/YOURDATABASE.s3db"];
if(![filemanager fileExistsAtPath:databasePath]) {
//Database doesn't exist yet, so we copy it from our resources
NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingString:#"/YOURDATABASE.s3db"];
if([filemanager copyItemAtPath:defaultDBPath toPath:databasePath error:nil]) {
NSLog(#"Database Copied from resources");
} else {
NSLog(#"Database copy FAILED from %# to %#",defaultDBPath,databasePath);
}
}
}
Then in your applicationDidFinishLaunching: method call this:
[self prepareDatabase];
I have an iPhone app that use an Sqlite database to store some data and some user configurations. The problem that I'm having is that when I submit an update of my application, the existing database on the user installation is overwrite with the empty database and the users lost their configurations. I'm sure it can not be too difficult to avoid this, but I don't know how to do it.
This is my code of the method that create the copy of the db:
// Creates a writable copy of the bundled default database in the application Documents directory.
- (void)createEditableCopyOfDatabaseIfNeeded {
// First, test for existence.
BOOL success;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSString *writableDBPath = [self databasePath];
success = [fileManager fileExistsAtPath:writableDBPath];
if (!success) {
// The writable database does not exist, so copy the default to the appropriate location.
//NSLog(dbName);
NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:dbName];
success = [fileManager copyItemAtPath:defaultDBPath toPath:writableDBPath error:&error];
if (!success) {
NSAssert1(0, #"Failed to create writable database file with message '%#'.", [error localizedDescription]);
}
}
}
This method is called form:
- (BOOL)openDatabase {
BOOL success = true;
if (!database) {
[self createEditableCopyOfDatabaseIfNeeded];
if (sqlite3_open([[self databasePath] UTF8String], &database) != SQLITE_OK) {
success = false;
// Even though the open failed, call close to properly clean up resources.
sqlite3_close(database);
NSAssert1(0, #"Failed to open database with message '%s'.", sqlite3_errmsg(database));
}
}
return success;
}
- (NSString*)databasePath {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:dbName];
return path;
}
Maybe I forgot something in my code?
Can some one help me to solve this out? Thank you!
How about copying the sqlite database from your main bundle to the application's document directory, but only if it does not already exist?
If you are using Core Data, or using sqlite - you are probibly storing your data in the "Documents" directory. This will not be wiped-out when updating your app.
I don't know much about sqlite databases, except that they are in-memory databases. It is not possible to 'keep' the in-memory databases. You have two options:
1) find a way to configure your sqlite to use a file instead of running in-memory (I don't know if this is possible, I looked but couldn't find a way quickly)
2) switch to a different database provider. If the pc is yours, you can install xampp or wamp (lamp on linux), containing a pre-configured, ready-to-run MySql database.
A final way would be to temporarily store the sqlite data when exiting and then reload it on startup, but that doesn't seem very optimal!
If you don't really need a database, you could also consider alternate storing such as xml or a flatfile