Stand: 12.06.2018

6.1 Problemstellung

Wenn Sie Daten in Instanzvariablen einer Activity speichern (z.B. in MainActivity), dann werden diese Daten immer zurückgesetzt, wenn

Das bedeutet, dass Sie Mechanismen brauchen, um Daten beständiger zu speichern. Statt beständig sagt man in der Informatik auch persistent. Sie brauchen so etwas wie "globale Variablen".

Es gibt noch weitere Fälle, wo die Frage nach der Persistenz eine Rolle spielt:

Für diese beiden Fälle helfen Singletons nicht. Beim Schließen einer App verschwinden die Daten eines Singletons. Um Persistenz beim Schließen einer App zu erreichen, schauen Sie sich das Modul Daten II an.

6.2 Singleton

Die zweite Möglichkeit, Daten über die Activities hinweg persistent zu machen, ist es, ein Einzelobjekt zu erstellen, das nur einmal existiert und auf das man von überall zugreifen kann. Das nennt man in der Softwaretechnik auch das Singleton-Pattern (siehe z.B. Wikipedia)).

Vorteil dieser Lösung ist, dass man mehrere Singleton-Klassen für unterschiedliche globale Daten definieren kann.

Wir erzeugen zunächst eine neue Klasse, nennen wir sie Globals, wo wir unsere Daten (z.B. Liste von Adressen) speichern können.

public class Globals {

  // Beispiele für Daten...
  private List<String> listOfFoo = new ArrayList<>();
  private int meaningfulNumber = 0;

}

6.2.1 Instanziieren verhindern

Aber ist es sinnvoll, dass man mehrere Objekte erzeugen kann? Wir wollen ja nicht mehrere Listen von Adressen speichern. Das heißt wir wollen nur genau eine Instanz der Klasse Globals erzeugen. So etwas nennt man ein Singleton. Ein Singleon ist also eine Klasse, von der es nur eine und genau eine Instanz gibt. Wir können das sicherstellen, indem wir den Konstruktor auf private schalten.

public class Globals {

  private List<String> listOfFoo = new ArrayList<>();
  private int meaningfulNumber = 0;

  // Privater Konstruktor => Klasse kann nicht instanziiert werden
  private Globals() {
  }
}

6.2.2 Instanz speichern und bereit stellen

Aber wie bekommt man diese eine Instanz? Hier benötigen wir das Konzept der statischen Variable und der statischen Methode. Wir erinnern uns: eine statische Methode (wie z.B. die statische main-Methode) gehört nicht einem Objekt, sondern der Klasse. Genauso verhält es sich mit einer statischen Variablen (auch Klassenvariable genannt). Wir nutzen dies, um in einer Klassenvariable instance die eine Instanz der Klasse zu speichern und mit einer Klassenmethode getInstance() darauf zuzugreifen.

public class Globals {

  // Hier wird die eine Instanz hergestellt
  private static final Globals instance = new Globals();

  ...

  private Globals() {
  }

  public static Globals getInstance() {
      return instance;
  }
}

Die Instanzvariable instance ist

Mit Hilfe der statischen Methode getInstance können wir von jeder beliebigen Klasse aus auf unsere eine Instanz zugreifen mit:

Globals g = Globals.getInstance();

Sie kennen vielleicht aus anderen Kontexten statische Zugriffe, z.B. bei

int x = Integer.parseInt("45");

Oder zum Erhalt der Systemzeit

long time = System.currentTimeMillis();

Allen Fällen ist gemein, dass Sie einen zentralen Service anbieten möchten, der allen Klassen zur Verfügung steht.

6.2.3 Datenzugriff

Jetzt fehlen noch Getter-Methoden für die Daten, die in der einen Instanz von Globals gespeichert sind. Diese Daten sind natürlich nicht statisch, denn diese gehören zu dem einen Objekt, das wir herstellen. Insofern richten wir ganz "normale" Getter und/oder Setter ein (je nach Bedarf).

public class Globals {
  private static final Globals instance = new Globals();

  // Beispiele für Daten...
  private List<String> listOfFoo = new ArrayList<>();
  private int meaningfulNumber = 0;

  private Globals() {
  }

  public static Globals getInstance() {
      return instance;
  }

  public List<String> getListOfFoo() {
    return listOfFoo;
  }

  public int getMeaningfulNumber() {
      return meaningfulNumber;
  }
}

Den privaten Konstruktor kann man auch nutzen, um die initialen Daten einzulesen (von einer Datei oder aus dem Internet). Häufig werden hier auch Testdaten hart einkodiert. Der Konstruktor wird ja bei der Herstellung der einen Instanz initial ausgeführt und kann daher für beide Fälle benutzt werden.

Wir nutzen den Konstruktor zum Beispiel dazu, unsere Liste mit drei Test-Strings zu befüllen:

private Globals() {
  listOfFoo.add("Enno");
  listOfFoo.add("Lukas");
  listOfFoo.add("Anna");
}

6.2.4 Beispiel

Die eine Instanz von Globals ist dann in der Tat so etwas wie eine globale Variable. Hier ein Beispielzugriff von einem beliebigen Objekt aus, das in der App existiert. Dazu holt man sich die eine Instanz von Globals und ruft anschließend die entsprechende Gettermethode auf:

Globals g = Globals.getInstance();
int x = g.getMeaningfulNumber();

Oder kurz:

int x = Globals.getInstance().getMeaningfulNumber();

6.3 Übung

Zähler-App

Schreiben Sie eine Zähler-App, die man z.B. beim Einlass bei Konzerten verwenden kann, um die Besucherzahl zu erfassen. Die App besteht lediglich aus einem Textfeld und einem Button (einfaches LinearLayout mit Gewichtung und Margins).

Zähler-App

Im ersten Schritt verwenden Sie einfach eine Instanzvariable counter in der Klasse MainActivity. Wenn Sie in der laufenden App aber die Orientierung ändern, stellt sich der Wert auf Null!

Im zweiten Schritt führen Sie eine Singleton-Klasse ein und verlagern die Variable counter dort hin.

Tipp: Statt einer Setter-Methode, schreiben Sie direkt die Methode, die Sie brauchen, in diesem Fall increase, die die Variable um eins erhöht.