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
- Sie in an andere Activity wechseln
- sich die Orientierung ändert (Portrait/Landscape)
- Sie in eine andere App wechseln und anschließend wieder zurückkehren
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:
- Sie schließen die App und öffnen sie wieder (siehe Modul Hilfe, wie man eine App schließt)
- Sie deinstallieren eine App, wollen aber die Daten behalten
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
- static: denn wir wollen genau eine Instanz für die gesamte Klasse ins Leben rufen
- final: denn diese eine Instanz wollen wir nie austauschen (gegen eine andere Instanz)
- private: damit man von außen nicht darauf zugreifen kann (muss mit
getInstance
geschehen)
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).
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.