I want to open my SQLite database in my appDelegate class and reference that database in all my other classes that need the database. I have tried using :
static sqlite3 *database = nil;
But when I try to reference it in my other classes with appDelegate.database, I get a compile error of "error: request for member 'database' in something not a structure or union." How do you reference these types of properies?
You should access any variables stored in the app Delegate through the follow general formula:
YourAppDelegateName *delegate = (YourAppDelegateName *)[[UIApplication sharedApplication]delegate];
//access specific variables in delegate as follows:
sqlite3 *temp = delegate.database;
I suggest you to use the FMDB Objective C wrapper for sqlite. It will really simplify the access to your sqlite database. You can download it from
http://code.google.com/p/flycode/source/browse/trunk/fmdb#fmdb/src
Then,
you can use the following sample code, and use a NSString *db_path variable to access your db from other classes (you use your app delegate to access db_path, then use
FMDatabase *db = [FMDatabase databaseWithPath:db_path];
to acces your db. See the sample code below.
- (NSString *) initialize_db {
NSString *DATABASE_RESOURCE_NAME = #"yourDbName";
NSString *DATABASE_RESOURCE_TYPE = #"db";
NSString *DATABASE_FILE_NAME = #"yourDbName.db";
// copy the database from the bundle if necessary
// look to see if DB is in known location (~/Documents/$DATABASE_FILE_NAME)
NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentFolderPath = [searchPaths objectAtIndex: 0];
NSString *dbFilePath = [documentFolderPath stringByAppendingPathComponent: DATABASE_FILE_NAME];
[dbFilePath retain];
if (! [[NSFileManager defaultManager] fileExistsAtPath: dbFilePath]) {
// didn't find db, need to copy
NSString *backupDbPath = [[NSBundle mainBundle]
pathForResource:DATABASE_RESOURCE_NAME
ofType:DATABASE_RESOURCE_TYPE];
if (backupDbPath == nil) {
// couldn't find backup db to copy, bail
NSLog (#"couldn't init db");
return NULL;
} else {
BOOL copiedBackupDb = [[NSFileManager defaultManager]
copyItemAtPath:backupDbPath
toPath:dbFilePath
error:nil];
if (! copiedBackupDb) {
// copying backup db failed, bail
NSLog (#"couldn't init db");
return NULL;
}
}
}
return dbFilePath;
}
- (void)applicationDidFinishLaunching:(UIApplication *)application {
FMResultSet *item_rs;
// copy the database from the bundle if necessary
db_path = [self initialize_db];
if (! db_path) {
// TODO: alert the user!
NSLog (#"couldn't init db");
return;
}
FMDatabase *db = [FMDatabase databaseWithPath:db_path];
if (![db open]) {
NSLog(#"Could not open the db");
}
FMResultSet *rs = [db executeQuery:#"select * from yourTable"];
if ([db hadError]) {
NSLog(#"Err %d: %#", [db lastErrorCode], [db lastErrorMessage]);
}
while ([rs next]) {
[yourArray addObject:[rs stringForColumn:#"yourColumnName"]];
}
[rs close];
[db close];
// Configure and show the window
[window addSubview:[navigationController view]];
[window makeKeyAndVisible];
}
I needed to create an instance property for the database. My assumption that the static declaration was sufficient was incorrect. BTW, the FMDB/ORM advice is great. I am a huge fan of ORMs. However, this project is my first iphone and it is a small amount of database work and I want to learn. So, I am going to do it old school. Thanks for the advice.
Here are the code changes I made to make my global reference work.. Hope it helps someone:
/* myAppDelegate.h file */
#import <sqlite3.h>
#interface myAppDelegate : NSObject <UIApplicationDelegate> {
... // you may have windows etc here
sqlite3 *database;
}
#property (readwrite) sqlite3 *database;
/* myAppDelegate.m file */
#implementation myAppDelegate
...
#synthesize database;
/* some method in some class that uses the database */
- (void) getSomeData
{
myAppDelegate *appDelegate = (myAppDelegate *) [[ UIApplication sharedApplication ] delegate ];
const char *sql = "SELECT * FROM myTable";
sqlite3_stmt *selectstmt;
if(sqlite3_prepare_v2(appDelegate.database, sql, -1, &selectstmt, NULL) == SQLITE_OK)
{
// get the data here.
}
}
Related
I'm using SQLite in iOS 4 on an iPhone, but the changes made by my update statements aren't saved. At first thought perhaps quitting the app might be deleting the database somehow, but they're not even persisted during the same session. The code to initialize my database is (using FMDB):
-(SQLiteDal*) init {
pool = [[NSAutoreleasePool alloc] init];
self = [super init];
if(self != nil){
// Setup some globals
NSString *databaseName = [self getDbPath];
NSLog([NSString stringWithFormat:#"db path: %#", databaseName]);
db = (FMDatabase *)[FMDatabase databaseWithPath:databaseName];
if (![db open]) {
NSLog(#"Could not open db.");
[pool release];
return 0;
}
}
//[self checkAndCreateDatabase];
return self;
}
#pragma mark DB Maintenance
-(NSString *)getDbPath {
NSString *databaseName = #"myapp.db";
// Get the path to the documents directory and append the databaseName
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDir = [documentPaths objectAtIndex:0];
databaseName = [documentsDir stringByAppendingPathComponent:databaseName];
return databaseName;
}
Both of these methods are called to create the database, then to insert to a table I call:
[db executeQuery:#"INSERT INTO MyTable (Name, Description, Area, Price, ID) VALUES (?,?,?,?,?)",
f.Name,
f.description,
f.area,
f.price,
f.id];
The problem is, when I come to read from MyTable using the statement below, I never get anything back:
FMResultSet *rs = [db executeQuery:#"SELECT * FROM MyTable WHERE ID = ?", id];
while ([rs next]) {
//.. this is never called
As far as I can see I'm not missing anything out, and the DB seems to be in a writable location.
When inserting you need to call executeUpdate not executeQuery. Also you should call beginTransaction and then commit, like this:
[_dbPointer beginTransaction];
BOOL isInserted = [_dbPointer executeUpdate:[NSString stringWithFormat:#"INSERT INTO MyTable (Name, Description, Area, Price, ID) VALUES(?,?,?,?,?);", f.Name,
f.description,
f.area,
f.price,
f.id]];
[_dbPointer commit];
I am doing an alarm app for iOS, but I am a bit confused in taking data from different classes and save them to db in controller class.
for taking name, I have Name class, for taking time I have Time Class, for taking ringtone type, I have Ringtone class, so I am taking different values for one alarmTable(sqlite table) and saving them to db on save button which is in Controller.
I thought to take from every class and save them to delegate variables, and then fetch in controller class, is it almost successful, but having trouble in saving again default values,
Can anyone guide me that what is logic behind this?
These are variables in appDelegate
NSString *name;
NSString *time;
NSString *repeat;
NSString *sound;
NSString *snooz;
NSString *soundFade;
NSString *volume;
NSString *vibrate;
NSString *soundName;
This is way of getting values from appDelegates
-(NSString *) getName {
return name;
}
-(NSString *) getTime {
return time;
}
-(NSString *) getRepeat {
return repeat;
}
-(NSString *) getSound {
return sound;
}
-(NSString *) getSnooz {
return snooz;
}
-(NSString *) getSoundFade {
return soundFade;
}
-(NSString *) getVolume {
return volume;
}
-(NSString *) getVibrate {
return vibrate;
}
and when I do assign values to these delegate variables, I do write in different classes are below
AlarmProjectAppDelegate *delegate = (AlarmProjectAppDelegate *)[[UIApplication sharedApplication] delegate];
[delegate setName:name_textField.text];//name_textField contain alarmname
and I do like this before adding to Database
AlarmProjectAppDelegate *delegate = (AlarmProjectAppDelegate *)[[UIApplication sharedApplication] delegate];
//data for databas for new alarm start
NSString *name=[delegate getName];
NSString *time=[delegate getTime];
NSString *repeat=[delegate getRepeat];
NSString *sound=[delegate getSound];
NSString *snooz=[delegate getSnooz];
NSString *soundFade=delegate.soundFade;
NSString *volume=[delegate getVolume];
NSString *vibrate= [delegate getVibrate];
To make it simpler, you have to do is...
1. Create a separate class for individual table and declare all columns of table as variable in it.
2. Whenever you want to insert into any table, just create an instance of that table class and set all the variables.
3.Finally, pass that instance to your insert query as a parameter and get values from that.
For example...Create a class for Alarm table as below.
For master class, I'm modifying partial code. Assume it with name, time and repeat class.
In Alarm.h
#import <Foundation/Foundation.h>
#interface Alarm : NSObject {
name *objName;
time *objTime;
repeat *objRepeat;
}
// sample code for master class.
#property (nonatomic, retain) NSString *objName;
#property (nonatomic, retain) NSString *objTme;
#property (nonatomic, retain) NSString *objRepeat;
#end
In Alarm.m
#import "Alarm.h"
#implementation Alarm
#synthesize objName;
#synthesize objTime;
#synthesize objRepeat;
#end
Now, call your insert query as given below...
- (void) InsertAlarmData : (Alarm*)objAlarm
{
name *objName = objAlarm.objName;
time *objTime = objAlarm.objTime;
repeat *objRepeat = objAlarm.objRepeat;
NSString *query = [NSString stringWithFormat:#"INSERT into Alarm(name, time, repeat) values ('%#','%#','%#')", objAlarm.objName, objAlarm.objTime, objAlarm.objRepeat];
const char *sql = [query cStringUsingEncoding:NSUTF8StringEncoding];
if (sqlite3_open([databasePath UTF8String], &dbaseConnection) == SQLITE_OK)
{
if (sqlite3_prepare_v2(masterDBase, sql, -1, &hydrate_statement, NULL) == SQLITE_OK)
{
int success = sqlite3_step(hydrate_statement);
sqlite3_reset(hydrate_statement);
if (success != SQLITE_DONE)
{
UIAlertView *alert=[[UIAlertView alloc]initWithTitle:#"Database operation is not successful.." message:#" " delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil,nil];
[alert show];
[alert release];
NSLog(#"Error: failed to excecute query domain with message '%s'.", sqlite3_errmsg(masterDB));
}
}
else
{
NSAssert1(0, #"Error while creating add statement. '%s'", sqlite3_errmsg (masterDB));
}
sqlite3_reset(hydrate_statement);
sqlite3_finalize(hydrate_statement);
}
}
To set values in Alarm class...
name *objName = [[name alloc] init];
objName.name = #"ABC";
time *objTime = [[time alloc] init];
objTime.time = #"5 AM";
repeat *objRepeat = [[repeat alloc] init];
objRepeat.repeat = #"After 5 mins";
Alarm *objAlarm = [[Alarm alloc] init];
objAlarm.objName = objName;
objAlarm.objTime = objTime;
objAlarm.objRepeat = objRepeat;
[self InsertAlarmData:objAlarm];
Hope, you are getting my point exactly now.
how to create a sqlite file when the application starts (didFinishLaunchingWithOptions) the test if it already or not exsist otherwise create the file sqlite
Like this... the sqlPath variable is the path to the pre-made sql database on your ressource
- (void) checkAndCreateSQL
{
if (![[NSFileManager defaultManager] fileExistsAtPath:[documentPath stringByAppendingString:#"/database.sql"]]) {
[[NSFileManager defaultManager] createFileAtPath:[documentPath stringByAppendingString:#"/database.sql"]
contents:[NSData dataWithContentsOfFile:sqlPath]
attributes:nil];
}
}
EDIT 1:
You can create the database on your mac using this command line :
sqlite3 database.sql < DATABASE_CREATION.txt
in the DATABASE_CREATION.txt something like this :
CREATE TABLE IF NOT EXISTS `group` (
`id` integer PRIMARY KEY,
`name` text,
`position` integer
);
Then put directly the database.sql file into your project resource. (like an image)
You'd probably want to use the default Core Data libraries instead of manually creating and handling a single sqlite file. Please check the official Apple Core Data Programming Guide. It will automatically handle the creation and update of the inner database in the app.
sqlite3 *reference2Database() {
if (_database == nil) {
// First, test for existence.
NSError *error;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *writableDBPath = [documentsDirectory stringByAppendingPathComponent:#"my.sqlite"];
if ([fileManager fileExistsAtPath:writableDBPath] == NO) {
// Database file doesnt exists. Copy the database at writable path (documents directory).
NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"my.sqlite"];
[fileManager removeItemAtPath:writableDBPath error:nil];
BOOL databaseCopied = [fileManager copyItemAtPath:defaultDBPath toPath:writableDBPath error:&error];
if (!databaseCopied) {
// Handle the error...
}
}else {
// Open the database. The database was prepared outside the application.
if (sqlite3_open([writableDBPath UTF8String], &_database) != SQLITE_OK) {
// Even though the open failed, call close to properly clean up resources.
sqlite3_close(_database);
_database = nil;
// Additional error handling, as appropriate...
}
}
}
return _database;
}
// Sample usage.
-(void) someDatabaseFunction {
sqlite3 *database = reference2Database();
// Do something with "database"...
}
// Close the database. This should be called when the application terminates.
void closeDatabase() {
if (_database == nil) return;
// Close the database.
if (sqlite3_close(_database) != SQLITE_OK) {
// Handle the error...
}
_database = nil;
}
NOTE: At the top of the file, you should have: static sqlite3 *_database = nil;
I use Matteo Bertozzi's SQLite Wrapper to create my sqlite database with the following code:
-(void)checkDatabase
{
if([[NSFileManager defaultManager] fileExistsAtPath:DBPATH] == NO)
{
sqlite = [[Sqlite alloc] init];
if (![sqlite open:DBPATH]) return;
[sqlite executeNonQuery:#"DROP TABLE yourtable"];
[sqlite executeNonQuery:#"CREATE TABLE yourtable (record1 TEXT NOT NULL,
record2 TEXT NOT NULL,
record3 TEXT NOT NULL,
record4 TEXT NOT NULL);"];
NSArray *results = [sqlite executeQuery:#"SELECT * FROM yourtable;"];
for (NSDictionary *dictionary in results) {
for (NSString *key in [dictionary keyEnumerator])
NSLog(#" - %# %#", key, [dictionary objectForKey:key]);
}
[results release];
[sqlite release];
}
}
if I try and reun the code below I get an EXE_bad_access message on [categoryList count]
NSMutableArray *categoryList = [[CategoryItem alloc] getAll];
NSLog(#"number of items is %#", [categoryList count]);
The class is below
#import "CategoryItem.h"
#import "SQLite.h"
#interface CategoryItem : NSObject {
NSInteger ID;
NSInteger SortOrder;
NSString *Name;
NSString *ShoppingImage;
}
#property (nonatomic, nonatomic) NSInteger SortOrder;
#property (nonatomic, retain) NSString * Name;
#property (nonatomic, retain) NSString * ShoppingImage;
#property (nonatomic, nonatomic) NSInteger ID;
- (id)initWithObject:(NSInteger)itemID;
-(NSMutableArray *)getAll;
#end
#implementation CategoryItem
#synthesize ShoppingImage;
#synthesize Name;
#synthesize ID;
#synthesize SortOrder;
- (id)initWithObject:(NSInteger)itemID {
if ((self = [super init])) {
sqlite3 *database;
// Open the database. The database was prepared outside the application.
if (sqlite3_open([[SQLite fullFilePath] UTF8String], &database) == SQLITE_OK) {
// Get the primary key for all books.
const char *sql = "SELECT ID, Name, ShoppingImage, SortOrder FROM CategoryItem WHERE ID =?";
sqlite3_stmt *statement;
// Preparing a statement compiles the SQL query into a byte-code program in the SQLite library.
// The third parameter is either the length of the SQL string or -1 to read up to the first null terminator.
if (sqlite3_prepare_v2(database, sql, -1, &statement, NULL) == SQLITE_OK) {
// We "step" through the results - once for each row.
sqlite3_bind_int(statement, 1, itemID);
while (sqlite3_step(statement) == SQLITE_ROW) {
// The second parameter indicates the column index into the result set.
self.ID = itemID;
self.Name = [NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, 1)];
self.ShoppingImage = [NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, 2)];
self.SortOrder = sqlite3_column_int(statement, 3);
}
}
// "Finalize" the statement - releases the resources associated with the statement.
sqlite3_finalize(statement);
} else {
// Even though the open failed, call close to properly clean up resources.
sqlite3_close(database);
NSLog(#"Failed to open database with message '%s'.", sqlite3_errmsg(database));
// Additional error handling, as appropriate...
}
}
return self;
}
-(NSMutableArray*)getAll{
NSMutableArray *listArray = [[[NSMutableArray alloc] init] autorelease];
sqlite3 *database;
// Open the database. The database was prepared outside the application.
if (sqlite3_open([[SQLite fullFilePath] UTF8String], &database) == SQLITE_OK) {
// Get the primary key for all books.
const char *sql = "SELECT ID, Name, ShoppingImage, SortOrder FROM CategoryItem ORDER BY SortOrder";
sqlite3_stmt *statement;
// Preparing a statement compiles the SQL query into a byte-code program in the SQLite library.
// The third parameter is either the length of the SQL string or -1 to read up to the first null terminator.
if (sqlite3_prepare_v2(database, sql, -1, &statement, NULL) == SQLITE_OK)
{
// We "step" through the results - once for each row.
while (sqlite3_step(statement) == SQLITE_ROW)
{
// The second parameter indicates the column index into the result set.
CategoryItem *categoryItem = [[CategoryItem alloc] init];
categoryItem.ID = sqlite3_column_int(statement, 0);
categoryItem.Name = [NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, 1)];
categoryItem.ShoppingImage = [NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, 2)];
categoryItem.SortOrder = sqlite3_column_int(statement, 3);
[listArray addObject:categoryItem];
[categoryItem release];
categoryItem = nil;
}
}else{
printf( "could not prepare statemnt: %s\n", sqlite3_errmsg(database) );
}
// "Finalize" the statement - releases the resources associated with the statement.
sqlite3_finalize(statement);
} else {
// Even though the open failed, call close to properly clean up resources.
sqlite3_close(database);
NSLog(#"Failed to open database with message '%s'.", sqlite3_errmsg(database));
// Additional error handling, as appropriate...
}
//NSLog(#"this is the list array count %#", [listArray count]);
return listArray;
}
- (void)dealloc {
[super dealloc];
[Name release];
[ShoppingImage release];
}
#end
It doesn't seem right the way you create your CategoryItem. You are calling allocbut not any init... method. You may want to use the initWithObject method that you have provided in your implementation.
From Apple docs:
It takes two steps to create an object
using Objective-C. You must:
Dynamically allocate memory for the
new object
Initialize the newly
allocated memory to appropriate values
An object isn’t fully functional until
both steps have been completed. Each
step is accomplished by a separate
method but typically in a single line
of code:
id anObject = [[Rectangle alloc]
init];
EDIT:
Beyond the initialization problem, there seems to be a conceptual problem (pointed out by #Terry Wilcox):
Calling the method getAllon an instance does not seem to make sense and therefore should be defined as a class method instead:
+ (NSMutableArray*)getAll;
and should be called like this:
NSMutableArray *categoryList = [CategoryItem getAll];
EDIT 2:
Your log statement does not seem right either. [categoryList count]returns a NSUIntegerand you are trying to print an object with %#. Use %iinstead:
NSLog(#"number of items is %i", [categoryList count]);
This code:
NSMutableArray *categoryList = [[CategoryItem alloc] getAll];
doesn't make sense. If getAll is a class method on CategoryItem, then it should be defined as
+ (NSMutableArray*)getAll;
and you should call it as
NSMutableArray *categoryList = [CategoryItem getAll];
Then categoryList will be an array that you don't own, so you may want to retain it when you get it.
I'm trying to use sqlite in a 'data services' class like below. I keep getting an error code 1 when calling 'sqlite3_prepare_v2'. The logs certainly back up that the database is getting opened before my recipeNames function is called. I'm so stumped! Is it a memory management thing with the sqlite3 *db ? Eg is it getting freed when i don't want it to?
I'm certain the code is OK otherwise, because if i put the sqlite_open in the recipeNames function, it works great. It's just if i try to open it in the init method it fails. Thanks a lot guys.
My header:
#import <Foundation/Foundation.h>
#include <sqlite3.h>
#interface DataServices : NSObject {
sqlite3 *db;
int dbrc;
}
- (NSMutableArray *) recipeNames;
#end
Implementation file:
#import "DataServices.h"
#import "MenuMakerAppDelegate.h"
#include <sqlite3.h>
#implementation DataServices
- (id)init {
[super init];
// my init stuff here
// Get the database name
const char *dbPathUtf8 = [((MenuMakerAppDelegate*)[UIApplication sharedApplication].delegate).dbFilePath UTF8String];
// Open the database
NSLog(#"opening db");
sqlite3_open (dbPathUtf8, &db);
NSLog(#"opened db");
return self;
}
- (void)dealloc {
// My dealloc stuff here
NSLog(#"Closing DB");
sqlite3_close(db);
[super dealloc];
}
- (NSMutableArray *) recipeNames {
// Create the empty array to return
NSMutableArray *arr = [[[NSMutableArray alloc] init] autorelease];
// The SQL
NSString *sql = #"select recipe from recipes";
const char *sqlUtf8 = [sql UTF8String];
// Make the prepared statement (like C# SqlCommand)
sqlite3_stmt *dbps;
dbrc=sqlite3_prepare_v2(db, sqlUtf8, -1, &dbps, NULL); // FAILS HERE !!!
if (dbrc) NSLog(#"Prepare!!! %d", dbrc);
// Loop thru rows
while (sqlite3_step(dbps) == SQLITE_ROW) {
NSString *recipe = [NSString stringWithUTF8String:(char*)sqlite3_column_text(dbps,0)];
[arr addObject:recipe];
}
// Get rid of the command
sqlite3_finalize(dbps);
// Return the array that was autoreleased before
return arr;
}
#end
On a hunch:
NSLog(#"opening db %s", dbPathUtf8);
int result = sqlite3_open(dbPathUtf8, &db);
NSLog(#"sqlite3_open returned %d", result);
I'm betting that dbPathUtf8 is null and that sqlite3_open is returning an error. 1 is SQLITE_ERROR (SQL error or missing database), which probably means db is NULL.