Android - Listen
Das Problem
In einer App kommt es häufig vor, das eine Liste von Daten dargestellt werden soll. Dies ist in einer Android-Anwendung viel komplizierter als man es im ersten Moment für nötig hält. Dies hängt aber damit zusammen, dass die Listendarstellung viele Probleme löst, die auf den ersten Blick nicht so offensichtlich sind.
- Das Darstellen langer Listen oder Listen aus einer fernen bzw. langsamen Quelle sollte die Anwendung nicht blockieren
- Eine einmal geholte Liste sollte im Speicher gehalten werden, um langwieriges Besorgen der Liste möglichst zu vermeiden.
- Änderungen der Listendaten sollte die Liste automatisch aktualisieren
Die Lösung
Um dies zu erreichen benutzt Android einen Loader. Dieser läuft in einem eigenen Thread und blockiert dadurch nicht die Anwendung, während er die Daten besorgt. Der Loader wird gekoppelt mit
- der Listendarstellung, an der er die Daten zur Darstellung weiterreicht.
- der Datenquelle, von der die Daten besorgt werden.
- einem Broadcast-Receiver, der meldet, ob es Datenänderungen an der Quelle gegeben hat.
Anlegen eines neuen Projekts
Um die Lösung zu veranschaulichen lege ich ein neues Projekt namens
List Example an. Als Minimun SDK lege ich API 14 an, füge eine
Blank Activity namens
MainActivity hinzu.
Die Listendarstellung
Um die Liste darzustellen ändere ich nun die MainActivity in eine
ListActivity und ändere die onCreate Methode so, dass Sie die Liste füttert. Das Ergebnis sieht dann so aus
....
public class MainActivity extends ListActivity {
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,R.layout.list_item,R.id.text);
setListAdapter(adapter);
adapter.add("Eintrag 1");
adapter.add("Eintrag 2");
}
...
Hier wird ein
ArrayAdapter definiert. Dieser wird mit einem Layout verknüpft, das das Aussehen der einzelnen Einträge definiert. Dieser Adapter wird dann der ListActivity übergeben. Die letzten beiden Zeilen zeigen, dass alles, was ab jetzt dem ArrayAdapter übergeben, in der Liste dargestellt wird. Das Layout der Listeneinträge ist in der Datei res/layout/list_item.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
Der Loader
Was wir bis jetzt also erreicht haben ist, dass wir eine Listendarstellung haben, die mit einem Adapter gekoppelt ist. Als nächstes brauchen wir also einen Loader, der den Adapter befüttert. Diesen Loader müssen wir dann mit dem Adapter koppeln.
public class DataLoader extends AsyncTaskLoader<List<String>> {
private List<String> data;
public DataLoader(Context context) {
super(context);
}
@Override
public List<String> loadInBackground() {
Log.i("DataLoader", "loadInBackground called");
List<String> data = new ArrayList<String>();
for(int i=0;i<20;i++) {
data.add("Eintrag " + i);
}
return data;
}
@Override
protected void onStartLoading() {
Log.i("DataLoader", "onStartLoading() called!");
if (data != null) {
Log.i("DataLoader", "Delivering previously loaded data");
deliverResult(data);
}
if (data == null) {
Log.i("DataLoader", "The current data is data is null. force load");
forceLoad();
}
}
}
Der Loader muss ein Kind der Klasse
Loader sein. Ich erweitere hier die Klasse
AsyncTaskLoader, die ein direktes Kind der Klasse Loader ist. Der Loader hat wie erwähnt viele Aufgaben. Damit die Darstellung der Liste funktioniert genügt wie oben gesehen die Implementierung der der Funktionen
loadInBackground und
onStartLoading. Die Funktion
onStartLoading überprüft, ob die Liste bereits befüllt ist. Wenn ja liefert Sie das Ergebnis aus, wenn nein stösst sie das Laden der Liste an. In der Funktion
loadInBackground wird die eigentliche Arbeit gemacht und die Daten von der Quelle geholt. In unserem Beispiel wird keine Datenquelle angezapft, sondern die Liste selbst produziert. Dies soll hier mal fürs erste genügen, um zu zeigen, dass Daten, die hier geholt werden, auch tatsächlich angezeigt werden. Die Ankoppelung der Quelle kommt später. Das Koppeln des Loaders an eine Activity ist recht einfach, da jede Activity und jedes Fragment bereits einen
LoaderManager hat. Die Ankopplung erfolgt also einfach durch die Implementierung des Interface
LoaderManager.LoaderCallbacks.html. Nach der Implementierung des Interface kann man durch die Funktion
initLoader dem LoaderManager Bescheid geben, dass es was zu tun gibt. Was das ist, erfährt er aus den Funktionen, die aufgrund des Interface implementiert wurden Das Android-Studio hilft einem freundlicherweise beim Implementieren und so sieht dann vorerst das Ergebnis aus.
...
public class MainActivity extends ListActivity implements LoaderManager.LoaderCallbacks<List<String>> {
private ArrayAdapter<String> adapter;
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
adapter = new ArrayAdapter<String>(this,R.layout.list_item,R.id.text);
setListAdapter(adapter);
getLoaderManager().initLoader(1, null, this);
}
......
@Override
public Loader<List<String>> onCreateLoader(int id, Bundle args) {
Log.i("MainActivity", "onCreateLoader called");
return new DataLoader(this);
}
@Override
public void onLoadFinished(Loader<List<String>> loader, List<String> data) {
Log.i("MainActivity", "onLoadFinished called");
adapter.addAll(data);
}
@Override
public void onLoaderReset(Loader loader) {
Log.i("MainActivity","onLoaderReset called");
adapter.clear();
}
}