code it

Martins Tech Blog

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.