Aufgaben-App Teil 4: Liste

Aufgaben-App Teil 4: Liste

Heute entwickeln wir weiter an unserer Aufgaben-App. Falls du die ersten Teile noch nicht gesehen hast, schau sie dir unbedingt an, denn dieses Video baut darauf auf.

In diesem Video werden wir die Liste der Aufgaben darstellen und dafür sorgen, dass man Aufgaben auch abhaken, also erledigen, kann.

ViewHolder

Zunächst brauchen wir einen ViewHolder. Der hält alle Views, die zu einer einzelnen Aufgabe gehören. Das sorgt dafür, dass die Liste später ohne Ruckeln gescrollt werden kann. Denn es gibt eine wichtige Besonderheit bei Listen, die man kennen muss.

Da eine Liste sehr lang werden kann, würde sie sehr viel Speicherplatz benötigen, wenn immer neue Zeilen hinzukämen. Um das zu vermeiden, werden die Zeilen wiederverwendet. Es gibt also immer nur so viele Zeilen im Speicher, wie gerade auf dem Screen angezeigt werden. Sobald eine Zeile nach oben rausgescrollt wird, wird sie unten wiederverwendet. Das heißt die Daten der Zeile werden ausgetauscht, aber die Zeile wird an sich nicht neu erzeugt. Dadurch dass Speicherplatz gespart wird, ist die App schneller, und die Liste lässt sich ruckelfrei scrollen.

Ein ViewHolder hat also alle Views von genau so einer Zeile unserer Liste. In unserem Fall ist das lediglich eine Checkbox, die aus einem Viereck, in das man später ein Häkchen setzen kann, und einem Namen rechts neben dem Viereck, besteht.

Wir nennen unseren ViewHolder „TaskViewHolder“ und erben vom RecyclerView.ViewHolder, der von Android schon mitgebracht wird. Anschließend implementieren wir den Konstruktor. Im Konstruktor werden alle Views der Zeile gesucht und lokal in Variablen gespeichert.

Adapter

Als nächstes benötigen wir einen Adapter. Einen Adapter brauchen wir, um die Daten (unsere Aufgaben) in die Zeilen (ViewHolder) zu schreiben.

Wir nennen unseren Adapter „TaskListAdapter“ und erben vom RecyclerView.Adapter, der von Android schon mitgebracht wird. Da wir noch Methoden implementieren müssen, wird die Klassendefinition rot unterstrichen. Klicke auf die rote Glühbirne, die links erscheint, wenn du auf die rot unterstrichene Zeile hältst. Klicke auf „Implement methods“, so dass Android Studio die Methoden erzeugt, die wir noch implementieren müssen. Das sollten 3 Stück sein.

Als erstes müssen wir „onCreateViewHolder“ implementieren. Das ist die Methode, die den ViewHolder erzeugt und zurückgibt. Mit dem Schlüsselwort „new“ erzeugen wir ein neues Objekt der Klasse TaskViewHolder. Dazu müssen wir dem Konstruktor eine Instanz der View (also einer Zeile der Liste) übergeben. Diese View erhalten wir durch „inflaten“ der task_row.xml Datei. Inflaten bedeutet Objekte zu erzeugen anhand der Layout-Definition, die wir als XML-Datei haben. Hierfür nutzen wir den „LayoutInflater“.

LayoutInflater.from(parent.getContext()).inflate(R.layout.task_row, parent, false);

Das Ergebnis des inflate-Aufrufs ist die View, die wir dem TaskViewHolder im Konstruktor übergeben. Das kann man schrittweise machen, oder gleich als zusammenhängende Code-Zeile. Alles zusammen sieht dann so aus.

Als zweites müssen wir „onBindViewHolder“ implementieren. Das ist die Methode, die die Zeile mit Daten befüllt. Die Zeile, die zu befüllen ist, bekommen wir übergeben, das ist der ViewHolder. Außerdem erhalten wir die Position in der Liste, die gerade befüllt werden soll.

Wir brauchen den TaskManager, um anhand der Position die richtigen Daten, also die richtige Aufgabe, zu holen. Der Adapter muss also den TaskManager kennen. Am Besten übergeben wir dazu den TaskManager im Konstruktor des Adapters. Dieser Vorgang nennt sich „Dependency Injection“, weil eine Abhängigkeit (dependency) von außen injiziert wird (injection). Den TaskManager speichern wir lokal im Adapter.

Nun können wir den TaskManager benutzen, um die Aufgabe anhand der Listenposition zu holen. Den Text der Aufgabe setzen wir dann als Text der Checkbox. Außerdem sollten wir die Checkbox immer zurücksetzen, so dass sie nicht abgehakt ist. Das ist wichtig, weil die Checkbox ja wiederverwendet wird, wie ich oben geschrieben hatte.

Als dritte und letzte Methode im Adapter müssen wir „getItemCount“ implementieren. Diese Methode muss die Anzahl der Zeilen wiedergeben, die unsere Liste insgesamt hat. Das entspricht der Anzahl der Aufgaben. Wir benutzen wieder den TaskManager, denn der kennt die Anzahl der Aufgaben. Wir hatten ihm dazu extra eine Methode gegeben. Die rufen wir jetzt auf und geben die Anzahl der Aufgaben zurück.

MainActivity

Nun müssen wir die MainActivity anpassen, um der RecyclerView zu sagen, welchen Adapter sie benutzen soll.

Zunächst suchen wir die RecyclerView in unserer initViews-Methode und speichern sie uns lokal, so wie wir das mit den anderen Views auch machen. Dann setzen wir den Adapter, indem wir eine neue Instanz der TaskListAdapter-Klasse übergeben. Denk daran, dass der Konstruktor des Adapters den TaskManager braucht (dependency injection). Wichtig ist auch einen LayoutManager zu setzen, da eine Liste unterschiedlich dargestellt werden kann. Wir wollen die Liste als vertikal scrollbare Liste darstellen (und nicht etwa als Grid). Deshalb setzen wir einen LinearLayoutManager.

Als letztes müssen wir dafür sorgen, dass der Adapter weiß, wenn eine neue Aufgabe hinzukommt. Das ist wichtig, denn genau in diesem Moment soll die Liste in der App aktualisiert werden. Der Adapter muss also neu angestoßen werden, wenn sich die Aufgaben ändern bzw. eine neue hinzukommt. Das erreichen wir, indem wir diese Zeile in der Methode „onSendButtonClick“ hinzufügen, nachdem wir die neue Aufgabe im TaskManager gespeichert haben.

recyclerView.getAdapter().notifyItemInserted(taskManager.getTaskCount() - 1);

Das heißt, wir nehmen den Adapter (aus der RecyclerView) und rufen „notifyItemInserted“ auf. Dieser Aufruf benötigt die Position, an der die neue Aufgabe hinzugekommen ist. Die neue Aufgabe ist an der letzten Position hinzugekommen. Die letzte Position entspricht der Anzahl aller Aufgaben minus 1. Wenn wir 3 Aufgaben haben, ist die letzte Position die Nummer 2, weil wir mit 0 anfangen zu zählen. Die erste Aufgabe ist an Position 0, die zweite Aufgabe an Position 1 und die dritte Aufgabe an Position 2. Daher lautet die Übergabe „taskManager.getTaskCount() – 1“.

Der erste Start

Wenn wir jetzt versuchen die App zu starten, stürzt sie ab. Das passiert, weil wir noch keine Aufgaben gespeichert haben, aber versuchen die Anzahl der Aufgaben zu ermitteln. Beim Zählen der Aufgaben rufen wir im TaskManager die Methode size() auf, was nicht funktioniert, weil die Liste gar nicht existiert.

Um diesen Fehler zu beheben, übergeben wir in der Methode loadTaskList im TaskManager ein zweites Argument, ein so genanntes Fallback- bzw. Default-Argument. Weil der Hawk die Aufgaben-Liste nicht aus dem Speicher laden kann, denn sie existiert ja noch gar nicht im Speicher, würde er „nichts“ (also NULL) als taskList speichern. Da wir auf NULL nicht zählen können, soll der Hawk in diesem Fall eine leere Liste erzeugen. Das erreichen wir, in dem wir „new ArrayList()“ als Fallback- bzw. Default-Argument übergeben.

Aufgaben abhaken

Die App läuft. Wir können Aufgaben hinzufügen. Die Aufgaben erscheinen in der Liste untereinander. Wunderbar!

Wenn wir jetzt Aufgaben abhaken, erscheint das Häkchen in der jeweiligen Checkbox. Auf Dauer ist das aber keine gute Lösung, denn unsere Aufgabenliste würde immer länger werden und oben immer die alten Aufgaben anzeigen, die wir ja schon längst erledigt haben. Besser wäre es, wenn die Aufgaben automatisch verschwinden, wenn wir sie abhaken. So bleibt die Liste schön übersichtlich.

Dazu passen wir den Adapter noch einmal an, und zwar in der Methode „onBindViewHolder“. Weil wir etwas tun wollen, wenn der Nutzer die Aufgabe abhakt, setzen wir einen „OnCheckedChangeListener“ auf die Checkbox und rufen eine neue Methode auf, die wir „onCheckedChange“ nennen. Ein solcher Methodenaufruf wird übrigens „Lambda“ genannt. Das kennen wir schon aus den vorigen Videos.

holder.checkBox.setOnCheckedChangeListener((buttonView, isChecked) -> onCheckedChange(isChecked, task, holder.getLayoutPosition()));

Die „onCheckedChange“-Methode sieht dann so aus:

Wir prüfen, ob die Checkbox vom Nutzer abgehakt wurde. Falls ja, entfernen wir die Aufgabe aus dem TaskManager. Da sich unsere Aufgaben verändert haben und die Liste in der App aktualisiert werden muss, müssen wir dem Adapter wieder Bescheid geben, ihn sozusagen nochmal anstoßen. Dafür rufen wir wieder so eine notify-Methode auf, diesmal die „notifyItemRemoved“.

Fertig!

Quellcode herunterladen

Wenn du möchtest, kannst du dir den kompletten Quellcode der App herunterladen. Das kann dir helfen, falls du beim Nachbauen nicht weiterkommst. Klicke dazu hier.