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".

Dazu gibt es zwei Optionen:

6.2 Unterklasse von Application

Eine Möglichkeit ist, eine Unterklasse der Klasse Application zu schreiben. Dann wird automatisch ein Objekt erzeugt, das global bezogen werden kann. Außerdem ist garantiert, dass es eben nur ein Objekt gibt. In diesem Objekt können Sie beliebige Daten in Instanzvariablen ablegen.

Ein Beispiel:

public class Data extends Application {
    private int counter = 0;

    public int getCounter() {
        return counter;
    }

    public void setCounter(int val) {
        counter = val;
    }
}

Sie müssen diese Klasse noch anmelden und zwar im sogenannten Manifest. Diese (XML-)Datei finden Sie unter

app/manifests/AndroidManifest.xml

Dort sehen Sie u.a.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="de.hsa.singletonapplication">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

Sie fügen jetzt im Tag <application> folgendes ein:

<application
    ...
    android:name="Data"
    ...>

Im Code können Sie wie folgt auf das Objekt zugreifen:

public class MainActivity extends AppCompatActivity {

    private Data data;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ...
        data = (Data)getApplicationContext();
        ...
    }
}

Jetzt können Sie über die Variable data und mit den entsprechenden Gettern und Settern Daten verwalten. Im Beispiel haben wir eine einfache int-Variable gespeichert, aber Sie können natürlich auch Listen, Arrays etc. speichern.

Der Nachteil dieser Methode ist, dass Sie nur genau eine solche Klasse anlegen können. Wenn Sie Ihre globalen Daten auf zwei oder mehr Klassen verteilen möchten, müssen Sie eigene sogenannte Singleton-Klassen anlegen. Das sehen wir uns im nächsten Abschnitt an.

6.3 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.3.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.3.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;
  }
}

Mit Hilfe der statischen Methode können wir von jeder beliebigen Klasse aus auf unsere 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.3.3 Datenzugriff

Jetzt fehlen noch Getter-Methoden für die Daten. Diese sind natürlich nicht statisch, denn diese gehören zu dem einen Objekt, das wir herstellen.

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 nutzen, um die Daten einzulesen oder hart einzukodieren, denn der Konstruktor wird ja bei der Herstellung der einen Instanz ausgeführt.

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

6.3.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.4 Ü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. Alternativ können Sie eine Unterklasse von Application erstellen.

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