Repositories with Realm

Realm 0.87 (for Android) came with a very important change: It introduced detached copies of RealmObjects. You can create a detached object using Realm.copyFromRealm. Any setter operation on a detached object won’t update the database so they can be called without opening a Realm transaction. In addition the detached objects are accessible from any thread.

We refactored our app code to use repositories and setup the following rules:

  • The UI only fetches realm objects via a repository (no more Realm code in Activities).
  • Repositories always return detached objects
  • Detached objects have to be saved using the save method in the repository.

Here’s a simple example repository:

public FooRepository {
 
    private Realm realm;
    
    public FooRepository(Realm realm) {
       this.realm = realm;  
    }
 
    public Foo getFooById(String fooId) {
        Foo foo = realm
                 .where(Foo.class)
                 .equalTo("id",fooId)
                 .findFirst();
        return realm.copyFromRealm(foo);
    }
    
    public void save(Foo detachedObjectToSave) {
       // isValid() always returns false on detached objects.
       if (object.isValid()) { 
          throw new IllegalArgumentException("received realm object is not a detached object");
       }

       realm.beginTransaction();
       realm.copyToRealmOrUpdate(object);
       realm.commitTransaction();
       // please note that this also could be saved 
       // in background using Realms asynchronous transactions.
    }
}

3 Dinge, die das BlackBerry-SDK gut löst

1. Manager und Fields

Die kleinste Basiseinheit im UI-Framework ist net.rim.device.api.ui.Field, welches einen rechteckigen, zeichenbaren Bereich darstellt. Beispiel für ein Field ist net.rim.device.api.ui.component.EditField, welches Texteingaben ermöglicht.

Fields werden durch net.rim.device.api.ui.Manager-Instanzen verwaltet. Ein Manager ist für die Positionierung der enthaltenen Fields sowie für das Scrolling verantwortlich. Jedes Field kann zu genau einem Manager zugeordnet werden. Beispiel für einen Manager ist net.rim.device.api.ui.container.HorizontalFieldManager, der seine Fields horizontal anordnet. Manager können auch andere Manager enthalten, da sie selbst von Field ableiten.

Für komplexere Views müssen eigene Manager und Fields implementiert werden. Was anfangs etwas gewöhnungsbedürftig erscheint, entpuppt sich schnell als durchdachtes Konzept.

2. Crypto-API

Kernkompetenz der BlackBerry-Smartphones ist der Einsatz in Unternehmen. Dementsprechend umfangreich und mächtig ist auch die Crypto-API des SDKs. Alle Verschlüsselungsanforderungen in unseren Projekten konnten wir mit der Crypto-API lösen, ohne fremde Bibliotheken verwenden zu müssen.

3. Eclipse-Integration

Mit eines der grössten Ärgernisse in der BlackBerry-Entwicklung war die veraltete, swing-basierte Entwicklungsumgebung names “JDE”. RIM hat reagiert und ein Plugin für Eclipse entwickelt, welches seit letztem Jahr, bzw. der Version 1.1.1 auch wirklich praxistauglich funktioniert. Durch das “Code Hot Swap”-Feature, also der Austausch von COD-Files zur Laufzeit verringern sich auch die zeitraubenden Neustarts des Simulators.

Zudem muss man immer noch eigene Ant-Skripte bauen, um auf Knopfdruck ein Deployment für alle unterstützten OS-Versionen zu generieren. Hier besteht noch Verbesserungspotential.

3 Dinge, die das BlackBerry-SDK eher suboptimal löst

1. Welchen Transportweg hätten Sie gern ?

Ein Http-Request kann über 5 verschiedene Transportwege übermittelt werden:
1. BIS (BlackBerry Internet Service)
2. BES (BlackBerry Enterprise Service)
3. WAP 2.0
4. Direct TCP – (Direkt über den APN des Netzbetreibers)
5. WiFi

Welcher Transportweg für eine Http-Verbindung genommen werden soll, muss der Entwickler selbst bestimmen und dabei gleichzeitig auch die Verfügbarkeit prüfen. Natürlich verhalten sich die Transportwege auch unterschiedlich: Beispielsweise wird man bei dem Transportweg BIS oder BES nie einen HTTP 301 oder 302 zu sehen bekommen, da die Redirects bereits in der BlackBerry-Infrastruktur aufgelöst werden.

Verschärfend kommt hinzu, dass dem Endkunden je nach Datentarif und gewählten Transport unterschiedliche Kosten entstehen können. Somit muss der Kunde in den Einstellungen der App die Möglichkeit haben, den präferierten Transportweg auszuwählen. iPhone-User und Applikationsentwickler schütteln hier einfach den Kopf. Zwar stellt RIM seit der OS-Version 5.0 eine Network-API zur Verfügung, die das ganze vereinfacht, aber für ältere OS-Versionen muss dies selbst implementiert werden.

2. Jeder schreibt sich das Logging-Framework selbst

Es soll ja tatsächlich in der Java-SE Welt noch vereinzelt Leute geben, die sich eine eigene Logger-Implementierung schreiben. In der BlackBerry-Welt macht das jeder, da die mitgelieferte EventLogger-Klasse nur rudimentäre Funktionalität bietet und sehr unglücklich zu programmieren ist. Beispiel gefällig ?

  // Register application for event logging with GUID
 EventLogger.register(0x9c805919833654d6L, SampleApp);

 // Log a numeric event.
 EventLogger.logEvent(0x9c805919833654d6L, 12, EventLogger.INFORMATION);

Im Übrigen kann ein Log maximal 16kB umfassen.

3. Anzahl der Threads bei älteren Geräten auf 16 limitiert

Eine App kann auf älteren Geräten max. 16 Threads gleichzeitig laufen lassen. Wenn eine App überdurchschnittlich komplex ist, und viele Dinge im Hintergrund erledigt (Parsing, GPS- und CellId-Lokalisierung, Laden von Kartendaten), dann muss man aufpassen, dass diese Grenze nicht überschritten wird. Ansonsten wird die Applikation unschön mit einem TooManyThreadError beendet. Dies betrifft allerdings nur die älteren Geräte. Modernere Modelle wie der Torch oder Bold können weitaus mehr Threads parallel laufen lassen.

Aber wo Schatten ist, ist auch Licht. Der nächste Blogeintrag handelt von 3 Dingen, die das BlackBerry-SDK elegant löst.

Mein Artikel “Brombeerplantage” im “Mobile Developer Android” Magazin


In eigener Sache: Morgen erscheint das Magazin “Mobile Developer Android” am Kiosk. Das Magazin setzt den Fokus klar auf Android-Entwicklung, behandelt aber andere mobile Plattformen wie BlackBerry oder Bada.

Ich konnte einen 7-seitigen Artikel beisteuern, der den Einstieg in diese Entwicklungsplattform erleichtert. Der Artikel stellt das UI-Framework vor, erläutert die Besonderheiten bei Netzwerkverbindungen und beleuchtet das Tooling sowie Fragmentierung der BlackBerry-Plattform. Viel Spaß beim Lesen …

URLEncoding à la RIM

Folgender Bug hat mich in der letzten Woche etwas Zeit gekostet: In einer BlackBerry-Applikation sollen Daten verschlüsselt über einen URL-Parameter verschickt werden. Die eigentliche Verschlüsselung erfolgt dabei mit RIM-Klassen. Das aus dem Verschlüsselungsprozess resultierende Byte-Array wird durch die URLEncodedPostData-Klasse encodiert.

Wie sich herausstellte, zeigt URLEncodedPostData ein ungewöhnliches (allerdings dokumentiertes) Verhalten bei Zeilenumbrüchen. Ein linefeed (\n) wird nicht zu %0A encodiert, sondern zu %0D%0A (entspricht \r\n). Somit verhält sich die RIM-Klasse anders als der java.net.URLEncoder der Java-SE.

Als Resultat konnten Byte-Arrays, die zufälligerweise ein linefeed enthielten, nicht mehr vom Server entschlüsselt werden. Da sich dieses Verhalten nicht beeinflussen lässt, musste eine alternative URLEncoder-Klasse in das Projekt integriert werden.

invokeAndWait und performSelectorOnMainThread

Wie bei Swing darf bei einer Blackberry-Applikation nur der Event-Dispatching Thread die UI-Komponenten modifizieren. Muss aus einem Background-Thread (bsp. bei der GPS-Lokalisierung oder nach dem Parsen von XML) eine UI-Komponente modifiziert werden, so muss dies über UIApplication.invokeAndWait oder UIApplication.invokeLater geschehen.

  1. UIApplication.getApplication().invokeAndWait( new Runnable() {
  2.    public void run() {
  3.         // modify the UI
  4.    }
  5. });

Beim iPhone OS ist das Prinzip identisch: Nur der Main-Thread darf UIKit-Komponenten verändern. Hier gibt es ebenfalls eine einfache Methode (performSelectorOnMainThread) um Code auf dem Main-Thread auszuführen:

  1. [delegate performSelectorOnMainThread:@selector(parserDidParse) withObject:nil waitUntilDone:NO];

Mit dieser Zeile wird der Main-Thread veranlasst, die Methode “parserDidParse” des “delegate”-Objekts ohne Parameter aufzurufen.

An diesem Beispiel sieht man wieder, wie stark sich die Konzepte von Blackberry und dem iPhone OS ähneln.

Blackberry Best-Practice: Zusammenspiel von Screens

Jeder Bildschirm in einer nativen Blackberry-Applikation leitet von net.rim.device.api.ui.Screen ab. Das Blackberry-Framework verwaltet die Screens als Stapel (“Stack”): Soll ein Screen geöffnet werden, muss er mit UIEngine.pushScreen(Screen screen) auf den Stapel gelegt werden.

Wird ein Screen geschlossen, wird wieder der darunterlegende Screen dargestellt. Dieses Verhalten entspricht im Übrigen genau dem UINavigationController des iPhone SDKs.

Beim “Pushen” des Screens werden dem Screen diejenigen Objekte übergeben, die er darstellen und verarbeiten muss. Hier ist beim Software-Design zu beachten, dass der Screen nicht das komplette ganze Modell kennt, sondern immer nur den spezifischen Modell-Abschnitt, den er darstellen muss. So wird eine enge Verzahnung der Screens vermieden und ein höherer Wartungs- und Wiederverwendungsgrad erreicht.

Wenn der oberste Screen Änderungen an darunterliegende Screens kommunizieren muss, so sollte dies über ein Interface geschehen, um weiterhin eine lose Koppelung der Screens sicherzustellen. Das nachfolgende vereinfachte Beispiel zeigt dieses Pattern anhand einer vereinfachten Mailapplikation.

Das Bild zeigt schematisch 3 Screens, von links nach rechts: IndexScreen, ListScreen und DetailScreen.

  1. Mit Klick auf “Posteingang” wird ein ListScreen erzeugt und auf den Stack “gepushed”
    Auf dem Stack liegen nun zwei Screens (IndexScreen und ListScreen). Der ListScreen hält eine Referenz auf einen Vector mit allen Emails.
  2. Beim Klick auf eine Email in der Liste wird eine Instanz des DetailScreens erzeugt und auf den Stack gepushed. Diese Instanz erhält eine Referenz auf das darzustellende E-Mail Objekt und den ListScreen über das Interface “DetailScreenListener”.
  3. Über genau dieses Interface teilt der Detailscreen dem ListScreen mit, wenn der User auf “Löschen” geklickt hat, damit dieser aus der Liste entfernt wird

Dieses Pattern erlaubt flexibel strukturierbare Anwendungen und wird sowohl im Blackberry- als auch in iPhone-Apps eingesetzt.

trakkcor-0.1.1

gibts ab heute hier zum download.

Die Version 0.1.1 ist ein Patchrelease mit kleineren Bugfixes und Änderungen. Beispielsweise gab es noch einen Fehler beim Parsen von NMEA Messages, wenn die Empfangsqualität (GPS Quality Indicator) schlecht war.

Viel Spass beim Ausprobieren. Wenn es wider Erwarten Probleme geben sollte, gleich hier als Kommentar posten.
Danke.

JABWT (JSR-82) Teil 5: Discovery Agent und DiscoveryListener

Ausgangspunkt für die Suche von Bluetoothgeräten ist die Klasse DiscoveryAgent. Sie erlaubt
die Suche nach Geräten und Diensten. Dabei wird die Suche im Hintergrund ausgeführt und blockiert nicht die Applikation. Wird ein Gerät oder ein Dienst gefunden, dann wird dies über ein Listener, dem DiscoveryListener, mitgeteilt.

Der Entwickler implementiert diesen Listener und übergibt ihn beim Start der Suche. Die JABWT-Implementierung ruft die Methoden dieses Listeners auf und übermittelt gefundene Geräte (Klasse RemoteDevice) bzw. Dienste (Interface ServiceRecord) als Übergabeparameter. Mit Hilfe dieser Objekte erhält man den Connection String (den wir ja nicht mehr hartcodieren möchten 😉 )

Im nachfolgenden Beispiel wird zunächst eine Suche nach Geräten mit der major device class “peripheral” ausgeführt. Anschliessend wird auf einem gefundenen Gerät die Suche nach dem RFCOMM-Dienst gestartet.

Beispielcode:

  1. ...
  2. DiscoveryListener listener      = new DiscoveryListener() {
  3.  
  4.  public void deviceDiscovered(RemoteDevice remoteDevice, DeviceClass deviceClass) {
  5.         // Ein Bluetoothgerät wurde gefunden.
  6.         // Filterung nach Major Device Class "Peripheral"
  7.         if ((deviceClass.getMajorDeviceClass() & 0x500) != 0) {
  8.                 // Speichern des remoteDevice in eine Liste
  9.         }
  10.         ...
  11.     }
  12.     public void servicesDiscovered(int i, ServiceRecord[] sr) {
  13.         // Ein oder mehrere Dienste wurden gefunden.
  14.         int reqSecurity = ServiceRecord.NOAUTHENTICATE_NOENCRYPT;
  15.         boolean mustBeMaster = false;
  16.         String connectionURL = sr[0].getConnectionURL(reqSecurity, mustBeMaster);
  17.         ...
  18.     }
  19.  
  20.     public void serviceSearchCompleted(int transactionID, int responseCode) {
  21.         // Abschluss der Suche nach Diensten.
  22.         if (responseCode == DiscoveryListener.SERVICE_SEARCH_ERROR) {
  23.                 // Suche war nicht erfolgreich -> Fehlerbehandlung
  24.         }
  25.         ...
  26.     }
  27.    
  28.     public void inquiryCompleted(int discType) {
  29.         // Abschluss der Suche nach Geräten.
  30.         if (discType == DiscoveryListener.INQUIRY_ERROR) {
  31.                 // Suche war nicht erfolgreich -> Fehlerbehandlung
  32.         }
  33.         ...
  34.     }
  35. }
  36.  
  37. LocalDevice local               = LocalDevice.getLocalDevice();
  38. DiscoveryAgent agent            = local.getDiscoveryAgent();
  39.  
  40. // Suche nach Geräten starten
  41. agent.startInquiry(DiscoveryAgent.GIAC, listener);
  42. ...
  43.  
  44. // Suche nach RFCOMM Dienst (0x0003) auf einem bereits gefundenen Gerät starten
  45. UUID[] uuidSet = new UUID[] { new UUID(0x0003) };
  46. int[] attrSet  = null; // null, weil uns nur die Default Attribute interessieren
  47. agent.searchServices(attrSet, uuidSet, remoteDevice, listener);