Verwendung einer SQLite Datenbank in einer Android App

Veröffentlicht in: Android, Java | 0

Will man in einer Android Anwendung Daten speichern, kann hierzu bequem die in das Android SDK integrierte SQLite Datenbank verwendet werden. Diese ist besonders für den mobilen Anwendungsbereich konzipiert, da sie mit gerade mal 250 KByte extrem genügsam im Bezug auf Speicherplatz ist.

Im folgenden Beispiel sollen die Einstellungen einer Anwendung in der Datenbank gespeichert werden. Hierzu wird eine SettingsActivity erstellt – nicht vergessen, die Activity im Android Manifest zu definieren –  wobei das Layout in einer XML-Datei definiert ist. Als variables Feld erhält die Activity einen Benutzer-Namen, welcher vom Benutzer editiert werden kann. Außerdem gibt es den obligatorischen Speichern-Button, damit die Änderungen vom Benutzer auch gespeichert werden können:

<EditText
android:id=“@+id/name“
android:layout_width=“fill_parent“
android:layout_height=“wrap_content“  android:background=“@android:drawable/editbox_background“
android:text=““
android:padding=“3dip“ />

<Button
android:layout_column=“1″
android:id=“@+id/save“
android:layout_width=“wrap_content“
android:layout_height=“wrap_content“
android:layout_alignParentRight=“true“
android:layout_marginLeft=“10dip“
android:text=“Speichern“ />

Um in den Activities der Anwendung direkten Zugriff auf die UI-Komponenten zu haben, wird das Dependency Injection Framework roboguice verwendet, welches zur Zeit in Version 1.2.2 unter folgendem Link zum Download bereit liegt: http://code.google.com/p/roboguice/wiki/Downloads

Zusätzlich zu roboguice wird noch die Google Library guice benötigt, die man unter folgendem Link erhält: http://code.google.com/p/google-guice/downloads/list

Hierbei ist zu beachten, dass nicht die aktuelle Version 3.0, sondern guice-2.0-no_aop.jar benötigt wird. Beide Libraries werden dem Classpath hinzugefügt. Nun wird im Android Manifest unter Application der Name roboguice.application.RoboApplication gesetzt. Dies ist nötig, da die Activities nun nicht mehr von Activity oder ListActivity direkt erben, sondern von RoboActivity bzw. RoboListActivity.

In der folgenden SettingsActivity wird über die Annotation @InjectView per Dependency Injection auf die im XML definierten UI-Elemente per Id zugegriffen:

public class SettingActivity extends RoboActivity {

private DatabaseManager databaseManager;
@InjectView(R.id.name)
private EditText nameText;
@InjectView(R.id.save)
private Button saveButton;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.setting);
databaseManager = new DatabaseManager();
final Settings settings = databaseManager.fetchSetting();
nameText.setText(settings.getName());
saveButton.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View view) {
settings.setName(nameText.getText().toString());
if (settings.getId() == null) {
databaseManager.createSettings(settings);
} else {
databaseManager.updateSettings(settings);
}
}
});
}

@Override
protected void onDestroy() {
super.onDestroy();
databaseManager.close();
}
}

In der onCreate-Methode wird ein DatabaseManager erzeugt, der sich um die Datenbank Connection kümmert, aber hierzu später noch mehr. Ebenfalls über den DatabaseManager werden die Settings bezogen, die in der UI dargestellt werden. Hierzu wird dem nameText, der Name aus den Settings zugewiesen. Nun wird noch dem Save-Button ein onClickListener hinzugefügt, der die Änderungen der UI dem Settings Objekt übergibt und dieses anschließend in der Datenbank speichert.

Wichtig ist, dass in der onDestroy Methode der Activity, die Datenbank-Connection wieder geschlossen wird – verlässt der Benutzer also die Einstellungen wird automatisch die Datenbank Verbindung zurück gesetzt.

Nun zur eigentlichen Interaktion mit der Datenbank über den DatabaseManager:

public class DatabaseManager {

private static final String DB_FILE = „/data/data/myapp.db“;
private SQLiteDatabase database;

public DatabaseManager() {
File file = new File(DB_FILE);
if (!file.exists()) {
database = SQLiteDatabase.openOrCreateDatabase(file, null);
database.execSQL(„create table Settings (id integer primary key autoincrement not null, name text);“);
} else {
database = SQLiteDatabase.openOrCreateDatabase(file, null);
}
}

public void close() {
database.close();
}

public Settings createSettings(Settings settings) {
ContentValues initialValues = createContentValues(settings);
long id = database.insert(„Settings“, null, initialValues);
settings.setId((int) id);
return settings;
}

public long updateSettings(Settings settings) {
ContentValues updateValues = createContentValues(settings);
return database.update(„Settings“, updateValues, „id=“ + settings.getId(), null);
}

public Settings fetchSetting() {
try {
Cursor cursor = database.query(„Settings“,
new String[] {
„id“, „name“
}, null, null, null, null, null);
cursor.moveToFirst();
if (!cursor.isAfterLast()) {
Integer id = cursor.getInt(cursor.getColumnIndexOrThrow(„id“));
String name = cursor.getString(cursor.getColumnIndexOrThrow(„name“);
Settings settings = new Settings(id, name);
return settings;
} else {
return new Settings();
}
} catch (SQLiteException e) {
return new Settings();
}
}

private ContentValues createContentValues(Settings settings) {
ContentValues values = new ContentValues();
values.put(„name“, settings.getName());
return values;
}
}

Im Konstruktor, der in der onCreate-Methode der SettingsActivity aufgerufen wurde, wird zunächst überprüft, ob die Datenbank-Datei bereits angelegt wurde. Ist dies nicht der Fall, werden die Settings der Anwendung Initial gespeichert und zusätzlich zum Öffnen der Datenbank muss die Settings Tabelle mittels SQL-Statement generiert werden.

Der nächste Aufruf der SettingsActivity lädt die bereits gespeicherten Settings über die fetchSettings-Methode. Hier wird auf der Datenbank eine Query gefeuert, der die gewünschten Properties mitgegeben werden und die sonst keinerlei Einschränkung enthält, es werden also wirklich alle Settings geladen. Das Ergebnis ist über einen Cursor zugreifbar, der zunächst über die moveToFirst-Methode auf die erste Position gesetzt werden muss. Die über den Cursor zugreifbaren Properties werden in einem Settings Objekt gespeichert, bzw. falls die Datenbank noch keinen Eintrag enthält wird ein neues Settings Objekt erzeugt und der SettingsActivity geliefert.

Werden die Einstellungen vom Benutzer geändert und der Speicher-Button betätigt, wird das Settings-Objekt entweder Initial in der Datenbank gespeichert oder aktualisiert. Dies hängt davon ab, ob die Settings bereits eine eindeutige Id enthalten. Bei dem Update-Statement wird eine Where Clause benötigt, die eine Einschränkung auf die gegebene Id – also den Primär Schlüssel – enthält. Andernfalls wird das Objekt neu erzeugt und die von der Datenbank vergebene Id in dem Settings-Objekt gespeichert.

Beim Insert bzw. Update wird nicht das Settings-Objekt selber verwendet sondern das generische Objekt ContentValues, dem per put-Befehl die einzelnen Properties zugewiesen werden können.

Sobald man nun noch die Permissions im Android-Manifest richtig gesetzt hat, steht dem ersten Probelauf jetzt nichts mehr im Wege.

<uses-permission android:name= „android.permission.WRITE_EXTERNAL_STORAGE“/>
<permission-group android:name=“android.permission-group.STORAGE“/>

Insgesamt lässt sich sagen, dass der Zugriff auf die SQLite Datenbank bei Android Anwendungen recht komfortable gestaltet ist, gerade weil hier alle benötigten Libraries bereits gegeben sind. Nur Schade, dass man nicht direkt mit Entities arbeiten kann, sondern dass diese auf die Datenbank Inhalte gemappt werden müssen. Es wäre angenehmer auf die Cursor zu verzichten und besser direkt über die Entity an die gewünschten Properties zu gelangen – ebenso wie beim Mappen der Properties auf die ContentValues ein zusätzlicher Schritt benötigt wird.

Quickstart in Java EE 6 und CDI

Veröffentlicht in: Java, JEE 6 | 0

Auf der JAX 2011 konnte ich mir ein paar interessante Vorträge über Java EE 6 im Allgemeinen und CDI im Speziellen anhören. Leider bin ich erst jetzt – einen guten Monat später – dazu gekommen, entsprechende Beispiele zu vertiefen.

Für einen möglichst schnellen Start empfiehlt es sich den Application Server GlassFish in der aktuellen Version 3.1 zu downloaden, bei diesem handelt es sich um die Referenz-Implementierung zu Java EE 6. Dementsprechend liefert GlassFish 3.1 bereits alle Java EE 6 Funktionalitäten inklusive der Servlet API 3.0 und CDI von Haus aus.

Aus Eclipse heraus lässt sich einfach ein Dynamic Web Project erstellen. Eclipse WTP in der aktuellen Version 3.2.3 unterstützt bereits GlassFish 3.1.

Dank der Servlet API 3.0 muss nicht länger die web.xml konfiguriert werden, eine einfache Annotation in der entsprechenden Klasse reicht aus, um das Servlet im Web Container zu registrieren.

@WebServlet(„/MyUserServlet“)
public class MyUserServlet extends HttpServlet {

}

Hier kann nun wie bereits in den Vorgängerversionen gehabt, die doGet Methode überschrieben werden. Anschließend ist das Servlet bereits über den Browser aufrufbar. Für weitere Request-Arten gibt es natürlich ebenfalls Methoden, die überschrieben werden können.

Kommen wir nun zur eigentlichen CDI Funktionalität, welche auch unter dem Namen Weld bekannt ist. Um CDI im Web Container zu aktivieren, ist es notwendig eine Datei beans.xml im WEB-INF Verzeichnis der Web Applikation zu erstellen. Eine leere Datei ist hierfür ausreichend.

Ein Beispiel von CDI ist die fachliche Dependency Injection. Hierbei wird nicht wie in der bisherigen Dependency Injection ein Service injiziert, sondern ein fachlicher Context. In einer herkömmlichen Web Applikation ist es nötig den UserService injiziert zu bekommen, ausschliesslich um an den eingeloggten Benutzer zu kommen. Statt dessen kann man sich nun einfach den Benutzer in die Applikation injizieren lassen und spart sich so den unnötigen Serveraufruf und eine Abhängigkeit auf den UserService.

Hierfür wird lediglich die Annotation @Produces über der Methode getCurrentUser benötigt, anschließend kann CDI eine @Inject Annotation auflösen, mit deren Hilfe der Benutzer an die entsprechende Stelle injiziert werden kann.

Der UserService hierfür sieht so aus:

@Produces
public User getCurrentUser() {
User user = new User(„max mustermann“);
return user;
}

Um den Benutzer in das Servlet injizieren zu lassen, reicht folgendes Code-Fragment:

@Inject User user;

Dieses Verfahren funktioniert allerdings nur, solange nicht weitere Methoden mit @Produces annotiert sind, die einen User zurück geben. Ist dies der Fall müssen diese unterschieden werden. Hierfür werden Qualifier benötigt, die mittels eigener Annotation definiert werden:

@Qualifier
@Retention(RUNTIME)
@Target( { TYPE, METHOD, PARAMETER, FIELD })
public @interface CurrentUser {
@Nonbinding String name();
}

Um das Beispiel interessanter zu machen, wird direkt in der Annotation der Name des Benutzers mit übergeben, so dass an der erzeugenden Stelle, der Name des Benutzers über die Annotation abgefragt werden kann:

@Produces
@CurrentUser(name=““)
public User getCurrentUser(InjectionPoint injectionPoint) {
String username = null;
for (Annotation qualifier : injectionPoint.getQualifiers()) {
if (qualifier instanceof CurrentUser) {
CurrentUser currentUser = (CurrentUser) qualifier;
username = currentUser.name();
}
}
User user = new User(username);
return user;
}

Die Annotation @CurrentUser zeigt an, dass diese Methode lediglich für den CurrentUser zuständig ist. Der Name der hier leer gelassen wurde, spielt keine Rolle, muss aber angegeben werden, da dies in der Annotation so gefordert ist. Der Parameter InjectionPoint wird direkt von CDI in die Methode injiziert. Über diesen Parameter kommt man an die Annotation, die im Injizierungspunkt gesetzt wurde. So kann man den Namen des Benutzers erfragen, der in der Annotation festgelegt wurde und diesen für den zu erstellenden Benutzer verwenden.

Die Stelle an der man den Benutzer injiziert bekommen möchte, sieht so aus:

@Inject @CurrentUser(name=“max mustermann“) User user;

Hier wird der Name gesetzt, der in der @Produces Methode dem Benutzer übergeben wird.

Dies war lediglich ein kleiner Teil der CDI Funktionalität. Ebenfalls sehr interessant ist das Event Konzept, die Möglichkeit einzelne Teile einer Anwendung per Annotation asynchron ausführen zu können, Aspekte unter CDI und vieles mehr.

Doch dazu zu einem späteren Zeitpunkt mehr.