Persistenz - EmRoLab @ htw

Werbung
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
Herunterladen