Mobile App Development - Persistenz - Mittwoch, 29. Mai 13 Inhalt • Shared Preferences • Dateien • Datenbanken • ORM Mobile App Development Mittwoch, 29. Mai 13 2 Shared Preferences Mittwoch, 29. Mai 13 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 Mittwoch, 29. Mai 13 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 Mittwoch, 29. Mai 13 5 Dateien Mittwoch, 29. Mai 13 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 Mittwoch, 29. Mai 13 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 Mittwoch, 29. Mai 13 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 Mittwoch, 29. Mai 13 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 Mittwoch, 29. Mai 13 10 Interner Speicher • Dateien schreiben • Context.openFileOutput(...) liefert einen FileOutputStream • existiert die Datei noch nicht, wird sie automatische angelegt • dsa Schreiben der Datei funktioniert wie auch bei Standard Java Mobile App Development Mittwoch, 29. Mai 13 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 exitiert */ ! 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 Mittwoch, 29. Mai 13 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 Mittwoch, 29. Mai 13 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 Mittwoch, 29. Mai 13 14 Interner Speicher •MODE_WORLD_WRITEABLE • Alle anderen Apps haben schreibenden Zugriff auf die Datei Mobile App Development Mittwoch, 29. Mai 13 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 Mittwoch, 29. Mai 13 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 Mittwoch, 29. Mai 13 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 Mittwoch, 29. Mai 13 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 Mittwoch, 29. Mai 13 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 Mittwoch, 29. Mai 13 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 Mittwoch, 29. Mai 13 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 Mittwoch, 29. Mai 13 22 Externer Speicher • Geteilte Daten sollten in öffentlichen Verzeichnissen abgelegt werden • Bsp.: /Music, /Podcasts, /Alarms, /Download, etc. Mobile App Development Mittwoch, 29. Mai 13 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 Mittwoch, 29. Mai 13 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 Mittwoch, 29. Mai 13 25 Datenbanken Mittwoch, 29. Mai 13 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 Mittwoch, 29. Mai 13 27 SQLite • Daten werden in SQLite durch sogenannte Storage Classes repräsentiert • Jedem Wert wird ein Datentyp zugeordnet Mobile App Development Mittwoch, 29. Mai 13 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 Mittwoch, 29. Mai 13 29 Storage Classes Datentyp Konvertierung Boolean INTEGER Date/ Time 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 Mittwoch, 29. Mai 13 Darstellung 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 Mittwoch, 29. Mai 13 31 Operatoren || * + << < = IN AND OR / >> <= == LIKE % & > != | >= <> GLOB IS IS NOT • siehe: http://www.sqlite.org/lang_expr.html Mobile App Development Mittwoch, 29. Mai 13 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 Mittwoch, 29. Mai 13 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 Mittwoch, 29. Mai 13 34 SQLiteOpenHelper • abstrakte Oberklasse für DB-Zugriffe • empfohlene Schnittstelle zum • Erstellen, Öffnen und • Aktualisieren von Datenbanken Mobile App Development Mittwoch, 29. Mai 13 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 Mobile App Development Mittwoch, 29. Mai 13 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 ausgelesen, gelöscht und neue Tabellen angelegt. Mobile App Development Mittwoch, 29. Mai 13 37 SQLiteOpenHelper • Beispiel: Erstellung einer Datenbank, mit der TODOs gespeichert werden können • Datenmodell: todo date Integer title Text description Text Mobile App Development Mittwoch, 29. Mai 13 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 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 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 Mittwoch, 29. Mai 13 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 Mittwoch, 29. Mai 13 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 Mittwoch, 29. Mai 13 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 Mittwoch, 29. Mai 13 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 Mittwoch, 29. Mai 13 43 Daten abfragen • query()-Parameter • orderBy: dient der Sortierung des Ergebnisses nach einer Spalte • limit: begrenzt die Anzahl der Ergebnisse Mobile App Development Mittwoch, 29. Mai 13 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 Mittwoch, 29. Mai 13 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 Mittwoch, 29. Mai 13 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 Mittwoch, 29. Mai 13 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 Mittwoch, 29. Mai 13 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 Mittwoch, 29. Mai 13 49 Daten abfragen • Zur Abfrage von Daten können auch Standard SQL Statements verwendet werden • Optional können diese parametrisiert werden Mobile App Development Mittwoch, 29. Mai 13 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 Mittwoch, 29. Mai 13 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 Mittwoch, 29. Mai 13 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 Mittwoch, 29. Mai 13 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 Mittwoch, 29. Mai 13 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 Mittwoch, 29. Mai 13 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 Mittwoch, 29. Mai 13 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 Mittwoch, 29. Mai 13 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 Mittwoch, 29. Mai 13 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 Mittwoch, 29. Mai 13 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 Mittwoch, 29. Mai 13 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 Mittwoch, 29. Mai 13 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 Mittwoch, 29. Mai 13 62 CursorAdapter Mittwoch, 29. Mai 13 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 Mittwoch, 29. Mai 13 64 CursorAdapter • Exkurs: Eigene List Layouts • Einträge in einer Liste können mit einem eigenen Layout versehen werden • Layout-Dateien werden genauso erstellt, wie bei Activity-Layouts Mobile App Development Mittwoch, 29. Mai 13 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 Mittwoch, 29. Mai 13 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 Mittwoch, 29. Mai 13 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 Mittwoch, 29. Mai 13 68 CursorAdapter private void setListCursorAdapter() { ! ! ! ! ! ! ! ! ! ! // Datenbank öffnen und Abfrage ausführen SQLiteDatabase db = new DbHelper(this).getReadableDatabase(); String sql = "select _id, title, datetime('now') as date from todo"; Cursor cursor = db.rawQuery(sql, null); ! // gibt an, welche Spalten in der ListView dargestellt werden sollen String[] from = new String[] { ! ! ! DbHelper.TITLE_FIELD_NAME, DbHelper.DATE_FIELD_NAME}; // 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 Mittwoch, 29. Mai 13 69 CursorAdapter • Hinweis: Um einen SimpleCursorAdapter zu nutzen, muss die verwendete Tabelle eine Spalte „_id“ besitzen! Mobile App Development Mittwoch, 29. Mai 13 70 Objekt-­‐relaConales Mapping Mittwoch, 29. Mai 13 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 Mittwoch, 29. Mai 13 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 Mittwoch, 29. Mai 13 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 Mittwoch, 29. Mai 13 1 n 74 todo_list 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 // ... Mobile App Development Mittwoch, 29. Mai 13 TODO_GROUP_TABLE_NAME = "todoGroup"; 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"; 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 Mittwoch, 29. Mai 13 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 Mittwoch, 29. Mai 13 77 ORM • Lösung • Es muss kein SQL mehr geschrieben werden (kann aber) • Es gibt viele verschiede ORM Frameworks (OrmLite, greenDAO, ...) • Hier wird OrmLite exemplarisch für Android vorgestellt Mobile App Development Mittwoch, 29. Mai 13 78 ORM • OrmLite (http://ormlite.com) • Sehr einfaches und simples OrmFramework • schnelle Einarbeitungszeit • Es muss nur eine jar-Datei eingebunden werden • Entities (Objekte) werden mit Annotations versehen Mobile App Development Mittwoch, 29. Mai 13 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; ! ! ! ! ! ! ! ! ! ! @DatabaseField(generatedId=true) private int id; @DatabaseField private String name; @ForeignCollectionField private Collection<Todo> todos; public Todo() { ! } ! } // setter & getter // setter & getter } Mobile App Development Mittwoch, 29. Mai 13 80 public TodoList() { ! } 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 Mittwoch, 29. Mai 13 81 ORM • OrmDbHelper 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 Mittwoch, 29. Mai 13 82 ORM • Verwendung • Live Präsentation Mobile App Development Mittwoch, 29. Mai 13 83 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 84 Mittwoch, 29. Mai 13