code it

Martins Tech Blog

Code Coverage Reports für UnitTests in Android Studio

Android Studio unterstützt automatisiertes Testen bereits out-of-the-box. Dabei wird grundsätzlich zwischen verschiedenen Arten von Tests unterschieden. Eine detaillierte Beschreibung findet man im Testing-Abschnitt der Doku.

Nach dem Durchführen der Tests erhält man einen Testreport und (sofern aktiviert) auch einen mit JaCoCo erzeugten Coverage-Report. Allerdings war das Android-Studio-Team hier nicht ganz so konsequent, denn die Coverage-Reports werden für UnitTests nicht erzeugt. Hier spiegeln sich lediglich die Android-Tests wider.

Wie kann man nun erreichen, dass meine ganz normalen Unit-Tests mit JUnit auch einen solchen Coverage-Report erzeugen. In meinem Projekt habe ich nämlich lediglich JUnit-Tests und ich mocke den ganzen Android-Kram mit Hilfe von Robolectric und Mockito - was zugegebenermaßen dafür sorgt, dass ich keine Integrationstests habe, aber dafür eben die Funktionen sauber getestet sind ohne dass ich ich auf einen Emulator verlassen muss.

Nach stundenlangem Suchen in ähnlich gelagerten Problemen auf Stackoverflow bin ich dann in den Kommentaren auf ein Plugin gestoßen, das mein Build-Script genau mit den erforderlichen Funktionalitäten erweitert wird - und seit der Version 0.2.1, welche derzeit bereits als Snapshot vorliegt werden auch Intermediates ignoriert, die "$$" im Dateinamen haben.

OutOfMemoryError beim Kompilieren von Xamarin Projekten

Heute hab ich versucht in ein Xamarin Android Projekt das Google Ads Framework hinzuzufügen. Nichts leichter als das: Nuget und dort Xamarin.GooglePlayServices.Ads ausgewählt. 

Die Ernüchterung kam recht schnell: Ab diesem Zeitpunkt ließ sich mein Projekt nicht mehr kompilieren. Der Build dauerte viel länger als gewohnt und brach dann mit dem Fehler
java.lang.OutOfMemoryError. Consider increasing the value of $(JavaMaximumHeapSize). Java ran out of memory while executing 'java.exe'
ab. 

Nun befinde ich mich ja in Visual Studio und hab dort nicht wirklich eine Einstellung gefunden, die mir weiter geholfen hätte. Nach einiger Internetrecherche hier nun die Lösung: In der csproj-Datei muss folgender Code hinzugefügt werden: 
<PropertyGroup>
    <JavaMaximumHeapSize>1G</JavaMaximumHeapSize>
</PropertyGroup>
und schon läuft der Build wieder durch.

GreenDao ... und wo sind meine Daten?

In meinen Android-Projekten verwende ich gern GreenDao als OR-Mapper, denn es ist einfach zu verwenden und nach Aussagen des Herstellers auch performant und ich habe bisher keinen Grund gehabt daran zu zweifeln.

Heute bin ich über ein mir zunächst nicht erklärbares Phänomen gestolpert, das sich im Nachhinein mit Performance-Optimierungen im GreenDao-Core erklären lässt.

Aber ich beginne von vorn. Meine App hat einen klassischen Aufbau - eine Liste und per Klick auf einen Eintrag sieht man die Details und kann diese editieren. Jeder dieser Einträge hat nun noch datumsabhängige Sub-Daten. 

Um es zu verdeutlichen: Wald - Baum - Stammumfang pro Tag.

Da ich sowieso den Baum identifizieren und dessen Daten anzeigen musste, habe ich wie folgt auf die Daten zugegriffen:
Tree tree = _daoSession.getTreeDao().load(id);
List<MeasuringValue> mvls = tree.getMeasuringValueList(); 

Das Speichern der eigentlichen Daten erfolgt dann auch ganz nach Doku.
MeasuringValue mv = new MeasuringValue();
mv.setTreeId(id);
mv.setDate(date);
mv.setCircumference(circumference);

_daoSession.getMeasuringValueDao().insert(mv);

Das Phänomen ist nun gewesen, dass beim erneuten Aufrufen die neu gespeicherten Datensätze zunächst nicht vorhanden waren. Das Problem war mit debugging auch nicht nachvollziehbar - alles funktionierte korrekt und war auch auslesbar. Noch seltsamer: auch ohne angehängten Debugger waren die Daten die zunächst nicht angefügt schienen nach einigen Minuten doch enthalten.

Wie schon geschildert verwendet GreenDao einen Cache und der scheint hier das Problem zu sein, da ich die Daten auf einem anderen Weg auslese als ich sie einfüge. Beim Auslesen gehe ich über den Fremdschlüssel von Tree, beim Einfügen gehe ich direkt über die Messwert-Tabelle.

Das Problem lässt sich umgehen, indem man den Cache von GreenDao leert. 
_daoSession.clear();
Nachteil: man tauscht aktuelle Daten gegen den Performancevorteil ein - und so wäre die richtigere Lösung die Daten über den gleichen Weg auszulesen wie man sie anfügt.

Proguard und Realm.IO

In einem Projekt habe ich mal Realm ausprobiert. Dabei handelt es sich um eine alternative Datenbank, die verspricht die Wiederverwendung von Code aufgrund gleicher Model-Klassen sowohl in IOs als auch in Android hoch zu halten.

Detaillierte Erfahrungsberichte dazu kommen sicher in einem späteren Post.

Zunächst gab es eine kleiner Überraschung nach der Aktivierung von Proguard. Der Hersteller gibt folgende Konfiguration an:
-keep class io.realm.annotations.RealmModule
-keep @io.realm.annotations.RealmModule class *
-dontwarn javax.**
-dontwarn io.realm.**
Allerdings erreichte mich dann bei der Ausführung folgende Exception:
java.lang.IllegalArgumentException: class io.realm.internal.d declares multiple JSON fields named c
    at a.a.a.b.a.q.a(Unknown Source)
    at a.a.a.b.a.q.a(Unknown Source)
    at a.a.a.j.a(Unknown Source)
     .....
Heißt, die korrekte Proguard-Konfiguration sollte dieses Package wohl besser auch von der Obfuskierung ausschließen:
##---------------Begin: proguard configuration for Realm  ---------- 
-keep class io.realm.annotations.RealmModule -keep @io.realm.annotations.RealmModule class * -keep class io.realm.internal.** { *; } -dontwarn javax.** -dontwarn io.realm.** ##---------------End: proguard configuration for Realm ----------

SSLPeerUnverifiedException in Verbindung mit Android und IIS8

Das Backend für meine App sollt nur über eine https-Verbindung erreichbar sein. Alles kein Problem dachte ich mir, schnell ein SSL-Zertifikat besorgt und dieses der https-Bindung der Webanwendung zugewiesen. Alles kein Problem und auch der Browser zeigt das kleine grüne Schloss an und der Zertifizierungspfad war ok.

Um so überraschter war ich, dass meine Android App nun jeden Request mit einer Exception quittierte: 
javax.net.ssl.SSLPeerUnverifiedException (SSL peer not authenticated)

Laut Dokumentation passiert das, wenn man ein selbst signiertes Zertifikat verwendet und die Tipps im Web sind sehr breit gestreut - unter anderem "stelle den HttpClient doch so ein, dass er allen Zertifikaten vertraut". Das hilft bei selbst signierten Zertifikaten für eine Demo sicher, für eine echte App mit Kundendaten verwende ich diesen Tipp lieber nicht.

Was war nun aber Ursache für das Problem? Ich hatte im IIS in der https-Bindung den Haken bei "Require Server Name Indication (SNI)" gesetzt. Diese Einstellung sorgt eigentlich dafür, dass sich mehrere verschlüsselt abrufbare Websites unterschiedlicher Domains einen Server mit nur einer IP Adresse teilen können. Allerdings können ältere Browser damit Probleme haben. Offenbar zählt der HttpClient in Android / Java dazu, denn kaum hatte ich den Haken entfernt, ließ sich problemlos auf meine REST-API über HTTPS zugreifen.

Update 14.08.2015
Nach einiger Recherche habe ich nun herausgefunden, dass die neueren HttpClients durchaus in der Lage sind, mit SNI umzugehen. In der App in der das Problem auftrat hatte ich Spring for Android in der Version 1 verwendet. Der HttpClient dort hat das Problem mit SNI. Die neuere Version 2 von Spring for Android verwendet einen HttpClient, der problemlos damit umgehen kann.

noHistory in der Activity und der Seiteneffekt

Normalerweise kann man im savedInstanceState Statusinformationen ablegen, die dann beim Resume der Activity dafür sorgen, dass die Benutzeroberfläche wieder so hergestellt werden kann, wie sie war bevor die App in den Ruhemodus versetzt wurde.

Heute hatte ich das Phänomen, dass im onSaveInstanceState meine Daten in das Bundle geschrieben wurden, beim Fortsetzen der App im onCreate aber immer NULL als savedInstanceState bereitstand, was dafür sorgte, dass sich die Benutzeroberfläche immer zurücksetzte auf den Initialzustand.

Nach einiger Suche kam ich dann auf des Problems Lösung: Ich hatte in der Manifest-Datei der Activity das Flag noHistory="true" gesetzt. Laut Dokumentation hat dies folgenden Effekt:
Whether or not the activity should be removed from the activity stack and finished [...] when the user navigates away from it and it's no longer visible on screen.

[...] 

A value of "true" means that the activity will not leave a historical trace. It will not remain in the activity stack for the task, so the user will not be able to return to it.
Mein Verständnis dieses Flags war, dass dadurch die Activity nicht auf dem Backstack landet. Das funktioniert auch. Allerdings meint "the user will not be able to return to it" auch, dass sämtliche Informationen im Bundle verworfen werden und jedes Resume wie ein Neustart der Activity ist.

Bedingte Kompilierung im Android Studio

Wer wie ich aus der .NET-Welt kommt, der kennt das Feature der bedingten Kompilierung. Ein klassisches Beispiel dafür ist ein Codeschnipsel wie dieser hier:
var config = new Config();
#if DEBUG
    config.EndPoint = "http://localhost/testendpoint/";
#else
    config.EndPoint = "http://myapp.endpointsoftheworld/";
#endif
    new Wizard().DoSomeMagic(config);
Was passiert hier? Je nach aktueller Buildkonfiguration können Variablen definiert werden. Sind diese gesetzt kann mit Precompiler Switches auf diese Variablen zugegriffen werden und ein bestimmter Teil des Codes wird in die Kompilierung einbezogen oder auch nicht. Die wohl bekannteste dieser Precompiler Variablen ist DEBUG, welche wie der Name schon vermuten lässt im Debug-Build gesetzt ist.

Damit kann man erreichen, dass beispielsweise in Testbuilds anderer Code ausgeführt oder andere Einstellungen verwendet werden oder dass man bestimmte Module in seiner Anwendung zur Verfügung hat oder auch nicht. Als Szenario wäre hier denkbar dass man eine Free- und eine Bezahlvariante seiner Anwendung anbietet.

Genau diese Anwendungsfälle sind auch im Bereich der App-Entwicklung durchaus gängige Szenarien. Allerdings unterstützt Java und damit auch Android solche Preprozessor Direktiven nicht. 

Eine durchaus gängige Lösung ist es, mit anwendungsweiten Konstanten zu arbeiten:
public final class Debug {
  //set to false to allow compiler to identify and eliminate
  //unreachable code
  public static final boolean ON = true;
} 
var config = new Config();
if (Debug.ON) {
    config.EndPoint = "http://localhost/testendpoint/";
} else {
    config.EndPoint = "http://myapp.endpointsoftheworld/";
}
new Wizard().DoSomeMagic(config);
Der Kompiler erkennt die Konstante und im eigentlichen Code ist dann auch nur der Code enthalten, der gewünscht ist. 

Diese Lösung funktioniert zwar und ist auch recht ähnlich zu den Precompiler Switches, hat aber einen entscheidenden Nachteil: Die Konstante muss jedes mal von Hand auf den korrekten Wert gesetzt werden. Ein Automatismus wie bei .NET ist nicht vorgesehen und wie schnell können so Fehler passieren und plötzlich laufen Testcalls gegen die echte API und zerstören Daten.

In Android Studio wird Gradle als Buildssystem verwendet. Damit sind automatisierte Builds möglich. Und genau hier kann man ansetzen, um das Problem zu lösen. Denn Gradle unterstützt Sourcesets und Flavors. "Debug" und "Release" sind bereits mit dem Projektsetup enthalten. Mit diesem Wissen kann man nun einfach neben dem eigentlichen Main-Ordner auf gleicher Ebene zwei neue Ordner an und benennt diese "debug" und "release". Hier kann man nun Packages und Quellcodedateien ablegen, die je nach Buildvariante enthalten sein sollen.


In obiger Konfiguration wird nun die Klasse Configuration aus debug verwendet, wenn ich assembleDebug aufrufe und nach gleicher Logik die gleichnamige Klasse Configuration aus release beim Aufruf von assembleRelease.

Meine Empfehlung ist in Java wie auch in .NET, mit solchen Switches umsichtig umzugehen. Die Komplexität der Anwendung steigt ungemein je mehr solcher Switches verwendet werden und verringern die Wartbarkeit.

Rückblick auf das MobileCamp 2014 - der Sonntag

Tag 2
Auch der zweiten Tag begann mit einer Keynote und diese war besonders interessant und kurzweilig. Zwei Google Glass zum selber ausprobieren und ein Xiaomi zum ausprobieren der chinesischen Konkurrenz für westliche Mobiltelefonhersteller waren der perfekte Auftakt für den Tag. Und ganz besonders spannend: Den ganzen Tag lang konnten Interessierte die Hardware ausprobieren.

Im Anschluss daran widmete ich mich einer Session betitelt mit "Meetings verbessern" und es war interessant herauszufinden wie andere mit Fokussierungsproblemen in Meetings umgehen, denn wer kennt sie nicht ergebnislose Meetings oder Marathonmeetings.

Und nun: ein Mittagessen zugeschnitten auf Entwickler: Pizza und Mate. Der schiefe Turm von Pizza(schachteln) hat es übrigens nach dem Foto nicht mehr lange ausgehalten.

Nach dem Mittag wieder ganz spannend für mich das Thema Open Source Compliance. Wie verhindere ich Copyleft-Effekte im Business Umfeld, welche Lizenzen sind verwendbar, wenn man Business-Anwendungen schreibt, die nicht OpenSource sein dürfen. Es gab viele Tipps, wie man gut durch den Lizenzdschungel kommt und mit vielen praktischen Tipps aus dem juritischen Alltag.

Die letzte themenbezogene Session war für mich Crowdfunding für mobile Apps und hat für mich etwas Licht ins Dickicht gebracht. Dachte ich bisher immer an Crowdinvesting und was es dabei alles zu beachten gibt, so bietet Crowdfunding eine ganz andere Perspektive. Und ganz wichtig dabei auch das Herangehen an so einen Funding-Prozess aus Unternehmenssicht. Es war gut, dass so viele dabei waren, die direkt aus der Praxis mitreden konnten, weil sie den Prozess bei Unternehmen schon begleitet haben und wussten was genau relevante Punkte sind.

Für alle, die sich etwas näher mit einem der Themen an denen ich teilgenommen hab oder auch an anderen beschäftigen wollen: unter http://lanyrd.com/cxdcb findet sich ein Großteil der Präsentationen und Unterlagen, die Grundlage für die Sessions waren.

Zu vielen Zeitpunkten wäre ich gern in mehreren Sessions parallel gewesen, einfach weil es so interessante Themen gab.

Ich kann diese Konferenz einfach nur jedem empfehlen, der auch nur im entferntesten etwas mit Mobile zu tun hat.

Noch einmal vielen Dank und im nächsten Jahr bin ich gern wieder mit dabei.

Rückblick auf das MobileCamp 2014 - der Samstag

Ein spannendes Wochenende geht vorbei und nach einiger Abstinenz vom Bloggen möchte ich genau das zum Anlass für einen Blogpost nehmen. Auch wenn im Vorfeld viele Teilnehmer unterschiedliche Angaben dazu gemacht haben - ja, es ist inzwischen schon das 6. MobileCamp, wie man dann unschwer am Tshirt erkennen konnte. Und mit einem Besucherrekord von knapp 300 Teilnehmern von denen überraschenderweise noch immer die Mehrzahl direkt aus Dresden und Ostsachsen kam, konnten Maßstäbe gesetzt werden. 

Thematisch richtet sich dieses Barcamp sowohl an Entwickler als auch an Designer, Grafiker, Rechtsexperten, Projektmanager, Vertriebler - eben alle, die sich mit der Thematik Mobile auseinandersetzen. Trotzdem war der Anteil an Nicht-Entwicklern relativ gering.

Für mich war es das dritte Mal, dass ich beim MobileCamp dabei war und was soll ich sagen: es hat sich gelohnt. An dieser Stelle vielen Dank an die Veranstalter und die anderen Sponsoren die die Veranstaltung, die das Barcamp erst möglich machen. Und wer sich wegen der Formulierung fragt: Ja,dieses Jahr waren wir mit unter den Sponsoren.

Nun aber zum spannenden Teil....

Tag 1
Der Tag beginnt mit einer großen Vorstellungsrunde (wer bin ich, was mache ich und warum bin ich hier), was bei der Anzahl der Teilnehmer schon mal eine ganze Weile in Anspruch nimmt. Im Anschluss daran - wie bei jedem Barcamp - Sessionfindung und Raumplanung. Ganz interessant - dieses Jahr gab es sowohl am ersten als auch am zweiten Tag eine Keynote, was eigentlich barcampunüblich ist. 

Die Keynote am ersten Tag drehte sich rund um das Thema Firefox OS. Und viele Aspekte waren doch sehr bekannt von anderen Plattformen, denn alles ist eine App. Sehr beruhigend ist, dass die Mozilla-Entwicklergemeinde wohl aus den Fehlern anderer Hersteller gelernt haben und an "Features" wie Markieren und Copy&Paste schon in der ersten Version denken.

In der zweiten Session des Tages drehte es sich für mich rund um das Thema Gamification und es war für mich sehr spannend einen Einblick in die wissenschaftlichen Hintergründe zu bekommen und das auf einem gut verständlichen und High-Level Weg. 

Nach der Mittagspause in der es (typisch ostdeutsch) Soljanka aus der Gulaschkanone gab

Im Anschluss daran stand ich das erste Mal vor der Qual der Wahl: Unity und Game Erstellung oder App Marketing. Als jemand mit BWL-Herz, landete ich in der App-Marketing Session die anhand eines aktuellen Beispiels und damit natürlich auch mit etwas Eigenmarketing der Session-Ownerin sehr interessante Aspekte aufzeigte.

Im Anschluss daran schaffte es Torsten mit einer Session zum Thema Psychologie die Hälfte der Teilnehmer in seinen Bann zu ziehen. Da ich vom  letzten den Developer Open Space aber schon einen Eindruck gewinnen konnte was er da erzählen wird, zog es mich eher zu einer Session die sich thematisch mit rechtlichen Neuerungen im eCommerce beschäftigt. Auch wenn ich aktuell noch keinen Onlineshop betreue und es mich deswegen nur am Rande betrifft, war es doch sehr spannend, zu diskutieren, was da ab Mitte Juni ansteht und wie Shopbetreiber damit umgehen müssen.

Die vorletzte Session des Tages war für mich zwar interessant, aber ohne direkte Anwendungsmöglichkeit: Haitech - wie steuere ich die Fernbedienung eines Spielzeugs fern, um schlechtes UX des Spieleherstellers zu umgehen. Es war spannend, was man mit ein paar Transistoren und Widerständen und ein klein wenig Programmierung erreichen kann, aber ich habe gemerkt dass ich bisher doch zu selten einen Lötkolben in der Hand hatte als dass ich das direkt machen würde. Glücklicherweise sind genug Ingenieure in meinem Bekanntenkreis.

17 Uhr - die letzte Session des Tages beginnt. Für mich Gamification Teil 2 - dieses mal ein Deep Dive in die wissenschaftlichen Ansätze - sehr sehr spannend, jedoch in Anbetracht der Uhrzeit hatte ich doch gelegentlich Schwierigkeiten, dem Ganzen mit "hier ist noch eine Grafik, die zeigt folgendes" und "ich hab hier eine Tabelle in der wird dargestellt, dass ... " zu folgen.

Den krönenden Abschluss des Tages gab es wie jedes Jahr im Citybeach Dresden und auch wie jedes Jahr bei Regen - Traditionen soll man beibehalten.

InvalidOperationException in WebBrowserTask.Show

In Windows Phone 7 hat man mit Hilfe der Launcher und Chooser die Möglichkeit, auf im System integrierte Funktionen zuzugreifen - sei es nun die Kamera, der Emailclient oder wie in meinem Fall der Browser. Meine Anwendung hat mehrere Buttons über die zu unterschiedlichen Webseiten weiternavigiert werden kann - hauptsächlich als weiterführende Information im Impressum.

Dafür hab ich abstrakt gesehen folgende zentrale Methode:

public static void OpenInWebBrowser(string url)
{
    var openBrowserTask = new WebBrowserTask { Uri = new Uri(url) };
    openBrowserTask.Show();
}

Diese Methode wird direkt in dem Command aufgerufen, das an dem Button hängt - alles kein Hexenwerk. Was mich nun wunderte: Gelegentlich bekam ich Error-Reports mit diesem Inhalt:

Navigation is not allowed when the task is not in the foreground. Error: -2147220990

Da stellte sich mir folgende Frage: Wie kann jemand den Button klicken, wenn die Anwendung nicht im Vordergrund ist?

Die Lösung ist ganz einfach: Der Benutzer hat kurz nacheinander auf den Button geklickt - sei es nun weil er so aufgeregt war, eine schöne Webseite zu sehen oder weil er gerade unterwegs auf einer holprigen Straße war - die Möglichkeiten sind da ja sehr vielfältig. Das ist möglich, weil der Launcher eine Weile braucht, um den Browser in den Vordergrund zu holen. Der Nutzer kann also dafür sorgen, dass der Click-Event zweimal ausgelöst wird - der zweite Event wird aber erst dann verarbeitet, wenn der erste WebBrowserTask den Browser geöffnet hat und damit die eigentliche Anwendung nicht mehr im Vordergrund ist. Und dann kommt es zu dieser Exception.

Wie geht man nun damit um? Es gibt mehrere Ansätze. Zum einen kann man natürlich mit Try-Catch-Blöcken arbeiten und genau diesen Exception-Typ abfangen. Da diese Exception bei dieser Methode bisher in der MSDN nicht dokumentiert ist, ist es etwas schwierig hier von allein auf die Idee zu kommen, diesen speziellen Typ abzufangen, aber nachher weiß man es immer besser. Zusätzlich könnte man noch den Click-Handler abhängen bzw. den Button oder das Command deaktivieren. 

Ich bin nicht der einzige, dem dieses Problem untergekommen ist - und Niko hat einen sehr ausführlichen Blogpost zum Thema geschrieben, der auch alle hier genannten Lösungsmöglichkeiten aufführt.