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"; private static final int DATABASE_VERSION = 1; public public public public public public public static static static static static static static final final final final final final final String String String String String String String // Datenbankname // Datenbankversion TABLE_NAME = "todo"; DATE_FIELD_NAME = "date"; DATE_FIELD_TYPE = "INTEGER"; TITLE_FIELD_NAME = "title"; TITLE_FIELD_TYPE = "TEXT"; DESCR_FIELD_NAME = "description"; 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 super(context, DATABASE_NAME, null, DATABASE_VERSION); } context) { // der Context // Datenbankname // CursorFactory // 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 public public public public static static static static static final final final final final String String String String String TODO_GROUP_TABLE_NAME = "todo_group"; TODO_GROUP_ID_FIELD_NAME = "_id"; TODO_GROUP_ID_FIELD_TYPE = "INTEGER PRIMARY KEY AUTOINCREMENT"; TODO_GROUP_NAME_FIELD_NAME = "name"; 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 Orm-Framework • 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="todolist") public class TodoList { @DatabaseTable(tableName="todo") public class Todo { @DatabaseField(generatedId=true) private int id; @DatabaseField private long date; @DatabaseField private String title; @DatabaseField private String description; @DatabaseField(foreign=true) private TodoList parent; @DatabaseField(generatedId=true) private int id; @DatabaseField private String name; @ForeignCollectionField private Collection<Todo> todos; public Todo() { // setter & getter public TodoList() { } } } // setter & getter } Mobile App Development 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, heißt Suchen, Einfügen, Aktualisieren und Löschen und 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