Forschungswoche AndroiDB

Einer der großen Vorteile, bei Blau zu arbeiten, ist die Forschungswoche: Jeder Entwickler darf eine Woche lang zu einem beliebigen Thema forschen und entwickeln. Ich habe diese Möglichkeit genutzt, um einen [OR-Mapper](http://en.wikipedia.org/wiki/Object-relational_mapping") AndroiDB für Android zu programmieren. Warum?

Motivation

Wenn man auf Android Objekte in der SQLite-Datenbank ablegen möchte, muss man ziemlich viel SQL in den Code schreiben (siehe NotePad-Tutorial). Mir ist das zu umständlich und alles andere als DRY: Die Objektstruktur (= Tabellenschema) muss mindestens 6mal beschrieben werden: In der Klassendefinition, in der onCreate()-Methode zum Erzeugen das SQL-Schemas und in den vier SQL-CRUD-Methoden.
Mein Google sagte mir, dass es derzeit auch keine brauchbaren Tools gibt, mit denen man einfach Objekte in die Datenbank schreiben kann.

Ziel

Jeder, der schon mal mit Rails gearbeitet hat, weiß die ActiveRecord Models zu schätzen. Man erzeugt sich eine Instanz davon, manipuliert die Werte und ruft ein einfaches save auf der Instanz auf. Fertig. Die Schemaspezifikation ist in der Klasse durch die column Felder definiert. Genau so stelle ich mir Datenbankoperationen für Android vor.

Konzept

Jedes Model erbt von der abstrakten Klasse Table und stellt somit die CRUD-Methoden für sich selbst zur Verfügung. Über Annotions werden die Felder als @Column markiert. Dank Reflections kann Table alle relevanten Informationen zur Schema-Erzeugung sammeln und an die Datenbank schicken.

Nehmen wir eine einfache Klasse Note, die zwei Felder text und name enthält:

public class Note extends Table {
  Note(final Context context) {
    super(context);
  }

  @Column
  private String text;

  @Column(notNull = true)
  private String name;

  public String getText() {
    return text;
  }

  public void setText(final String text) {
    this.text = text;
  }

  public String getName() {
    return name;
  }

  public void setName(final String name) {
    this.name = name;
  }

  @Override
  protected void setValue(final Field field, final Object value) throws IllegalArgumentException,
      IllegalAccessException {
    field.set(this, value);
  }

  @Override
  protected Object getValue(final Field field) throws IllegalArgumentException, IllegalAccessException {
    return field.get(this);
  }
}

Durch @Column wird das Feld als Spalte definiert. Der Name der Spalte ist auch gleich der Name des Feldes, genauso wie der Klassenname den Tabellennamen beschreibt. Darüber hinaus kann man die Flags notNull, autoincrement und primaryKey setzen. Die beiden letzten Methoden setValue und getValue sind leider notwendig, damit aus der Table-Klasse auf die Felder zugegriffen werden kann. Das ist zwar hässlich, aber ich habe bisher keine Alternative gefunden, als sonst die Felder public machen zu müssen.

UPDATE: Matthias hat mir den Tipp gegeben, dass man einfach mit field.setAccessible(true) die Felder auch von außen manipulieren kann, solange kein SecurityManager in der App sitzt. Habe das natürlich gleich auf Github umgesetzt.

Kommen wir nun zum angenehmen Teil:

Note note = new Note(this);
note.setName("foo");
note.setText("bar");
note.save();

note.setText("blabla");
note.update();

note.delete();

Table selbst hält den Primary Key _id und kann darüber alle wichtigen Operationen ausführen.
Ein ausführlicheres Android-Beispielprojekt habe ich unter github.com/mattelacchiato/androiDBExample angelegt.

Erfahrungen

Wenn man eine Bibliothek für Android schreiben will, ist das gar nicht so einfach. Damit die lib in Android genutzt werden kann, muss sie mit dx-Compilier übersetzt sein. Daher gibt es derzeit auch kein JAR zum download, da ich noch kein Build-System dafür konfiguriert habe. Auch muss für das JUnit-Setup etwas mehr beachtet werden als normalerweise.

Sobald man mehr als zwei Abhängigkeiten hat, lohnt es sich einen Dependency Manager einzusetzen. Ich habe mir zum ersten Mal Maven angeschaut, das dank m2eclipse sehr leicht zu bedienen ist.

Auch wenn ich nicht strikt Testdriven gearbeitet habe, hat es mir sehr geholfen, von Anfang an Tests zu schreiben. Ich habe viele Refactoring-Stufen durchlaufen und interessanterweise waren die Tests mehr nützlich als hinderlich. So musste ich sie zwar auch immer wieder anpassen, aber ich konnte direkt sehen, welche Auswirkungen meine Änderungen haben.

Ausblick

Leider geht eine Woche sehr schnell rum, wenn man sich mit tollen Dingen beschäftigt. Daher gab es eine Menge Features, die noch nicht implementiert werden konnten. Zum Beispiel:

  • Relationen und Foreign Keys
  • find(Long id)/all()/delete(Long id) als Klassenmethoden anstatt Instanzmethoden
  • Versionierung der Tabellen und Migrationshelfer
  • SAFE_MODE, in dem mehr checks laufen um den Programmierer früher auf Fehler hinzuweisen und die Benutzung robuster zu machen

Falls du Interesse an dem Projekt hast: Fork me on Github

Mehr? Aktuelle Artikel oder alle Artikel im Archiv.