Mobile App Development - Persistenz - Inhalt • Shared Preferences • Dateien • Datenbanken • ORM Mobile App Development 2 Shared Preferences Shared Preferences • Framework zum Speichern von Key-ValuePaaren • Datentypen: boolean, float, int, long, String • Daten werden in einer Datei gespeichert • Nach Deinstallation der App werden Preferences-Dateien mit gelöscht Mobile App Development 4 Shared Preferences // DemoActivity für SharedPreferences public class SharedPrefsDemoActivity extends Activity {" ! " " " " " " " " " " " " " " " " " " " " " " " " } public static final String PREFS_FILE = "prefsFile";" public static final String MY_KEY = "myKey";" " private int mMyMember;" " protected void onCreate(Bundle savedInstanceState) {" " super.onCreate(savedInstanceState);" " " " // Aufrufen der SharedPreferences" " SharedPreferences prefs = getSharedPreferences(PREFS_FILE, MODE_PRIVATE);" " // Abruf des Wertes für MY_KEY (default: 0)" " mMyMember = prefs.getInt(MY_KEY, 0);" }" " @Override" protected void onStop() {" super.onStop();" // Aufruf der SharedPreferences" SharedPreferences prefs = getSharedPreferences(PREFS_FILE, MODE_PRIVATE);" // Editor aufrufen, um Änderungen zu machen" Editor editPrefs = prefs.edit();" // Key-Value-Paar anlegen/aktualisieren" editPrefs.putInt(MY_KEY, mMyMember);" // Änderungen schreiben" editPrefs.commit();" }" Mobile App Development 5 Dateien Dateien • Zwei mögliche Plätze zum Speichern von Dateien: • Internal Storage geräteinterner Speicher • External Storage externer Speicher, wie SD-Karten Mobile App Development 7 Interner Speicher • Standardmäßig sind Dateien, die auf dem internen Speicher abgelegt werden privat, d.h. andere Anwendungen können nicht darauf zugreifen • Wird die Anwendung deinstalliert, werden auch die internen Dateien gelöscht Mobile App Development 8 Interner Speicher • Lesen von Dateien • Context.openFileInput(...) liefert einen FileInputStream • Die weitere Verarbeitung erfolgt wie bei Standard Datei- und Stream-Verarbeitung in Java Mobile App Development 9 Interner Speicher • Datei lesen private void readFile(String fileName) throws Exception {" " /* openFileInput() öffnet die Datei mit dem angegebenen Namen" " * und liefert eine FIS zurück */" " FileInputStream fis = openFileInput(fileName);" " BufferedReader reader = new BufferedReader(" " " " new InputStreamReader(fis));" " String line = "";" " " " " /* Auslesen der Datei */" " while ( (line = reader.readLine()) != null) {" " " /* zeilenweise Verarbeitung */" " " Log.d(LOG, line);" " }" " " " " /* reader und stream schließen */" } Mobile App Development 10 Interner Speicher • Dateien schreiben • Context.openFileOutput(...) liefert einen FileOutputStream • existiert die Datei noch nicht, wird sie automatische angelegt • das Schreiben der Datei funktioniert wie auch bei Standard Java Mobile App Development 11 Interner Speicher • Dateien schreiben private void writeFile(String fileName, String content) " " " " throws Exception {" " " " " /* openFileOutput öffnet einen FOS, der mit der angegebenen " " Datei verbunden ist und legt die Datei an, wenn sie noch " " nicht existiert */" " FileOutputStream fos = openFileOutput(fileName, MODE_PRIVATE);" " OutputStreamWriter osw = new OutputStreamWriter(fos);" " " " " /* Inhalt in die Datei schreiben */" " osw.write(content);" " " " /* writer und stream schließen */" " osw.close();" } Mobile App Development 12 Interner Speicher • Android unterschiedet verschiedene Modi zum Anlegen einer Datei •MODE_PRIVATE" • Datei ist privat, kann also nur von der erstellenden App gelesen werden • wenn Datei bereits vorhanden, wird sie überschrieben Mobile App Development 13 Interner Speicher •MODE_APPEND" • Wenn Datei bereits vorhanden, wird neuer Inhalt angehängt •MODE_WORLD_READABLE" • Alle anderen Apps haben lesenden Zugriff Mobile App Development 14 Interner Speicher •MODE_WORLD_WRITEABLE" • Alle anderen Apps haben schreibenden Zugriff auf die Datei Mobile App Development 15 Interner Speicher • Es gibt einige weitere nützliche Methoden, um mit Dateien und Verzeichnisse zu arbeiten" • Context.getFilesDir() Absoluter Pfad zum dem Verzeichnis, in dem interne Dateien abgelegt werden • Context.getDir(name, mode) Erzeugt oder öffnet ein Verzeichnis Mobile App Development 16 Interner Speicher • Weitere Methoden" • Context.deleteFile(name) Entfernt die angegebene Datei • Context.fileList() Listet alle mit der App assoziierten (internen) Dateien auf Mobile App Development 17 Interner Speicher • Weitere Methoden" • Context.getCacheDir() Gibt den Pfad zum Verzeichnis für CacheDateien zurück • File.createTempFile(prefix, suffix) Erzeugt eine temporäre Datei im tempVerzeichnis Mobile App Development 18 Externer Speicher • Im Gegensatz zum internen Speicher gibt es bei externem Besonderheiten zu beachten: • Daten können von jedermann gelesen werden • Speicher muss nicht immer verfügbar sein (SD-Karte kann entfernt werden) Mobile App Development 19 Externer Speicher • Verfügbarkeit des ext. Speichers prüfen " private boolean mExtStorageReadable = false;" private boolean mExtStorageWritable = false; " ..." " " " " " " " " " " " " " " " " " " " " // Überprüft, ob der externe Speicher gelesen und geschrieben werden kann private void checkAvailability() {" " " " // Status des externen Speichers abrufen" " String state = Environment.getExternalStorageState();" " " " if (Environment.MEDIA_MOUNTED.equals(state)) {" " " // Speicher kann gelesen und geschrieben werden" " " mExtStorageReadable = true;" " " mExtStorageWritable = true;" " } else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {" " " // Speicher kann nur gelesen werden" " " mExtStorageReadable = true;" " " mExtStorageWritable = false;" " } else {" " " // Speicher ist z.B. entfernt oder hat kein unterstütztes Dateisystem" " " mExtStorageReadable = false;" " " mExtStorageWritable = false;" " }" } Mobile App Development 20 Externer Speicher • Unterscheidung zwischen App-eigenen Daten und geteilten Daten • App-eigene Daten sind nur für die eigene App relevant und werden bei Deinstallation mit gelöscht • Geteilte Daten können auch von anderen Apps interpretiert werden (Musik, Bilder, etc.) Mobile App Development 21 Externer Speicher • Zugriff auf App-eigene Daten " " " // Methode, um auf externen Speicher zuzugreifen private void accessFiles() {" " " " // Verfügbarkeit des ext. Speichers prüfen" " checkAvailability();" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " } ! ..." " // bis API Level 7 (Eclair)" File extStore = Environment.getExternalStorageDirectory();" File extAppDir7 = new File(extStore, " " "Android" + File.separator + " " "data" + File.separator + " getPackageName() + File.separator + " "files");" extAppDir7.mkdirs();" " // ab API Level 8 (Froyo)" File extAppDir8 = getExternalFilesDir(null);" // ..." // Standard Java Dateiarbeit, wie gehabt // ..." Mobile App Development 22 Externer Speicher • Geteilte Daten sollten in öffentlichen Verzeichnissen abgelegt werden • Bsp.: /Music, /Podcasts, /Alarms, /Download, etc. Mobile App Development 23 Externer Speicher • auf geteilte Daten zugreifen " " " " " " " " " " " " " " " " " " " " " private void accessPublicFiles() {" " " " // Verfügbarkeit des ext. Speichers prüfen" " checkAvailability(); " " ..." " " " // öffentliches Verzeichnis für Musik" " File musicDir = null;" " " " // bis API Level 7" " File extDir = Environment.getExternalStorageDirectory();" " musicDir = new File(extDir, "Music");" " " " // ab API Level 8" " musicDir = Environment" " " .getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC);" " " " // Danach, wie gewohnt" " File musicFile = new File(musicDir, "myMusic.mp3");" " musicFile.createNewFile();" } Mobile App Development 24 Externer Speicher • Temporäre Dateien auf den externen Speicher schreiben " " " " " " " " " " " " " " " " " " private void cacheFiles() {" " " " // Verfügbarkeit des ext. Speichers prüfen" " checkAvailability();" " // ..." " " " " " File cacheDir = null;" " " " // bis API Level 7" " File extDir = Environment.getExternalStorageDirectory();" " cacheDir = new File(extDir, "/Android/data/" + getPackageName() + "/cache/");" " " " // ab API Level 8" " cacheDir = getExternalCacheDir();" " " " // Erstellen der temporären Datei" " File cacheFile = File.createTempFile("myPrefix", "mySuffix", cacheDir);" " " } Mobile App Development 25 Datenbanken SQLite • SQLite ist die Standard Datenbank-Engine, die in Android verwendet wird • optimiert für Embedded Systems • zeichnet sich aus durch • Abgeschlossenheit, Serverlosigkeit • Minimal- bis 0-Konfiguration • unterstützt Standard SQL Mobile App Development 27 SQLite • Daten werden in SQLite durch sogenannte Storage Classes repräsentiert • Jedem Wert wird ein Datentyp zugeordnet Mobile App Development 28 Storage Classes Datentyp Beschreibung NULL Null-Wert INTEGER vorzeichenbehaftete Ganzzahl (1-8 Byte) REAL Dezimalzahl (8 Byte) TEXT String BLOB Binärdaten Mobile App Development 29 Storage Classes Datentyp Konvertierung Boolean INTEGER Date/ Time Darstellung 0 = false, 1 = true TEXT ISO8601: YY-MM-DD HH:MM.SS.SSS REAL Zeit in Tagen seit 01.01.4713 v.Chr. 12:00 INTEGER UNIX-Zeit, Sekunden seit 1970-01-01 00:00:00 UTC Mobile App Development 30 Datentypen • Alle Standard SQL-Datentypen können verwendet werden • Integer, Numeric, Decimal, Float, Real, Double • Varchar, Char • Date, Time, Timestamp • Boolean • Blob, Clob Mobile App Development 31 Operatoren ||" * + << < = IN AND OR / -" >> <= == LIKE " %" & > != |" >=" <> GLOB" IS IS NOT • siehe: http://www.sqlite.org/lang_expr.html Mobile App Development 32 DatenbankfunkConen • Kernfunktionen • abs, hex, min, max, random, round, ... • substr, trim, replace, upper, lower, ... • siehe: http://www.sqlite.org/ lang_corefunc.html Mobile App Development 33 DatenbankfunkConen • Aggregatfunktionen • min, max, avg, count, sum • weitere: http://www.sqlite.org/ lang_aggfunc.html • Datum & Zeit • date, time, datetime, strftime • http://www.sqlite.org/lang_datefunc.html Mobile App Development 34 SQLiteOpenHelper • abstrakte Oberklasse für DB-Zugriffe • empfohlene Schnittstelle zum • Erstellen, Öffnen und • Aktualisieren von Datenbanken Mobile App Development 35 SQLiteOpenHelper • Wenn von SQLiteOpenHelper abgeleitet wird, müssen folgende Methode überschrieben werden • Konstruktor: Hier wird dem super Konstruktor der Context, Datenbankname und -version mitgegeben • onCreate(): wird aufgerufen, wenn die Datenbank erstellt wird. Hier werden SQL Statements ausgeführt, um die Tabellen zu erstellen. Mobile App Development 36 SQLiteOpenHelper • onUpgrade(): wird aufgerufen, wenn eine • onDowngrade(): ist das Pendant zu der Methdode onUpgrade() (ab API 11) Datenbankversion gefunden wurde, die älter ist, als die aktuelle. Hier werden alte Datenbanktabellen angepasst. Mobile App Development 37 SQLiteOpenHelper • Beispiel: Erstellung einer Datenbank, mit der TODOs gespeichert werden können • Datenmodell: todo date Integer title Text description Text Mobile App Development 38 DatabaseHelper // Hilfsklasse zum Erstellen, Aktualisieren und Öffnen der Datenbank public class DatabaseHelper extends SQLiteOpenHelper {" ! " " " " " " " " " " " " " " " " " " " " " " " " " " " public static final String DATABASE_NAME = "todo.db"; " // Datenbankname" private static final int DATABASE_VERSION = 1;"" " // Datenbankversion" " public static final String TABLE_NAME = "todo";" " public static final String DATE_FIELD_NAME = "date";" public static final String DATE_FIELD_TYPE = "INTEGER";" public static final String TITLE_FIELD_NAME = "title";" public static final String TITLE_FIELD_TYPE = "TEXT";" public static final String DESCR_FIELD_NAME = "description";" public static final String DESCR_FIELD_TYPE = "TEXT";" " // SQL statement zum Erstellen der Tabelle" private static final String TABLE_CREATE = " " " "CREATE TABLE " + TABLE_NAME + "(" " " " " + DATE_FIELD_NAME + " " + DATE_FIELD_TYPE + ", "" " " " + TITLE_FIELD_NAME + " " + TITLE_FIELD_TYPE + ", "" " " " + DESCR_FIELD_NAME + " " + DESCR_FIELD_TYPE + ")";" " // Konstruktor" public DatabaseHelper(Context context) {" " super(context, " " " // der Context " " DATABASE_NAME, " " // Datenbankname " " null, " " " " // CursorFactory " " DATABASE_VERSION);" // Datenbankversion" }" " // ..." Mobile App Development 39 DatabaseHelper // Hilfsklasse zum Erstellen, Aktualisieren und Öffnen der Datenbank public class DatabaseHelper extends SQLiteOpenHelper { " " " " " " " " // Wird aufgerufen, wenn die DB das erste mal erstellt wird" public void onCreate(SQLiteDatabase db) {" " try {" " " db.execSQL(TABLE_CREATE);" " } catch (SQLException ex) {" " " Log.e("DatabaseHelper", "error creating tables", ex);" " }" }" " " " " " " " " " " } public void onUpgrade(SQLiteDatabase db, int oldVer, int newVer) {" " // alte Tabellen auslesen und löschen, neue Tabellen anlegen" " // alten Inhalt in neue Tabellen einfügen" }" ! // ab API-Level 11" public void onDowngrade(SQLiteDatabase db, int oldVer, int newVer) {" " // alte Tabellen auslesen und löschen, neue Tabellen anlegen" " // alten Inhalt in neue Tabellen einfügen" }" Mobile App Development 40 Daten abfragen • Mit der Methode getReadableDatabase() wird aus dem SQLiteOpenHelper eine lesbare Datenbankinstanz erzeugt • Mit der query()-Methode der DB Instanz können Daten gelesen werden • Die query() Methode besitzt einige Parameter, die einer SQL-Abfrage entsprechen Mobile App Development 41 Daten abfragen • query()-Parameter • table: der Tabellenname • columns: String Array, das die abzufragenden Spaltennamen enthält oder null für alle Spalten • selection: entspricht der WHERE-Klausel eines SQL-Statements ohne das WHERE Schlüsselwort Mobile App Development 42 Daten abfragen • query()-Parameter • selectionArgs: dient der Parametrisierung der selection • groupBy: dient der Gruppierung von Zeilen in Verbindung mit Aggregatfunktionen • having: Filter für Gruppierungen Mobile App Development 43 Daten abfragen • query()-Parameter • orderBy: dient der Sortierung des Ergebnisses nach einer Spalte • limit: begrenzt die Anzahl der Ergebnisse Mobile App Development 44 Daten abfragen // Methode zum abfragen von Daten aus der DB" private void doSelect() {" " " " " // Datenbank zum Lesen öffnen " " SQLiteDatabase db = new DatabaseHelper(this).getReadableDatabase();" " " " " // query(String table, String[] columns, String selection, String[] " " // selectionArgs, String groupBy, String having, String orderBy, " // String limit)" " Cursor result = db.query(" " " " DatabaseHelper.TABLE_NAME, null, null, null, null, null, " " " " DatabaseHelper.DATE_FIELD_NAME, null);" " " " " // Ergebnis verarbeiten" " " " " // Datenbank schließen" " db.close();" } Mobile App Development 45 Daten abfragen • Die query() Methode der DatenbankInstanz liefert einen Cursor zurück • Cursor repräsentieren das Ergebnis der Datenbankabfrage • Ein Cursor besitzt einen Zeiger, mit dem man datensatzweise über das ResultSet navigieren kann Mobile App Development 46 Daten abfragen • Nützliche Cursor Methoden • getCount(): gibt die Anzahl der • moveTo[First, Next, Last](): • getPosition(): gibt die aktuelle Datensätze im Cursor an bewegt den Datensatzzeiger im Cursor Zeigerposition im Cursor zurück Mobile App Development 47 Daten abfragen • Nützliche Cursor Methoden • getColumnIndex(): gibt den Index zum • get[Long,String,Int,Short,...](): übergebenen Spaltennamen zurück gibt den Wert im Datensatz für den angegebenen Spaltenindex zurück Mobile App Development 48 Daten abfragen // Methode zum abfragen von Daten aus der DB" private void doSelect() {" " // Datenbank zum Lesen öffnen und query ausführen" " Cursor result = db.query(...);" " " " " if (result.moveToFirst()) { // wenn Cursor nicht leer" " " // Spaltenindex vom Datums- und Titel-Feld abrufen" " " int dateIdx = result.getColumnIndex(DatabaseHelper.DATE_FIELD_NAME);" " " int titleIdx = result.getColumnIndex(DatabaseHelper.TITLE_FIELD_NAME);" " " do { " " " " // Datum und Titel auslesen" " " " long date = result.getLong(dateIdx);" " " " String title = result.getString(titleIdx);" " " " // ... und verarbeiten" " " " Log.d(LOG_TAG, new Date(date) + " " + title);" " " } while (result.moveToNext()); // solange Datensätze vorhanden" " }" ! " " " " } // Cursor direkt wieder schließen, um Ressourcen freizugeben " result.close();" // Datenbank schließen" db.close();" Mobile App Development 49 Daten abfragen • Zur Abfrage von Daten können auch Standard SQL Statements verwendet werden • Optional können diese parametrisiert werden Mobile App Development 50 Daten abfragen public void doRawSelect() {" " // Datenbank zum Lesen öffnen " " SQLiteDatabase db = new DatabaseHelper(this).getReadableDatabase();" " // SQL Statement erstellen" " String sql = "SELECT * FROM " + DatabaseHelper.TABLE_NAME + " " " " " ORDER BY " + DatabaseHelper.DATE_FIELD_NAME;" " // query ausführen" " Cursor result = db.rawQuery(sql, null);" " " " " if (result.moveToFirst()) { // wenn Cursor nicht leer" " " // Spaltenindex vom Datums- und Titel-Feld abrufen" " " int dateIdx = result.getColumnIndex(DatabaseHelper.DATE_FIELD_NAME);" " " int titleIdx = result.getColumnIndex(DatabaseHelper.TITLE_FIELD_NAME);" " " do { // für jeden Datensatz" " " " // Datum und Titel auslesen" " " " long date = result.getLong(dateIdx);" " " " String title = result.getString(titleIdx);" " " " // ... und verarbeiten" " " " Log.d(LOG_TAG, new Date(date) + " " + title);" " " } while (result.moveToNext()); // solange Datensätze vorhanden" " }" result.close();" // Cursor schließen" " db.close(); // Datenbank schließen" } Mobile App Development 51 Daten einfügen • Um Datensätze in die Datenbank einzufügen werden ContentValues Objekte verwendet • Ein ContentValues Objekt repräsentiert einen Datensatz in einer Tabelle • Ein CotentValues Objekt besitzt put() Methoden, die einen Schlüssel und einen Wert erhalten Mobile App Development 52 Daten einfügen • Schlüssel ist der Spaltenname aus der Datenbank und dazu der zu setzende Wert • ContentValues Objekte werden unter Angabe der Tabelle per insert() Methode in die Datenbank eingefügt Mobile App Development 53 Daten einfügen " " " " " " " " " " " " " " " " " " // Methode zum Einfügen von Daten in die DB" private void doInsert() {" " " " // Datenbank zum Schreiben öffnen" " SQLiteDatabase db = mDbHelper.getWritableDatabase();" " " " // Datensatz erstellen" " ContentValues vals = new ContentValues();" " vals.put(DatabaseHelper.DATE_FIELD_NAME, System.currentTimeMillis());" " vals.put(DatabaseHelper.TITLE_FIELD_NAME, "meinTitel");" " vals.put(DatabaseHelper.DESCR_FIELD_NAME, "meineBeschreibung");" " " " // Datensatz in die Datenbank einfügen" " db.insert(DatabaseHelper.TABLE_NAME, null, vals);" " " " // Datenbank schließen" " db.close();" } Mobile App Development 54 Daten aktualisieren • Die Aktualisierung von Datensätzen funktioniert ähnlich wie das Einfügen • Es wird ein ContentValues Objekt mit den neuen Daten erzeugt und per update() Methode abgeschickt Mobile App Development 55 Daten aktualisieren " " " " " " " " " " " " " ! " " " " " " // Mehtode zum Aktualisieren von Datensätzen" private void doUpdate() {" " " " // Datenbank zum Schreiben öffnen" " SQLiteDatabase db = mDbHelper.getWritableDatabase();" " " " // ContentValues Objekt mit den neuen Daten erzeugen" " ContentValues values = new ContentValues();" " values.put(DatabaseHelper.DATE_FIELD_NAME, " " " new java.util.Date().getTime());" " " " // Datensätze unter Angabe des Tabellennamens und einer " " // WHERE Bedingung aktualisieren" ! int rows = db.update(DatabaseHelper.TABLE_NAME, values, " " " " DatabaseHelper.DATE_FIELD_NAME + " LIKE 'b%'", null);" " Log.d(LOG_TAG, "affected rows: " + rows);" " " // Datenbank schließen" " db.close();" } Mobile App Development 56 Daten löschen • Datensätze werden gelöscht, indem eine WHERE Bedingung angegeben wird, die auf die zu löschende Datensätze zutrifft Mobile App Development 57 Daten löschen " " " " " " " " " " " " " " " " // Methode zum Löschen von Datensätzen" private void doDelete() {" " " " // Datenbank zum Schreiben öffnen" " SQLiteDatabase db = mDbHelper.getWritableDatabase();" " " " // Datensätze unter Angabe einer WHERE Bedingung löschen" " int rows = db.delete(DatabaseHelper.TABLE_NAME, " " " " DatabaseHelper.DATE_FIELD_NAME + " < date('now')", null);" " " " // Anzahl gelöschter Datensätze löschen" " Log.d(LOG_TAG, rows + " rows deleted");" " " " // Datenbank schließen" " db.close();" } Mobile App Development 58 Raw Queries • Einfügen, Aktualisieren und Löschen private void doRawInsertUpdateDelete() {" " // Datenbank zum Schreiben öffnen" " SQLiteDatabase db = new DatabaseHelper(this).getWritableDatabase();" " " " // insert statement erstellen & abschicken" " String insertSQL = "INSERT INTO " + DatabaseHelper.TABLE_NAME + " VALUES("" " " + System.currentTimeMillis() + ", 'mein Titel', 'meineBeschreibung')";" " db.execSQL(insertSQL);" " " " " // update statement erstellen & abschicken" " String updateSQL = "UPDATE " + DatabaseHelper.TABLE_NAME + " SET " " " " + DatabaseHelper.TITLE_FIELD_NAME + "=neuerTitel WHERE " " " " + DatabaseHelper.DATE_FIELD_NAME + "<" + System.currentTimeMillis();" " db.execSQL(updateSQL);" " " " " // delete statement erstellen & abschicken" " String deleteSQL = "DELETE FROM " + DatabaseHelper.TABLE_NAME + " WHERE "" " " + DatabaseHelper.TITLE_FIELD_NAME + " LIKE 'n%'";" " db.execSQL(deleteSQL);" " " " " db.close(); // Datenbank schließen" } Mobile App Development 59 TransakConen • Standardmäßig wird jedes SQL-Statement in einer eigenen Transaktion ausgeführt • Gründe, um einen eigenen Transaktionsrahmen zu setzen: • „succeed or fail as a whole“ • Performance Mobile App Development 60 TransakConen • Transaktionen werden mit beginTransaction() gestartet • ... und mit endTransaction() beendet • Um die Transaktion als erfolgreich zu markieren, muss vor dem Aufruf von endTransaction() die Methode setTransactionSuccessful() aufgerufen werden Mobile App Development 61 TransakConen • Transaktionen ausführen private void doTransaction() {" " // Datenbank zum Schreiben öffnen" " SQLiteDatabase db = new DatabaseHelper(this).getWritableDatabase();" " " " try {" " " // Transaktion starten" " " db.beginTransaction();" " " " " " /* auf der Datenbank arbeiten */" " " " " " // Transaktion als erfolgreich markieren" " " db.setTransactionSuccessful();" " } catch (Exception ex) {" " " // Fehler behandeln" " } finally {" " " // Transaktion beenden" " " db.endTransaction();" " }" } Mobile App Development 62 CursorAdapter CursorAdapter • Die Ergebnisse von Datenbankabfragen können über einen Adapter direkt in AdapterViews (ListView, SpinnerView) dargestellt werden • Hierfür werden CursorAdapter verwendet, die den AdapterViews ebenso wie ArrayAdapter ihre Daten zur Verfügung stellen Mobile App Development 64 CursorAdapter • Exkurs: Eigene List Layouts • Einträge in einer Liste, also die Darstel- lung der einzelnen Einträge, können mit einem eigenen Layout versehen werden • Layout-Dateien werden genauso erstellt, wie bei Activity-Layouts Mobile App Development 65 CursorAdapter • Exkurs: Eigene List Layouts <?xml version="1.0" encoding="utf-8"?>" <LinearLayout xmlns:android="..."" android:layout_width="match_parent"" android:layout_height="match_parent"" android:orientation="horizontal" >" ! ! ! <TextView" android:id="@+id/title"" android:layout_width="match_parent"" android:layout_height="wrap_content"" android:layout_weight="1"/>" <TextView" android:id="@+id/date"" android:layout_width="wrap_content"" android:layout_height="wrap_content"/>" </LinearLayout> Mobile App Development 66 CursorAdapter • Exkurs: Eigene List Layouts • Layout verwenden private void useCustomListLayout() {" " // ListView Referenz abrufen" " ListView listView = " " " (ListView) findViewById(R.id.listView1);" " // Adapter bekommt die Layout Referenz " " // zum angepassten Layout" " ArrayAdapter<String> listAdapter = " " " new ArrayAdapter<String>(" " " " this, R.layout.list_item);" " " " " // ... wie gehabt" } Mobile App Development 67 CursorAdapter • CursorAdapter stellen eine Brücke zwischen AdapterViews und Cursor Objekten her • Im CursorAdapter wird angegeben, welche Spalte auf welches Element im ListItem Layout abgebildet wird • Das Auslesen des Cursors und Setzen der ListItems wird vom Adapter übernommen Mobile App Development 68 CursorAdapter private void setListCursorAdapter() {" ! " " " " " " " " " // Datenbank öffnen und Abfrage ausführen" SQLiteDatabase db = new DbHelper(this).getReadableDatabase();" String sql = "select _id, title, date from todo";" Cursor cursor = db.rawQuery(sql, null);" " " // gibt an, welche Spalten in der ListView dargestellt werden sollen" String[] from = new String[] {"title", "date"};" // gibt an, welche Spalte in welcher View dargestellt werden soll" int[] to = new int[]{R.id.title, R.id.date};" " " " " " " " } // CursorAdapter erstellen" SimpleCursorAdapter sca = new SimpleCursorAdapter(this," " R.layout.list_item, cursor, from, to, 0);" " " // Adapter sezten" ListView lv = (ListView) findViewById(R.id.listView1);" lv.setAdapter(sca);"" ! Mobile App Development 69 CursorAdapter • Hinweis: Um einen SimpleCursorAdapter zu nutzen, muss die verwendete Tabelle eine Spalte „_id“ besitzen! Mobile App Development 70 Objekt-­‐relaConales Mapping ORM • Problemstellung • Objekt-Modell und Relationales Modell unterscheiden sich • Objekte müssen immer wieder in Tabellen-Datensätze konvertiert werden und umgekehrt Mobile App Development 72 ORM • Problemstellung • Hohe Fehleranfälligkeit durch Verwendung von Plain SQL (Typo‘s) • große Menge an SQL Statements muss programmiert werden Mobile App Development 73 ORM • Beispiel • zwei Tabellen mit einfacher 1:nVerknüpfung todo date title description fk_todo_lis t Integer Text Text Integer Mobile App Development 1 n 74 todo_group id name Integer Text ORM • Beispiel public class TodoDbHelper extends SQLiteOpenHelper {" " " " public static final String LOG_TAG = TodoDbHelper.class.getName();" ! " " ! " " " " " " " " " " " " " " " " " " " } public static final String DATABASE_NAME = "groupedtodo.db";" private static final int DATABASE_VERSION = 1;" // table creation attributes" public static final String TODO_TABLE_NAME = "todo";" " public static final String TODO_ID_FIELD_NAME = "_id";" public static final String TODO_ID_FIELD_TYPE = "INTEGER PRIMARY KEY AUTOINCREMENT";" public static final String TODO_DATE_FIELD_NAME = "date";" public static final String TODO_DATE_FIELD_TYPE = "INTEGER";" public static final String TODO_TITLE_FIELD_NAME = "title";" public static final String TODO_TITLE_FIELD_TYPE = "TEXT";" public static final String TODO_PARENT_FIELD_NAME = "parent";" public static final String TODO_PARENT_FIELD_TYPE = "INTEGER";" public static final String TODO_PARENT_FOREIGN_KEY = " "CONSTRAINT fk_parent FOREIGN KEY (parent) REFERENCES todoGroup(_id)";" " public static final String TODO_GROUP_TABLE_NAME = "todo_group";" public static final String TODO_GROUP_ID_FIELD_NAME = "_id";" public static final String TODO_GROUP_ID_FIELD_TYPE = "INTEGER PRIMARY KEY AUTOINCREMENT";" public static final String TODO_GROUP_NAME_FIELD_NAME = "name";" public static final String TODO_GROUP_NAME_FIELD_TYPE = "TEXT";" " // ..." Mobile App Development 75 ORM • Beispiel public class TodoDbHelper extends SQLiteOpenHelper {" " " " // ..." " " " // SQL statement for table creation" " private static final String TODO_TABLE_CREATE = " " " " "CREATE TABLE " + TODO_TABLE_NAME + "(" " " " " " + TODO_ID_FIELD_NAME + " " + TODO_ID_FIELD_TYPE + ", "" " " " " + TODO_DATE_FIELD_NAME + " " + TODO_DATE_FIELD_TYPE + ", "" " " " " + TODO_TITLE_FIELD_NAME + " " + TODO_TITLE_FIELD_TYPE + ", " " " " " " + TODO_PARENT_FIELD_NAME + " " + TODO_PARENT_FIELD_TYPE + ", "" " " " " + TODO_PARENT_FOREIGN_KEY + ")";" ! " " " " }" // SQL statement for TodoList table creation" private static final String TODO_GROUP_TABLE_CREATE = " " " String.format("CREATE TABLE %s (%s %s, %s %s)", TODO_GROUP_TABLE_NAME, " " " " " TODO_GROUP_ID_FIELD_NAME, TODO_GROUP_ID_FIELD_TYPE," " " " " TODO_GROUP_NAME_FIELD_NAME, TODO_GROUP_NAME_FIELD_TYPE);" Mobile App Development 76 ORM • Lösung • Objekt-relationales Mapping (ORM) ermöglicht die Abbildung von Objekten auf Tabellen • Abstraktion der Datenbankzugriffe durch Methoden zum Erstellen, Löschen, ... • Foreign-Key Beziehungen zwischen Datensätzen werden in Objektreferenzen umgewandelt Mobile App Development 77 ORM • Lösung • Es muss kein SQL mehr geschrieben werden (kann aber) • Es gibt viele verschiede ORM Frameworks (OrmLite, greenDAO, ...) • Hier in der Vorlesung wird OrmLite exemplarisch für Android vorgestellt Mobile App Development 78 ORM • OrmLite (http://ormlite.com) • Sehr einfaches OrmFramework • schnelle Einarbeitungszeit • Es muss nur eine jar-Datei eingebunden werden • Entities (Objekte) werden mit Annotations versehen Mobile App Development 79 ORM • Todo Beispiel @DatabaseTable(tableName="todo")" public class Todo {" @DatabaseTable(tableName="todolist")" public class TodoList {" " " " " " " " " " " " " " " " " " " " " " " " " ! ! ! @DatabaseField(generatedId=true)" private int id;" @DatabaseField" private long date;" @DatabaseField " private String title; " @DatabaseField" private String description;" @DatabaseField(foreign=true)" private TodoList parent;" " public Todo() {" " " }" ! " // setter & getter" }" ! ! ! // setter & getter" } Mobile App Development @DatabaseField(generatedId=true)" private int id;" @DatabaseField" private String name;" @ForeignCollectionField" private Collection<Todo> todos;" " public TodoList() {" " " }" 80 ORM • OrmDbHelper public class OrmDbHelper extends OrmLiteSqliteOpenHelper {" ! " " " " " " " " " " " " " " " " } public static final String LOG = OrmDbHelper.class.getName();" public static final String DB_NAME = "todo.db";" public static final int DB_VERSION = 1;" " public OrmDbHelper(Context context) {" " super(context, DB_NAME, null, DB_VERSION);" }" " @Override" public void onCreate(SQLiteDatabase db, ConnectionSource source) {" // Registrierung der Klassen beim ORM Framework" " try {" " " TableUtils.createTable(source, Todo.class);" TableUtils.createTable(source, TodoList.class);" " } catch (SQLException ex) {" " " Log.e(LOG, "error creating tables", ex);" " }" }" Mobile App Development 81 ORM • Data Access Objects • Der Zugriff auf die Entitäten, da heißt Suchen, Einfügen, Aktualisieren und Löschen, wird über sog. DAOs realisiert • Die DAOs kapseln die eigentlichen Datenbankzugriffe (SELECT, INSERT, ...) • Für jede Entität wird ein eigenes DAO erstellt Mobile App Development 82 ORM • Data Access Object erstellen public class OrmDbHelper extends OrmLiteSqliteOpenHelper {" ! ! " " " " " " " " ! // ... onCreate() ..." // Erzeugung des Data Access Objects" public Dao<Todo, Integer> createTodoDAO() {" " try {" " " return DaoManager.createDao(connectionSource, Todo.class);" " } catch (SQLException ex) {" " " Log.e(LOG, "error creating DAO for Todo class", ex);" " }" " return null;" }" } Mobile App Development 83 ORM • Daten suchen und manipulieren ! private void handleTodos() throws SQLException {" // Data Access Object für Todo erstellen " final Dao<Todo, Integer> todoDAO = dbHelper.createTodoDAO();" ! // query all todos from db" final List<Todo> allTodos = todoDAO.queryForAll();" ! // create new todo" Todo todo = new Todo(new Date(), "Test title", "Test description");" todoDAO.create(todo);" ! // delete todo element with id=5" todoDAO.deleteById(5);" ! // update todo element" todoDAO.update(todo);" }" Mobile App Development 84 Literatur I. Mark L. Murphy: The Busy Coder‘s Guide To Android Development,Version 4.3, 2012 II. Thomas Künneth: Android 3 - Apps entwickeln mit dem Android SDK Galileo Press, 2011 III. http://developer.android.com/guide/topics/ data/data-storage.html 85