ListView aktualisiert nicht

  • 10 Antworten
  • Letztes Antwortdatum
T

Tom299

Stamm-User
122
Hallo Leute,

nach ca. 2 Jahren Pause bin ich wieder bei Android gelandet. Ich hab gerade ein triviales Programm, was aber nicht so will, wie ich das möchte. Hab sogar Code aus meinen alten Programmen zum Vergleich genommen. Dort hat es damals so funktioniert, und jetzt irgendwie im neuen Code nicht mehr.

Folgendes Problem:
Ich initialisiere meinen eigenen ArrayAdapter und setze ihn an die ListView. Anfangs ist die Liste natürlich leer. Dann drück ich den Button zum Aktualisieren, es wird eine XML-Datei aus dem Inet gelesen und die Liste wird dadurch gefüllt. Leider wird die ListView aber nicht aktualisiert, und ich weiß nicht, woran es hängt. statusAdapter.notifyDataSetChanged() sollte eigentlich reichen, mehr stand damals im alten Code an dieser Stelle auch nicht und es hat funktioniert ...

Nur wenn ich den Adapter nochmal neu nach dem Lesen initialisiere und wieder neu an die ListView hänge, passiert etwas. Aber das kanns doch nicht sein, ging doch im alten Code auch. Fehlt mir irgendein Setting?

Das nächste Problem hängt gleich damit zusammen:
Mein ArrayAdapter wird im 1. Durchlauf (1. Row) initialisiert und die 3 TextViews sind vorhanden. Beim 2. Durchlauf, wo die View nur noch gecastet wird, weil ja schon vorhanden, sind die TextView aber NULL bzw. es knallt beim Cast der 1. TextView. Auch hier weiß ich im Moment keinen Rat :-(

Hier meine Listings:
MainActivity:
Code:
package de.test.activity;

import java.util.ArrayList;

import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup.LayoutParams;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.ListView;
import de.ianeo.servermonitor.R;
import de.ianeo.servermonitor.adapter.StatusArrayAdapter;
import de.ianeo.servermonitor.model.ServerCheck;
import de.ianeo.servermonitor.tools.XMLReader;


public class MainActivity extends Activity {

	private ArrayList<ServerCheck> serverChecks = new ArrayList<ServerCheck>(0);
	private StatusArrayAdapter statusAdapter;
	
	private Button btnRefresh;
	
	private ListView listViewStatus;
	
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		
		LinearLayout mainLayout = (LinearLayout)findViewById(R.id.activity_main_linear_layout);
		
		listViewStatus = new ListView(this);
		listViewStatus.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
		
//		ServerCheck sc = new ServerCheck();
//		sc.setCustomer("Test1");
//		sc.setChecktype("HTML");
//		sc.setInfo("OK");
//		serverChecks.add(sc);
//		sc = new ServerCheck();
//		sc.setCustomer("Test2");
//		sc.setChecktype("ASPX");
//		sc.setInfo("ERROR");
//		serverChecks.add(sc);
		
		statusAdapter = new StatusArrayAdapter(this, R.layout.table_server_status, serverChecks);
		listViewStatus.setAdapter(statusAdapter);
		listViewStatus.setOnItemClickListener(new OnItemClickListener() {
			@Override
			public void onItemClick(AdapterView<?> av, View v, int index, long arg) {
				ServerCheck serverCheck = serverChecks.get(index);
				if (serverCheck.isExpanded()) {
					// zuklappen
					serverCheck.setExpanded(false);
				}
				else {
					// aufklappen
					serverCheck.setExpanded(true);
				}
				
				statusAdapter.notifyDataSetChanged();
			}
		});
		
		mainLayout.addView(listViewStatus);
		
		btnRefresh = (Button)mainLayout.findViewById(R.id.main_btnRefresh);
		btnRefresh.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View arg0) {
				updateServerStatus();
			}
		});
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}

	private void updateServerStatus() {
		Thread thStatusUpdate = new Thread( new Runnable() {
			@Override
			public void run() {
				final XMLReader xmlReader = new XMLReader("http://test.de/status.php");
				runOnUiThread(new Runnable() {
					@Override
					public void run() {
						serverChecks = xmlReader.getServerChecks();
						statusAdapter.notifyDataSetChanged();
						//statusAdapter = new StatusArrayAdapter(MainActivity.this, R.layout.table_server_status, serverChecks);
						//listViewStatus.setAdapter(statusAdapter);
					}
				});
			}
		});
		thStatusUpdate.start();
	}
}

Mein ArrayAdapter:
Code:
package de.test.adapter;

import java.util.List;

import de.ianeo.servermonitor.R;
import de.ianeo.servermonitor.model.ServerCheck;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TableLayout;
import android.widget.TextView;


public class StatusArrayAdapter extends ArrayAdapter<ServerCheck> {

	private int resource;
	
	private TableLayout statusView;
	
	public StatusArrayAdapter(Context context, int resourceId, List<ServerCheck> items) {
		super(context, resourceId, items);
		this.resource = resourceId;
	}
	
	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		ServerCheck serverCheck = getItem(position);
		
		if (statusView == null) {
			statusView = new TableLayout(getContext());
			LayoutInflater inflater = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
			inflater.inflate(resource, statusView, true);
		}
		else {
			statusView = (TableLayout)convertView;
		}
		
		TextView tvCustomer = (TextView)statusView.findViewById(R.id.tv_table_server_status_customer);
		TextView tvCheckType = (TextView)statusView.findViewById(R.id.tv_table_server_status_checktype);
		TextView tvInfo = (TextView)statusView.findViewById(R.id.tv_table_server_status_info);
		
		if (serverCheck.getCustomer() != null) {
			tvCustomer.setText(serverCheck.getCustomer());
		}
		
		if (serverCheck.getChecktype() != null) {
			tvCheckType.setText(serverCheck.getChecktype());
		}
		
		if (serverCheck.getInfo() != null) {
			tvInfo.setText(serverCheck.getInfo());
		}
		
		return statusView;
	}
}

Manifest:
Code:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="de.test"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="10" android:targetSdkVersion="19" />

    <uses-permission android:name="android.permission.INTERNET" />
    
    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="de.ianeo.servermonitor.activity.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

</manifest>
 
mach ich doch eigentlich:

Code:
runOnUiThread(new Runnable() {
   @Override
   public void run() {
	serverChecks = xmlReader.getServerChecks();
	statusAdapter.notifyDataSetChanged();
   }
});
 
Huch, hatte ich übersehen. Bist du sicher, dass der Code aufgerufen wird?

Aber den Network IO sieht übrigens auch verdächtig aus, wo wird denn da der XML Code geparst?
 
Der XMLReader macht ne Connection, liest die Daten, erzeugt mir meine fertigen Objekte und gibt sie als Liste zurück. Den Code hab ich vorher schon in Java programmiert und das funktioniert auch unter Android. Die Liste ist ja gefüllt.

Beim debuggen ist mir jetzt aufgefallen, daß der ArrayAdapter doppelt durchlaufen wird. Beim 2. Durchlauf ist die 2. TextView nicht findbar über findViewByID ... warum auch immer ... das notifyDataSetChanged() funktioniert aber nach wie vor NICHT.

Ich versuch es jetzt gleich mal mit einem Handler anstatt dem runOnUIThread. Aktuell benutze ich Android 4.4.2 vielleicht liegts ja auch daran?
 
Wenn man im Inflator den falschen parent setzt passieren oft komsiche Dinge. Ich baue den Inflater im Adapter immer etwas anders auf:

Code:
inflator = LayoutInflater.from(context);
...
itemView = inflator.inflate( R.layout.list_item, null );

Vielleicht klappt das ja besser.
 
Am Inflator scheint es nicht zu liegen. Ich hab testweise mal die NULL-Abfrage auf die view rausgenommen und inflate jetzt bei JEDEM aufruf und siehe da, es gibt kein NULL-Pointer mehr und die ListView wird angezeigt.

D.h. beim cast mit der vorhandenen View läuft was schief, und das ist unverständlich. Den Code hatte ich in alten Projekten in jedem Adapter gleich und es gab nie Probleme.

Kann das vielleicht wirklich an der 4.4.2 liegen?
 
Versuch mal (testhalber), statt

Code:
inflater.inflate(resource, statusView, true);

den Parameter attachToRoot auf false zu setzen:

Code:
inflater.inflate(resource, statusView, [B]false[/B]);

Ich habe mal irgendwas darüber gelesen, kann mich aber nicht mehr genau daran erinnern. Dein Problem klingt aber ein wenig danach, dass beim zweiten Durchlauf die convertView nicht die View ist, für die du sie hälst. Dadurch findest du mit findViewById() dann die TextViews nicht. Und mein Gedächtnis sagt mir, dass es an attachToRoot liegt. Ein Versuch wärs mal Wert :winki:
 
Ich mache es bis jetzt immer so:
Code:
 LayoutInflater inflater = activity.getLayoutInflater();
        View view = inflater.inflate(R.layout.simple_geofance_item, null);
owbowl mir
inflator = LayoutInflater.from(context);
die schönere möglichkeit zu sein scheint, die ich bis gerade aber nicht kannte.

lg. Dagobert
 
Hi Leute,

erst mal Danke für die Antworten. Leider hat das setzen vom Parent auf false oder true keine Auswirkungen bei mir gezeigt.

Wo ich aber weitergekommen bin, ist das Problem mit dem notifyDataSetChanged() bzw. daß da nichts passiert und ich den Adapter neu erstellen mußte usw.

In meinem alten Projekt reichte es, wenn ich die Liste geändert habe, die im Adapter drin steckt und dann ein statusAdapter.notifyDataSetChanged() ausführte.

Mit der "Liste" meine ich in diesem Fall die serverChecks:
Code:
private ArrayList<ServerCheck> serverChecks = new ArrayList<ServerCheck>(0);

statusAdapter = new StatusArrayAdapter(this, R.layout.table_server_status2, serverChecks);

Scheinbar bekommt der Adapter aber die Änderung der Liste nicht mehr mit, warum auch immer.

Wenn ich jetzt aber auf dem Adapter arbeite, dann bekommt er die Änderungen mit:

Code:
statusAdapter.clear();
for (ServerCheck check : serverChecks) {
	statusAdapter.add(check);
}
statusAdapter.notifyDataSetChanged();

also brauch ich den Adapter nach dem Aktualisieren der Liste nicht mehr neu zu erstellen und wieder an die ListView zu setzen.

Allerdings muß man noch aufpassen: addAll vom Adapter funktioniert erst ab 3.0 oder so. Wenn man z.B. nen 2.3 EMU nimmt, dann gibts bei addAll einen Absturz (no such method). Ist zwar Blöd, aber wenn man's weiß halt 2 Zeilen Code mehr und intern wird wohl kaum was anderes passieren beim addAll.


Ich werde mich jetzt nochmal an getView testen, vielleicht find ich ja noch eine Lösung. Bei Vogella gibts noch ein Beispiel mit ViewHolder-Klasse, das werde ich auch mal testen:
Android ListView - Tutorial

:thumbup:

Der ursprüngliche Beitrag von 10:56 Uhr wurde um 11:02 Uhr ergänzt:

Oh Mann, ich hab den Fehler gefunden ... Anfängerfehler *lol*

Ich hatte in der If-Abfrage nicht die converView auf NULL getestet sondern meine statusView ... das konnte ja nicht klappen. Ist mir jetzt erst aufgefallen, nachdem ich nen alten Adapter zum Vergleich genommen habe :rolleyes2:
 
Hihi uns ist es auch nicht aufgefallen :D
 
Zurück
Oben Unten