Monday, December 8, 2008

Creating a database project - iPhone

This is the second part of an article outlining the code to implement a Client/Server application between the iPhone and a Windows WCF Service. The first part focused on re-factoring an existing webservice to use a back end database. Everything in this article assumes that you have successfully implement the changes described.

Creating our local iPhone database


The easiest way to create a SQLite database is to download the SQLite Firefox extension. This provides a GUI to create and manage any SQLite databases. Simply select "Database/New Database" from the menu and type the name JokeGenLocal as the name. You will be asked for a location of the database, i'd recommend not having it within your XCode Application, especially if ypu're using SVC for source control. I simply created the file in a folder called iWCFDemoDB at the same level as the original iWCFDemo project folder.



The local database format will mirror the server database format (created previously) as much as possible at this stage of development so I create two tables Joke and JokeCategory.



JokeCategory


Joke


We should also create indexes for these database table, but as our record set is very low at this stage I'm not going to do that.

Add the Database File to Your XCode Project


Now we have a database we want to include this as part of the XCode project. Right click the Resources folder in XCode and select "Add/Existing File..". Browse to the location where you saved the JokeGenLocal.sqlite file and click OK. I would not copy the file as local resource, but that decision is up to you.

Next we need to link to the library that contains all of the SQLite functions. To link to that library, from your XCode project right click the frameworks folder and select “Add/Existing Frameworks…”. Browse to the iPhoneSDK folder (mine was in/Developer/Patforms/iPhoneOS.platform/Developer/SDKs/iPkoneOS2.2.sdk/usr/lib/) and select the libsqlite3.0.dylib file.

Adding classes to xCode to hold access the database information


Once you have your database added to the application we need to create proxy classes which mirror the database structure and provide a mechanism for reading the records from the DB File. These are much the same as the Entity Framework classes which are created in Visual Studio, except Microsoft make it far easier for the developer. We also need a class that checks whether the iPhone has the database or not, if not then we copy it to the phone this will be the job of the SQLAppDelegate.

The boolean "isDirty" tells the application if the object was changed in memory or not and "isDetailViewHydrated" tell the application, if the data which shows up on the detail view is fetched from the database or not. Our application allows for very little in the way of editing at present however I'm leaving these in there as best practice and to assist in future development.

Create three new classes with headers and add the following code to each:
Category.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <sqlite3.h>
@interface Category : NSObject {
NSInteger categoryID;
NSString *categoryDescription;

//Intrnal variables to keep track of the state of the object.
BOOL isDirty;
BOOL isDetailViewHydrated;
}

@property (nonatomic, readonly) NSInteger categoryID;
@property (nonatomic, copy) NSString *categoryDescription;
@property (nonatomic, readwrite) BOOL isDirty;
@property (nonatomic, readwrite) BOOL isDetailViewHydrated;

@end


Category.m


#import "Category.h"
@implementation Category
@synthesize categoryID, categoryDescription, isDirty, isDetailViewHydrated;

- (void) dealloc {
[categoryDescription release];
[super dealloc];
}
@end


Joke.h

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <sqlite3.h>

@interface Joke : NSObject {

NSInteger jokeID;
NSInteger jokeCategory;
NSString *jokeText;
NSString *jokeSource;
NSString *jokeGraphic;

//Intrnal variables to keep track of the state of the object.
BOOL isDirty;
BOOL isDetailViewHydrated;
}

@property (nonatomic, readonly) NSInteger jokeID;
@property (nonatomic, readonly) NSInteger jokeCategory;
@property (nonatomic, copy) NSString *jokeText;
@property (nonatomic, copy) NSString *jokeSource;
@property (nonatomic, copy) NSString *jokeGraphic;

@property (nonatomic, readwrite) BOOL isDirty;
@property (nonatomic, readwrite) BOOL isDetailViewHydrated;

@end


Joke.m
#import "Joke.h"
@implementation Joke
@synthesize jokeID, jokeCategory, jokeText, jokeSource, jokeGraphic, isDirty, isDetailViewHydrated;
- (void) dealloc {
[jokeText release];
[jokeSource release];
[jokeGraphic release];
[super dealloc];
}
@end


SQLAppDelegate.h

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

@class Category;
@class Joke;

@interface SQLAppDelegate : NSObject <UIApplicationDelegate> {
UIWindow *window;
UINavigationController *navigationController;
//To hold a list of Category objects
NSMutableArray *categoryArray;
NSMutableArray *jokeArray;
}

@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet UINavigationController *navigationController;
@property (nonatomic, retain) NSMutableArray *categoryArray;
@property (nonatomic, retain) NSMutableArray *jokeArray;

- (void) copyDatabaseIfNeeded;
- (NSString *) getDBPath;

@end


SQLAppDelegate.m




Creating the contracts


We know the format of the message to be sent and how it will be received from the previous run through and I've bold the important areas.
Client Sends:
WSGetAllRequest
<s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<GetJokes xmlns="http://tempuri.org/">
<wsJokeRequest>
<RefreshDate>2008-12-09T16:18:36.5706144 08:00</RefreshDate>
</wsJokeRequest>
</GetJokes>
</s:Body>


WSGetAllResponse
<s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<GetJokesResponse xmlns="http://tempuri.org/">
<GetJokesResult>
<Jokes>
<Joke>
<EntityKey>
<EntitySetName>Joke</EntitySetName>
<EntityContainerName>JokeGenDBEntities</EntityContainerName>
<EntityKeyValues>
<EntityKeyMember>
<Key>ID</Key>
<Value xsi:type="xsd:int">1</Value>
</EntityKeyMember>
</EntityKeyValues>
</EntityKey>
<ID>1</ID>
<Text>"Knock, Knock". "Who's there?", "Sarah", "Sarah Who?", "Sarah doctor in the house."</Text>
<JokeCategoryReference>

<EntityKey>
<EntitySetName>JokeCategory</EntitySetName>
<EntityContainerName>JokeGenDBEntities</EntityContainerName>
<EntityKeyValues>
<EntityKeyMember>
<Key>Category</Key>
<Value xsi:type="xsd:int">1</Value>
</EntityKeyMember>
</EntityKeyValues>
</EntityKey>
</JokeCategoryReference>
</Joke>

... clipped there will be many, many iterations or this .....

</Jokes>
</GetJokesResult>
</GetJokesResponse>
</s:Body>