5.3 Verwendung der integrierten Datenbank SQLite 153 5.3 Verwendung der integrierten Datenbank SQLite Ein wichtiges Element des iphone OS ist die integrierte Datenbank SQLite. Damit haben Sie die Möglichkeit, strukturierte Daten, wie z.b. Produkte, abzuspeichern. Dies können Sie in vielen Anwendungen einsetzen. In diesem Kapitel möchte ich Ihnen die grundlegenden Schritte zeigen, damit Sie in Ihren Anwendungen die SQLite-Datenbank verwenden können. Als Beispiel hierfür wird eine einfache Einkaufsliste implementiert. In der Einkaufsliste soll es möglich sein, Produkte hinzufügen und wieder zu löschen. Die einzelnen Produkte werden in der integrierten SQLite-Datenbank gespeichert. Die Anzeige der Produkte erfolgt in einer Liste. Mit strukturierten Daten umgehen Die Beispielanwendung 5.3.1 Erzeugen einer neuen Datenbank Für die Arbeit mit SQLite in einer Anwendung müssen Sie zuerst eine Datenbank anlegen. Dazu benötigen Sie entweder das Kommandozeilenwerkzeug sqlite3 oder das Plug-in SQLite Manager für den Firefox. Eine SQLite-Datenbank wird dabei in einer einzigen Datei gehalten. Das bereits installierte Kommandozeilenwerkzeug sqlite3 können Sie zur Arbeit mit einer SQLite-Datenbank heranziehen. In unserem Falle somit auch zur Erzeugung einer Datenbank. Um beispielhaft die Datenbank demo zu erzeugen, müssen Sie das Kommando sqlite3 demo absetzen. 26 Nun öffnet sich die SQLite-Konsole und Sie können darüber SQL-Kommandos eingeben. sqlite> create table tabelle1(columna varchar(10), columnb smallint); sqlite> insert into tabelle1 values('hallo',1); sqlite> insert into tabelle1 values('welt', 2); Für den Firefox existiert das Plug-in SQLite Manager, 27 das Ihnen eine grafische Oberfläche für die Administration von SQLite-Datenbanken bietet. Hiermit können Sie vorhandene SQLite-Datenbanken verwalten und auch neue Datenbanken erzeugen. Über den Menüpunkt Database/New Database erzeugen Sie eine neue Datenbank. Hierbei werden Sie zuerst nach dem Namen und dem lokalen Speicherort gefragt. Als Nächstes können Sie die Tabellen für Ihre Datenbank definieren. Über die Schaltfläche Create Table gelangen Sie zum Dialog zur Arbeit mit dem Kommandozeilenwerkzeug sqlite3 Listing 5 57 Datenbank über Kommandozeile erzeugen Arbeit mit dem Firefox- Plug-in SQLite Manager 26. Dokumentation unter http://www.sqlite.org/sqlite.html. 27. Verfügbar unter https://addons.mozilla.org/en-us/firefox/addon/5817.
154 5 Anwendungsentwicklung mit dem iphone SDK Erzeugung einer neuen Tabelle (siehe Abb. 5 30). Nach Betätigung der Schaltfläche OK wird Ihnen das SQL-Statement zur Anlage der Datenbank präsentiert und Sie müssen nur noch die Ausführung bestätigen. Nach erfolgreicher Ausführung ist die Datenbank angelegt. Über den Reiter Execute SQL ist es möglich, einzelne SQL-Statements auf der Datenbank abzusetzen. Damit können Sie schnell Ihre Datenstruktur testen. Abb. 5 30 Erzeugen einer Tabelle mit SQLite Manager Erzeugen der Beispieldatenbank Wir wollen uns nun beispielhaft ansehen, wie mit SQLite die Datenbank einkaufsliste mit der zugehörigen Tabelle produkt erzeugt wird. In der Tabelle werden die einzelnen Produkte für die Einkaufsliste gespeichert: CREATE TABLE "Produkt" ("ProduktID" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "Name" VARCHAR, "Anzahl" REAL) Die Tabelle wird mit zwei Beispielwerten gefüllt: insert into produkt (name, anzahl) values ('Banane', 5) insert into produkt (name, anzahl) values ('Apfel', 2) 5.3.2 Methoden zum Datenbankzugriff Die soeben erzeugte Tabelle wollen wir nun in der Anwendung Einkaufsliste verwenden. Erstellen Sie hierzu die Vorlage Navigation- Based Application, kopieren Sie die erzeugte Datenbank einkaufsliste.sqlite z.b. mittels Finder in das Projektverzeichnis und fügen Sie diese über Add/Existing Files dem Projekt hinzu.
5.3 Verwendung der integrierten Datenbank SQLite 155 Für den Zugriff auf SQLite müssen Sie die Bibliothek libsqlite3.0.dylib hinzufügen. Hier selektieren Sie den Ordner Frameworks innerhalb von Groups & Files. Aus dem Kontextmenü wählen Sie nun den Menüpunkt Add/Existing Frameworks aus. Die genannte Bibliothek befindet sich im Verzeichnis /usr/lib. Bibliothek hinzufügen Abb. 5 31 Frameworks der Anwendung Für die Darstellung der Produkte auf der Einkaufsliste erzeugen wir die Klasse Produkt (siehe Listing 5 58). Ein einzelnes Produkt besitzt einen Namen (1) und eine Anzahl (2). Die Instanzvariable produktid (3) ist für die Speicherung der ID des Produktes zuständig. @interface Produkt : NSObject { NSInteger produktid; 3 NSString *name; 1 NSDecimalNumber *anzahl; 2 BOOL isdirty; BOOL isdetailviewhydrated; Für die Anwendung sind drei Datenbankaktionen erforderlich. Diese Aktionen sind als Methoden der Klasse Produkt realisiert: Klasse für die Produkte Listing 5 58 Header-Datei für die Produkte Zugriffe auf die Datenbank Produkt hinzufügen (Methode addprodukt) Produkt löschen (Methode deleteprodukt) Produkte auslesen (Methode getinitialdatatodisplay) In der Klasse werden drei statische Variablen für die Arbeit mit SQLite gehalten. Eine Instanz von sqlite3 für die Datenbankverbindung (1), zwei Instanzen von sqlite3_stmt für das Hinzufügen (2) und Löschen (3) von Einträgen. static sqlite3 *database = nil; 1 static sqlite3_stmt *addstmt = nil; 2 static sqlite3_stmt *deletestmt = nil; 3 Das Öffnen der Datenbank erfolgt in der Methode getinitialdata- ToDisplay (siehe Listing 5 62). Diese Methode wird beim Start der Anwendung von der Klasse EinkaufslisteAppDelegate aufgerufen. Der Methode wird der Pfad zur Datenbank als Parameter übergeben. Da eine der Hauptfunktionen der Anwendung Einkaufsliste das Hinzufügen eines Produktes ist, wird für diese Aktion mit einem Listing 5 59 Statische Variablen Produkt hinzufügen
156 5 Anwendungsentwicklung mit dem iphone SDK PreparedStatement gearbeitet. Dies wird mit der Methode sqlite3_prepare_v2 (1) erzeugt. Die Parameter (Name des Produktes und Anzahl) werden über sqlite3_bind_text (2) und sqlite_bind_ double (3) gesetzt. Über die Methode sqlite3_step (4) wird das Kommando an die Datenbank abgesetzt. Zum Ende müssen Sie das Statement über sqlite3_reset (5) zurücksetzen. Listing 5 60 Methode zum Hinzufügen eines Produktes - (void) addprodukt { if(addstmt == nil) { const char *sql = "insert into Produkt(Name, Anzahl) Values(?,?)"; if(sqlite3_prepare_v2(database, sql, -1, &addstmt, NULL)!= SQLITE_OK) 1 NSAssert1(0, @"Error while creating add statement. '%s'", sqlite3_errmsg(database)); sqlite3_bind_text(addstmt, 1, [name UTF8String], -1, SQLITE_TRANSIENT); 2 sqlite3_bind_double(addstmt, 2, [anzahl doublevalue]); 3 if(sqlite_done!= sqlite3_step(addstmt)) { 4 NSAssert1(0, @"Error while inserting data. '%s'", sqlite3_errmsg(database)); else { produktid = sqlite3_last_insert_rowid(database); sqlite3_reset(addstmt); 5 Produkt löschen Listing 5 61 Methode zum Löschen eines Produktes Für das Löschen eines Produktes muss wie zuvor das Statement vorbereitet (1) werden. Als Parameter wird die eindeutige ID gesetzt (2). Die Ausführung erfolgt wieder über die Methode sqlite3_step (3). - (void) deleteprodukt { if(deletestmt == nil) { const char *sql = "delete from Produkt where produktid =?"; if(sqlite3_prepare_v2(database, sql, -1, &deletestmt, NULL)!= SQLITE_OK) 1 NSAssert1(0, @"Error while creating delete statement. '%s'", sqlite3_errmsg(database)); sqlite3_bind_int(deletestmt, 1, produktid); 2 if (SQLITE_DONE!= sqlite3_step(deletestmt)) 3 NSAssert1(0, @"Error while deleting. '%s'", sqlite3_errmsg(database)); sqlite3_reset(deletestmt);
5.3 Verwendung der integrierten Datenbank SQLite 157 Beim Öffnen der Anwendung sollen alle Produkte der Einkaufsliste in einer Liste angezeigt werden. Hierfür implementieren wir die statische Methode getinitialdatatodisplay (siehe Listing 5 62) in der Klasse Produkt. Der erste Schritt ist das Öffnen der Datenbank über die Methode sqlite3_open (1). Danach wird das Statement für das Auslesen vorbereitet (2). In einer Schleife (3) werden alle Werte aus der Datenbank ausgelesen: Über die API von SQLite werden die einzelnen Spalten herausgesucht. Für den Namen des Produktes wird z.b. die Methode sqlite3_column_text (4) verwendet. Über die Property isdirty (5) wird gespeichert, ob das Produkt aufgrund einer Änderung in der Datenbank abgespeichert werden soll. + (void) getinitialdatatodisplay:(nsstring *)dbpath { EinkaufslisteAppDelegate *appdelegate = (EinkaufslisteAppDelegate *)[[UIApplication sharedapplication] delegate]; Produkte auslesen Listing 5 62 Alle Produkte auslesen if (sqlite3_open([dbpath UTF8String], &database) == SQLITE_OK) { 1 const char *sql = "select produktid, name, anzahl from produkt"; sqlite3_stmt *selectstmt; if(sqlite3_prepare_v2(database, sql, -1, &selectstmt, NULL) == SQLITE_OK) { 2 while(sqlite3_step(selectstmt) == SQLITE_ROW) {3 NSInteger primarykey = sqlite3_column_int(selectstmt, 0); Produkt *produktobj = [[Produkt alloc] initwithprimarykey:primarykey]; produktobj.name = [NSString stringwithutf8string:(char *)sqlite3_column_text(selectstmt, 1)]; 4 NSDecimalNumber *anzahldn = [[NSDecimalNumber alloc] initwithdouble:sqlite3_column_double(selectstmt,2)]; produktobj.anzahl = anzahldn; [anzahldn release]; produktobj.isdirty = NO; 5 [appdelegate.productarray addobject:produktobj]; [produktobj release]; else sqlite3_close(database);
158 5 Anwendungsentwicklung mit dem iphone SDK 5.3.3 Einbindung in die Anwendung Nachdem nun die Methoden für den Datenbankzugriff implementiert sind, geht es an die Verwendung dieser Methode. Der Aufruf der Methoden erfolgt durch den Delegate, repräsentiert durch die Klasse EinkaufslisteAppDelegate (siehe Listing 5 63). Die Produkte werden in einer Instanz von NSMutableArray (1) gehalten. Listing 5 63 Instanzvariablen von EinkaufslisteApp- Delegate Methoden für den Delegate Listing 5 64 Methoden von EinkaufslisteApp- Delegate Listing 5 65 Laden der Daten @class Produkt; @interface EinkaufslisteAppDelegate : NSObject <UIApplicationDelegate> { UIWindow *window; UINavigationController *navigationcontroller; NSMutableArray *productarray; 1 Die Klasse EinkaufslisteAppDelegate definiert insgesamt vier Methoden für die Arbeit mit der Datenbank. - (void) copydatabaseifneeded; - (NSString *) getdbpath; - (void) addprodukt:(produkt *)produktobj; - (void) removeprodukt:(produkt *)produktobj; In der Methode applicationdidfinishlaunching (siehe Listing 5 65) erfolgt das Laden der Daten aus der Datenbank. Zunächst wird die Datenbank mit der Methode copydatabaseifneeded (siehe Listing 5 66) in den Anwendungsbereich kopiert (1). Über getinitialdatatodisplay (2) werden die Daten aus der Datenbank geladen. - (void)applicationdidfinishlaunching:(uiapplication *)application { [self copydatabaseifneeded]; 1 NSMutableArray *temparray = [[NSMutableArray alloc] init]; self.productarray = temparray; [temparray release]; [Produkt getinitialdatatodisplay:[self getdbpath]]; 2 [window addsubview:[navigationcontroller view]]; [window makekeyandvisible]; Prüfung, ob die Datenbank auf dem Endgerät existiert Bevor die Datenbank geladen werden kann, muss geprüft werden (siehe Listing 5 66), ob auf dem Endgerät des Benutzers die Datenbank bereits existiert. Die Prüfung erfolgt mit der Methode fileexistsatpath (1) der Klasse NSFileManager. Bei negativer Prüfung wird die Datenbank auf dem Endgerät gespeichert (2).
5.3 Verwendung der integrierten Datenbank SQLite 159 - (void) copydatabaseifneeded { NSFileManager *filemanager = [NSFileManager defaultmanager]; NSError *error; NSString *dbpath = [self getdbpath]; BOOL success = [filemanager fileexistsatpath:dbpath]; 1 Listing 5 66 Datenbank speichern if(!success) { NSString *defaultdbpath = [[[NSBundle mainbundle] resourcepath] stringbyappendingpathcomponent:@"einkaufsliste.sqlite"]; success = [filemanager copyitematpath:defaultdbpath topath:dbpath error:&error]; 2 if (!success) NSAssert1(0, @"Failed to create writable database file with message '%@'.", [error localizeddescription]); Für das Laden der Datenbank wird der Dateipfad benötigt. Dieser wird in der Methode getdbpath (siehe Listing 5 66) zusammengesetzt. - (NSString *) getdbpath { NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *documentsdir = [paths objectatindex:0]; return [documentsdir stringbyappendingpathcomponent:@"einkaufsliste.sqlite"]; Das Hinzufügen eines Produktes wird durch die Methode addprodukt (siehe Listing 5 68) ausgeführt. Zuerst wird das neue Produkt in der Datenbank gespeichert (1). Danach wird dieses zum internen productarray hinzugefügt (2). - (void) addprodukt:(produkt *)productobj { [productobj addprodukt]; 1 [productarray addobject:productobj]; 2 Listing 5 67 Zusammensetzen des Pfades zur Datenbank Hinzufügen eines Produktes Listing 5 68 Produkt hinzufügen In der Methode viewdidload (siehe Listing 5 69) der Klasse RootView- Controller werden die Schaltflächen für das Hinzufügen (1) und das Löschen (2) von Produkten konfiguriert. Zusätzlich wird der Delegate (3) gesetzt. Die Methoden für die Schaltflächen werden über @selector angegeben. - (void)viewdidload { [super viewdidload]; self.navigationitem.leftbarbuttonitem = [[UIBarButtonItem alloc] initwithbarbuttonsystemitem:uibarbuttonsystemitemadd Listing 5 69 Anzeige der Daten
160 5 Anwendungsentwicklung mit dem iphone SDK target:self action:@selector(add_clicked:)]; 1 self.navigationitem.rightbarbuttonitem = [[UIBarButtonItem alloc] initwithbarbuttonsystemitem:uibarbuttonsystemitemtrash target:self action:@selector(delete_clicked:)]; 2 appdelegate = (EinkaufslisteAppDelegate *)[[UIApplication sharedapplication] delegate]; 3 self.title = @"Einkaufsliste"; Methode add_clicked Listing 5 70 Aktion add_clicked Die Methode add_clicked (siehe Listing 5 70) lädt den AddProductView- Controller aus der nib-datei über die Methode initwithnibname (1). Nach dem Laden der nib-datei wird der View zum Hinzufügen eines Produktes angezeigt (2). - (void) add_clicked:(id)sender { if(avcontroller == nil) avcontroller = [[AddProductViewController alloc] initwithnibname:@"addproductview" bundle:nil]; 1 if(addnavigationcontroller == nil) addnavigationcontroller = [[UINavigationController alloc] initwithrootviewcontroller:avcontroller]; [self.navigationcontroller presentmodalviewcontroller:addnavigationcontroller animated:yes]; 2 Aussehen der Anwendung Das Aussehen der Anwendung (siehe Abb. 5 32) ist sehr einfach gehalten. Neben einer einfachen Liste erscheinen in der oberen Leiste die zwei Schaltflächen zum Hinzufügen und Löschen von Produkten. Abb. 5 32 Die Einkaufsliste im Einsatz