Intent put Extra übergibt falsche Daten

  • Antworten:12
Viktor R.
  • Forum-Beiträge: 14

07.01.2012, 18:08:09 via Website

Hallo zusammen,

ich sitze gerade an einem Formular (Test). Ein User trägt in zwei EditText Felder "Artikel" und "Meng"e ein, daraus entsteht ein Einkaufszettel.
Artikel und Menge werden einer ArrayList hinzugefügt.
Der User kann über den Button b1 die Daten auf den Einkaufszettel speichern und danach weitere Elemente hinzufügen. Über den Button b2 wird eine neue Activity (EinkaufszettelAnzeigen) aufgerufen, die über eine ListView seine Eingaben darstellt. Wenn alles passt, wird der Zettel gespeichert, ansonsten verworfen.
Mein Problem dabei ist, dass der letzte Eintrag (Menge) immer doppelt übertragen wird. Ich finde den Fehler nicht, da auch im Debugger bis zum Intent alles korrekt zu sein scheint.
Bsp. Artikel "a" Menge "1" wird in der neuen Activity als a,1,1 ausgegeben, obwohl der Vergleich (Methode, die den letzten Eintrag des Users abfängt und prüft, ob sich dieser Wert bereits in der ArrayList befindet) true ergibt.

Anbei poste ich meinen Code
(sorry für die Länge, unwichtiges hab ich so weit es geht entfernt)

[code]

public class Test extends Activity{

private static EditText Artikel=null;
private static EditText Menge=null;

ArrayList<String> einkauf=new ArrayList<String>();

String artikel=null;
String menge=null;

public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.test);
setTitle("Einkaufszettel anlegen");

// wenn ein rekursiver Aufruf stattfindet
Bundle bundle=getIntent().getExtras();
if(bundle==null){

}else{
String [] array=bundle.getStringArray("array");
int l=array.length;
for(int i=0;i<l;){
einkauf.add(array[i]);
i++;
}}

Artikel=(EditText)findViewById(R.id.eintragenArtikel);
Menge=(EditText)findViewById(R.id.eintragenMenge);
Artikel.setInputType(1);
Menge.setInputType(2);

Button weiter=(Button)findViewById(R.id.b1);
Button speichern=(Button)findViewById(R.id.b2);
Button abbrechen=(Button)findViewById(R.id.b3);

weiter.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
addArtikel();
}
});
speichern.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
speicherEinkaufzettel();

}
});
abbrechen.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Intent in=new Intent();
in.setClass(Test.this, Artikel_anzeigen.class);
startActivity(in);
}
});
Artikel.setOnKeyListener(new View.OnKeyListener() {
public boolean onKey(View v, int keyCode, KeyEvent event) {
event.getDisplayLabel();
if(keyCode==66 & event.getAction()==KeyEvent.ACTION_DOWN){
getArtikel();
return true;
}
return false;
}
});
Menge.setOnKeyListener(new View.OnKeyListener() {
public boolean onKey(View v, int keyCode, KeyEvent event) {
event.getKeyCode();
if(keyCode==66 & event.getAction()==KeyEvent.ACTION_DOWN){
getMenge();
return true;
}
return false;
}
});
}
public void getArtikel(){
artikel=Artikel.getText().toString();
einkauf.add(artikel);
}
public void getMenge(){
menge=Menge.getText().toString();
einkauf.add(menge);
}

public void speicherEinkaufzettel(){
int a=einkauf.size();
Intent in=new Intent();
in.setClass(Test.this, EinkaufszettelAnzeigen.class);

if(Vergleich()==true){
String []ar =new String[a];

einkauf.toArray(ar);
in.putExtra("array",ar);
startActivity(in);
}
String []ar =new String[a+1];
einkauf.add(menge);
einkauf.toArray(ar);
in.putExtra("array",ar);
startActivity(in);
}

public void addArtikel(){
int a=einkauf.size();
Intent in=new Intent();
in.setClass(Test.this, Test.class);

if(Vergleich()==true){
String []ar =new String[a];
einkauf.toArray(ar);
in.putExtra("array",ar);
startActivity(in);
}
String []ar =new String[a+1];
einkauf.add(menge);
einkauf.toArray(ar);
in.putExtra("array",ar);
startActivity(in);
}
public boolean Vergleich(){
String vergleich=einkauf.get(einkauf.size()-1);
menge=Menge.getText().toString();
if (vergleich.equals(menge)){
return true;
}
return false;
}
}

// Die zweite Activity
public class EinkaufszettelAnzeigen extends Activity{

public void onCreate(Bundle SavedInstanceState){
super.onCreate(SavedInstanceState);
setContentView(R.layout.einkaufzettel_anzeigen);

Bundle bundle=getIntent().getExtras();
String [] array=bundle.getStringArray("array");

ListAdapter adapter=new ArrayAdapter<String>(this, android.R.layout.simple_list_item_multiple_choice, array);
ListView einkauf=(ListView)findViewById(R.id.added_food);
einkauf.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
einkauf.setAdapter(adapter);
}
[/code]

vielen Dank im Voraus

Viktor

Antworten
Viktor R.
  • Forum-Beiträge: 14

08.01.2012, 02:06:01 via Website

Hallo zusammen,

ich bin noch mal Schritt für Schritt mit dem Debugger durch den Code gegangen und habe folgendes festgestellt:
Im Formular sind die Daten der ArrayList korrekt, zwei Eingaben bewirken zwei Elemente. Bsp.(a,2)
Seltsamerweise wird die neue Activity zwei mal durchlaufen. Daraufhin habe ich statt eines
OnClickListeners ein OnTouchListener verwendet und das gleiche Ergebnis.
Wie bekomme ich es hin, dass bei einem Event (Touch,Click etc.) die nachfolgende Anweisung nur ein Mal ausgeführt wird?

gruß

Viktor

Antworten
Felix
  • Forum-Beiträge: 259

08.01.2012, 11:10:04 via Website

Tach!

Der gezeigte Code läuft nicht. Es scheitert beim Vergleich(). Beim erstmaligen Aufruf ist die ArrayList einkauf leer und ein Zugriff auf das Element -1 wirft eine ArrayIndexOutOfBoundsException. Mein erster Versuch, diese zu umgehen war ein
1if (einkauf.size() == 0)
2 return false;
am Anfang einzufügen, doch das war nicht wirklich zielführend. Die Methode Vergleich() hat nämlich noch die wichtige Nebenwirkung, die globale Eigenschaft menge zu setzen. Solche Nebenwirkungen sind sehr ungünstig, weil sie unerwartet sind und auch zu untestbarem Code führen. Korrigier bitte den Code so, dass das Problemnachvollziehen nicht bereits an anderen Ursachen scheitert. (Die onKeyListener kannst du auch weglassen, die tragen zum Problem nichts bei.)

Weitere Anmerkungen: Es gibt einen Unterschied zwischen den Operatoren & und &&. Bei logischen Verknüpfungen solltest du && nehmen und nicht &.
Wenn du die Daten eingibst, ruft sich Test immer wieder selbst auf. Meines Erachtens füllt das nicht nur unnötig den Back Stack, es ist auch ansonsten unnötig. Du kannst doch in der Activity bleiben und nur die eingegebenen Daten sammeln und anschließend die Eingabefelder leeren.
Test ist auch ein ungünstig gewählter Namen, weil er mit junit.framework.Test kollidieren kann. Ich hatte das umgeschrieben zu TestActivity, weil die Haupt-Activity in meinem Test-Projekt bereits so benannt war und ein Umbenennen an mehreren Stellen Änderungen nach sich zieht (z.B. im Manifest). Test.this hat Eclipse nicht erkannt und zurecht unterkringelt, für Test.class aber stillschweigend ein import hinzugefügt.
if (x == true) … => wenn es wahr ist, dass x wahr ist … – Du siehst sicher an der Übertragung in natürliche Sprache, dass da was überflüssiges in dem Ausdruck steckt. Auch das
1if (x.equals(y))
2 return true;
3return false;
ist unnötig umständlich und lässt sich abkürzen zu: return x.equals(y);
"Artikel und Menge werden einer ArrayList hinzugefügt." Ja, welcher denn bitte? Muss ich mir das selbst erarbeiten? Ist es einkauf oder hast du noch eine weitere irgendwo versteckt, und welche Aufgabe haben die String-Arrays? Ich sehe nämlich nur, was du programmiert hast und nicht was du programmieren wolltest. Versuch möglichst exakt zu beschreiben, was dein Code so treibt/treiben soll. Der Typ in meinem Profilbild ist der Erklärbär. Vom Programmieren hat er keine Ahnung, kann aber logisch denken. Wenn du ihm zu erklären versuchst, was dein Programm macht, so dass er es verstehen kann, wird es verständlicher für diejenigen, die es nachvollziehen sollen und unter Umständen auch für dich selbst, so dass dir des Rätsels Lösung vielleicht sogar dabei schon einfällt. Denn für diese Erklärung kann es nämlich notwendig sein, dass du nochmal genau nachschauen musst, was du so fabriziert hast.

Antworten
Viktor R.
  • Forum-Beiträge: 14

11.01.2012, 03:14:15 via Website

Hallo Felix,

der Code war nicht gut durchdacht.
Den OnKey Listener hab ich auch rausgeschmissen, genauso wie den rekursiven Aufruf. Die Eingaben werden alle in der ArrayList einkauf gespeichert und dann an die nächste Activity übertragen.

Aber ich stehe jetzt vor einem anderen Problem.
in der Nachfolgenden Activity sollen alle Eingaben des Users (Artikel / Menge) in einer ListView angezeigt werden.
Ich habe dafür eine ListView verwendet, bestehend aus zwei TextViews und einer CheckBox pro Line. Die CheckBox hat den Status checked. Hat der User etwas falsch eingegeben, nimmt er den Haken raus und diese Zeile wird nicht in eine Datenbank geladen.

Mein Problem im Moment ist, dass weder für die CheckBox noch für die TextViews Listener einsetzen kann, da sofort eine Fehlermeldung kommt.
Ich habe ein xml File für die ListView und ein xml File für die Spaltensicht mit der Checkbox erstellt.
Zusätzlich habe ich einer seperaten Klasse einen BaseAdapter implementiert.

Sollte ich die Listener in der getView Methode des BaseAdapter definierten?
Das habe ich gemacht, mit dem Problem, dass ich nicht weiß, wie ich an die selektierten Items komme.

gruß

Viktor

Antworten
Felix
  • Forum-Beiträge: 259

11.01.2012, 12:56:58 via Website

Tach!

Mir scheint, du hast das System wie einen Shop mit Einkaufswagen umgesetzt. Statt Produkte zu wählen, hast du aber Eingaben aus Textfeldern, die du dem "Einkaufswagen" hinzufügst. Von dem gehst du vermutlich nach jedem Eintrag wieder zurück zur Eingabe-Activity. Wenn ich das richtig vermute – ich würde das ändern. Die Liste wäre meine Haupt-Activity, und über einen Button würde ich einen Dialog oder eine weitere Activity mit der Eingabemaske aufrufen. Und mit dem Notepad-Tutorial gäbe es dafür auch schon eine ausbaufähige Vorlage.

Mein Problem im Moment ist, dass weder für die CheckBox noch für die TextViews Listener einsetzen kann, da sofort eine Fehlermeldung kommt.

Dann hast du irgendwas falsch gemacht. Was es ist, kann ich der Aussage nicht entnehmen. Ich würde hier der ListView-Activity mit implements die benötigten Listener-Interfaces nebst implementierenden Methoden hinzufügen und im getView() des Listadapters den setOn…Listener()s die Activity als Parameter übergeben. Siehe Dev Guide – Input Events, Abschnitt Event Listeners, das zweite Code-Beispiel, nur dass du im ListAdapter nicht this übergeben kannst sondern einen im Konstruktor übergebenen Verweis auf die Activity (den brauchst du ja (mitunter) auch, wenn der Adapter den Context benötigt).

Sollte ich die Listener in der getView Methode des BaseAdapter definierten?
Das habe ich gemacht, mit dem Problem, dass ich nicht weiß, wie ich an die selektierten Items komme.

Wenn das Ereignis eintritt, bekommst du nur die auslösende View übergeben. Von der aus musst du dann irgendwie auf den zugehörigen Datensatz schließen. Wenn du keine weitere Zuordnungstabelle erstellen willst, kannst du jedem View über einen Tag beliebige Daten mitgeben. In deinem Fall die Information, zu welchem Datensatz er gehört.


Felix.

Antworten
Viktor R.
  • Forum-Beiträge: 14

12.01.2012, 01:24:11 via Website

Hallo Felix,

das mit dem Einkaufswagen geht in die richtige Richtung. Ich habe eine Activity "EinkaufszettelAnlegen". Es gibt zwei Eingabefelder, Artikel und Menge. Beide Felder in einer ArrayList gesammelt, bis der Button "speichern" gedrückt wird. Nach dem speichern rufe ich die Activity EinkaufszettelAnzeigen auf.
In dieser Activity wird der Einkaufszettel in einer ListView angezeigt. Der user prüft seine Eingabe und kann noch Artikel hinzufügen oder entfernen. Nach Klick auf den Button "beenden" wird dieser Einkaufszettel in der Datenbank geschrieben und beim nächsten Einkauf aufgerufen.
Mein Problem dabei ist folgendes:
Ich habe eine Activity mit einem "TwoElemenAdapter" extends BaseAdapter. Dazu habe ich ein Layout.xml bestehend aus Header, ListView und den Buttons am Fußenende. Die ListView wiederum bekommt ihr Layout einem weiteren xml file bestehend aus zwei TextViews und einer Checkbox.
Ich habe mich an deinen Tips versucht, weiß im Moment aber nicht wirklich weiter, wie ich die Position des Elementes bestimmen kann, welches der User durch die Checkbox abwählt. Durch die TextViews erhalte ich keine Position.

Anbei mein Adapter:

1public class TwoElementAdapter extends BaseAdapter{
2
3 private ArrayList<holeElemente> Elemente=new ArrayList<holeElemente>();
4 private Activity activity;
5 private String [] artikel;
6 private String [] menge;
7 private static LayoutInflater inf=null;
8
9 public TwoElementAdapter(Activity a,String [] artikel, String [] menge){
10 activity=a;
11 this.artikel=artikel;
12 this.menge=menge;
13 inf = (LayoutInflater)activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
14 }
15 public int getCount(){
16 return artikel.length;
17 }
18 public Object getItem(int position){
19 return position;
20 }
21 public long getItemId(int position){
22 return position;
23 }
24
25
26 public View getView(final int position, View v, ViewGroup parent){
27 View vi=v;
28
29 if(v==null)
30 vi=inf.inflate(R.layout.twoitems,null);
31
32
33
34 TextView text1=(TextView)vi.findViewById(R.id.var1);
35 TextView text2=(TextView)vi.findViewById(R.id.var2);
36 text1.setText(artikel[position]);
37 text2.setText(menge[position]);
38 final CheckBox check=(CheckBox)vi.findViewById(R.id.checkBox1);
39 check.setChecked(true);
40 check.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener(){
41 public void onCheckedChanged(CompoundButton bt, boolean arg1) {
42
43 holeElemente element=(holeElemente)check.getTag();
44 element.setSelected(arg1);
45 }
46
47 });
48
49 return vi;
50 }
51}

Und die Activity EinkaufszettelAnzeigen (
[code]
public class Einkaufszettel_Anzeigen extends Activity{

ListView einkaufszettel;
TwoElementAdapter adapterTwo;
Button speichern;
Button verwerfen;
CheckBox check;

String DB_name="WWS_APP";
String DB_table="Warengruppe";

final ArrayList<String> ListeArtikel=new ArrayList<String>();
final ArrayList<String> ListeMenge=new ArrayList<String>();
String [] artikelliste;
String [] mengenliste;
String [] ListeEingaben;
int indexArtikel=0;
int indexMenge=1;
int feld;

public void onCreate(Bundle icicle){
super.onCreate(icicle);

setContentView(R.layout.einkaufszettel_anzeigen);

einkaufszettel=(ListView)findViewById(R.id.einkaufszettel);
adapterTwo=new TwoElementAdapter(this,getArtikel(),getMenge());
einkaufszettel.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
einkaufszettel.setAdapter(adapterTwo);

check.setChecked(true);
}

public String[] getArtikel(){
getEingabe();
getFeld();
artikelliste=new String[feld/2];

for(int i=0;i<feld/2;){
artikelliste[i]=ListeEingaben[indexArtikel];
indexArtikel=indexArtikel+2;
i++;
}
return artikelliste;
}

public String[] getMenge(){
getEingabe();
getFeld();
mengenliste=new String[feld/2];

for(int i=0;i<feld/2;){
mengenliste[i]=ListeEingaben[indexMenge];
indexMenge=indexMenge+2;
i++;
}
return mengenliste;
}

public String[] getEingabe(){
Bundle bundle=getIntent().getExtras();
ListeEingaben=bundle.getStringArray("array");
return ListeEingaben;
}

public int getFeld(){
feld=ListeEingaben.length;
return feld;
}

}
[/code]

gruß

Viktor

Antworten
Felix
  • Forum-Beiträge: 259

12.01.2012, 10:00:34 via Website

Tach!

Viktor R.
Ich habe mich an deinen Tips versucht, weiß im Moment aber nicht wirklich weiter, wie ich die Position des Elementes bestimmen kann, welches der User durch die Checkbox abwählt. Durch die TextViews erhalte ich keine Position.

An dieser Stelle arbeitest du mit Elementen der GUI und nicht mit der dahinterliegenden Datenstruktur. Das wäre anders, wenn du auf das onItemClickListener des Listview reagieren würdest, dann bekämest du die Position. So bekommst du nur das View-Element. Und von dem kannst du maximal die Parent- und Children-Kette entlanglaufen, kommst aber nicht wieder zur Position in der Datenhaltung. Du brauchst eine Zuordnung zwischen dem View-Element und der Position der Daten. Das geht über zwei Wege. Der erste, umständlichere und mehr Ressourcen verbrauchende Weg ist, sich eine Zuordnungsliste anzulegen. In der müssen ein Verweis auf die View und die Positionsnummer abgelegt sein. Wenn ein View-Element aktiviert wurde, suchst du das in der Liste und findest dazu die Position. Der einfachere Weg ist aber das Tagging. Jedes View-Element erbt von View die Möglichkeit des Taggings. Darüber kann man individuelle Daten an ein View-Element binden. Das kann ein Verweis auf das Element in der Datenhaltung sein, aber auch die Positionsnummer, Hauptsache es ist von Object abgeleitet (zum Beispiel Integer, aber nicht int). setTag() und getTag() heißen dazu die Methoden, erstere hatte ich ja schon in der vorigen Antwort verlinkt. Und dieses Tag kannst du im Click- oder sonsteinem Ereignis des View-Elements abfragen.


Felix

Antworten
Viktor R.
  • Forum-Beiträge: 14

12.01.2012, 13:37:03 via Website

Hallo Felix,

auf genau die Art und Weise (2. Vorschlag) werden die Arrays in der ListView gesetzt.
1text1.setText(artikel[position]);
2text2.setText(menge[position]);
Ich übergeb die beiden Arrays an den Kontruktor des Adapters und die Elemente werden an die entsprechende Position geseztz. Diesen weg möchte ich jezt auch rückwärts gehen, für die elemente, bei denen die CheckBox auf false gesetzt wird durch den User.
Ich habe die getView Mehode etwas angepasst:
1Public View getView(final int position, View v, ViewGroup parent){
2 View vi=v;
3 if(v==null)TextView text1=(TextView)vi.findViewById(R.id.var1);
4
5 vi=inf.inflate(R.layout.twoitems,null);TextView text2=(TextView)vi.findViewById(R.id.var2);
6
7 text1.setText(artikel[position]);
8 text2.setText(menge[position]);
9 final CheckBox check=(CheckBox)vi.findViewById(R.id.checkBox1);
10 check.setTag(position) // Die CheckBox erhält die Position n
11 check.setChecked(true);
12
13 check.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener(){
14 public void onCheckedChanged(CompoundButton bt, boolean arg1) {
15
16 check.getTag(position);
17 check.setChecked(false);
18 element.add(artikel[position]); // element ist eine ArrayList, auf die ich aus der ListView Activity zugreifen möchte. Wie erhalte ich die Daten dieser Liste?
19 }
20
21 });
22
23 return vi;
24 }
25}

Muss ich für die ArrayList noch eine getMethode definieren, um auf die Liste aus der ListView Activity zugreifen zu können? Ich sehe, dass ich an den Konstruktor Daten übergebe, weiß im Moment aber nicht, wie ich von diesem Daten bekomme.
Oder benutze ich die getTag() Methode in der ListView Activity?

gruß

Viktor

Antworten
Felix
  • Forum-Beiträge: 259

12.01.2012, 14:32:48 via Website

Tach!

Der ListAdapter hat die Methoden getItem(int position) und getItemId(int position). (Wenn du eine eigene Adapterklasse verwendest, solltest du diese ordentlich implementieren.) Der Hinweg ist beim Erstellen der View des jeweiligen Items in getView() das Setzen des Tags und der Rückweg bei ausgelöstem Ereignis ist das Lesen des Tags und das Ermitteln des konkreten Items, zum Beispiel über die oben genannten Methoden. Das wäre jedenfalls mein Weg. Ich würde nicht getrennte Arrays verwenden sondern eine Klasse mit den jeweiligen Eigenschaften eines Eintrags und die Objekte der Einträge in einer Liste ablegen. Dann kann man auch einfach ArrayAdapter<T> verwenden. Wie auch immer, mit der Angabe der Positions in deinen Arrays als Tag kommst du jedenfalls an die Daten ran.

check.setTag(position) // Die CheckBox erhält die Position n

Es gibt jeweils zwei Methoden zu setTag() und getTag().

void setTag(Object tag) ist das Gegenstück zu Object getTag()
void setTag(int key, Object tag) ist das Gegenstück zu Object getTag(int key)

Die zweite Version ist dafür gedacht, um mehrere Tags unter jeweils eigenen IDs abzulegen. Das brauchst du nicht, dir reicht die erste Variante. Mit der obigen Zeile setzt du den primitiven Wert position. Die Methode will zwar ein Objekt haben, aber das wandelt Java wohl selbständig.

check.getTag(position);

Das geht so nicht. getTag() liefert den Tag zurück. Du musst ihn also auf jeden Fall auf der linken Seite irgendwohin zuweisen, wenn du damit weiterarbeiten willst. Mit Argument rufst du die zweite Variante auf, was nicht zu deinem setTag() passt und auch nicht das ist, was du willst. Wenn du das weglässt, bekommst du jedenfalls die Positionsangabe als Integer-Objekt und nicht als int-Primitive. Gegebenenfalls musst du das zu Fuß zu einem primitiven int unboxen, wenn Java das nicht selbst hinbekommt. Jetzt hast du also die Position und kannst damit auf deine Arrays zugreifen.


Felix.

Antworten
Viktor R.
  • Forum-Beiträge: 14

12.01.2012, 22:45:51 via Website

Hallo Felix,

ich habe den TwoElementAdapter entsprechend umgeschrieben. Ich hab der CheckBox einen Listener hinzugefügt je eine getter und setter Methode:
[code]
check.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
int i=(Integer) check.getTag();

text1.getTag(position);
text1.setText(menge[position]);
check.setChecked(false);
String tmp=artikel[i];
setElemente(tmp);

}
});

return vi;
}
public void setElemente(String s){
Elemente.add(s);
}
public ArrayList<String> getElemente(){
return Elemente;
[/code]
Mit "text1.setText(menge[position]);" wollte ich nur prüfen, ob tatsächlich der richtige Wert an die Liste Elemente übergeben wird.
Was mit noch fehlt ist Verwendung der Liste Elemente. Wie greife ich aus der ListView Activity auf die Liste zu? Ich will nach einem Klick auf den Button "speichern" die markierten Elemente in die DB laden.
Wenn ich jedoch einen Listener in der ListView Activity verwende, erhalte ich eine Null Pointer Exception, lasse ich den Listener weg und schreibe stattdesseen

ArrayList<String> test=adapter.getElemente();

erhalte ich eine leere Liste. An diesem Punkt stecke ich jetzt fest.

gruß

Viktor

Antworten
Felix
  • Forum-Beiträge: 259

12.01.2012, 23:58:16 via Website

Tach!

Versuchen wir erstmal eine Erklärung für das aktuelle Problem zu bekommen, aber ich denke, du kommst besser, wenn du nochmal gehörig umbaust. Das Zusammensuchen der Informationen über eine Positionsnummer ist nicht der Weisheit letzter Schluss. Für zusammenhängende Informationen hat man in der OOP die Klassen erfunden. Für dich käme also eine Klasse infrage, die artikel und menge als public-Felder hat (keine Getter und Setter, das ist für einen Androiden nur überflüssiger Ressourcenverbrauch). Damit könntest du eine einzige ArrayList<T> als Datenhaltung verwenden, den vorgefertigten ArrayAdapter<T> verwenden (beziehungsweise von ihm ableiten und get(DropDown)View überschreiben) und statt der Positionen als Tag das komplette Item-Objekt verwenden. Aus der ArrayList lässt sich ein Eintrag auch über sein Objekt selbst und nicht nur seine Position in der Liste löschen. Wobei mir da auffällt, dass du mit der Position sowieso ein Problem bekommen wirst, denn wenn ein Element gelöscht wird, rutschen die anderen nach und nehmen neue Positionen ein. Irgendwelche anderswo gemerkte Positionsinformationen (speziell in den Tags) werden nicht aktualisiert. Referenzen auf Items als Objekte bleiben aber immer gleich, egal an welcher Listenposition sie sich befinden.

1check.setOnClickListener(new View.OnClickListener() {
2 public void onClick(View v) {
3 int i=(Integer) check.getTag();

Damit hast du erstmal erfolgreich die Position aus dem Tag gelesen.

1text1.getTag(position);
2 text1.setText(menge[position]);

Aber was ist das? Die erste Zeile tut nichts – oder besser: sie liefert etwas, nur wird das Ergebnis nicht aufgefangen. Und es ist immer noch die getTag-Variante, die wir nicht brauchen. Du wolltest wohl lediglich die zweite Zeile stehenlassen, dafür aber mit menge[i] als Argument. (position kommt auch irgendwo anders her, mit irgendeinem Inhalt, der für das aktuelle Geschehen nicht von Belang ist. Ansonsten wäre das ein Syntax-Fehler.)

Was mit noch fehlt ist Verwendung der Liste Elemente. Wie greife ich aus der ListView Activity auf die Liste zu? Ich will nach einem Klick auf den Button "speichern" die markierten Elemente in die DB laden.

Wenn du auf Item-Klasse umbaust, stellt sich diese Frage nicht mehr. Denn dann bekommst du über den Tag das komplette Item mit all seinen Informationen (muss natürlich vorher im getView() vom Adapter zugewiesen worden sein, so wie derzeit die Positionsnummer) und kannst es direkt in Richtung DB geben, ohne noch zugehörige Informationen in Listen nachschlagen zu müssen.

Wenn ich jedoch einen Listener in der ListView Activity verwende, erhalte ich eine Null Pointer Exception,

Was genau ist denn null? Aber das und das nachfolgende ist nicht mehr wichtig, wenn du auf Item-Klasse umgebaut hast. Die Positionsnummerngeschichte ist sowieso eine Sackgasse. Das ist mir erst beim Verfassen dieses Postings aufgefallen. Es lohnt nicht, sie noch weiter auszubauen.


Felix.

Antworten
Viktor R.
  • Forum-Beiträge: 14

13.01.2012, 01:04:47 via Website

Hallo Felix,

vielen Dank für die Zeit die du dir nimmst.
wenn ich dich richtig verstanden soll das Programm folgendermaßen aussehen:
Ich habe die Activity "EInkaufszettel anlegen" und dazu eine Activity "irgendeinAdapter extends irgendeinAdapter" plus eine Klasse "Daten".
Aus der Klasse Daten würde ich mir in der AdapterActivity eine ArrayList erzeugen und die nicht markierten Elemente in die Liste eintragen.
Das laden in die DB soll aber erst erfolgen, nach dem der Button "speichern" (und dieses Widget ist nicht Bestandteil der ListView, also auch nicht im Adapter definiert) gedrückt wurde. Aus irgendeinem Grund erhalte ich aber immer, wenn für diesen Button in der Activity einen Listener definiere, eine Null Pointer Exception.
Was genau ist denn null?

1public class EinkaufszettelAnlegen extends Activity implements OnClickListener{
2
3 ListView list;
4 TwoElementAdapter adapter;
5 Button save;
6 final ArrayList<String> artikel=new ArrayList<String>();
7 final ArrayList<String> menge=new ArrayList<String>();
8
9 public void onCreate(Bundle savedInstanceState) {
10 super.onCreate(savedInstanceState);
11 setContentView(R.layout.einkaufszettel_anzeigen);
12// in diesem Layout ist der Button definiert
13
14 list=(ListView)findViewById(R.id.einkaufszettel);
15// Layout für ListView {TextView,TextView,CheckBox}
16 adapter=new TwoElementAdapter(DBAdmin.this,getArtikel(),getMenge());
17 list.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
18 list.setAdapter(adapter);
19
20 final Button save=(Button)findViewById(R.id.saveZettel);
21 save.setOnClickListener(DBAdmin.this); // Auf diese Zeile verweist die Null Pointer Exception
22 }
23 public void onClick(View v) {
24 adapter.getDaten(v);
25 }

Und beim Item Vorschlag mit der Zusätzlichen Klasse weiß ich nicht so recht, wie ich die Einträge der ArrayList in die Activity "EinkaufszettelAnlegen" bekomme, außer ich schreibe sowas wie getListe() die kontinuierlich gefüllt wird, wenn ich den Haken bei einem Item entferne. Dann habe ich trotzdem noch das Problem mit dem Button, der die Liste in die DB eintragen soll.

Antworten
Felix
  • Forum-Beiträge: 259

13.01.2012, 18:07:32 via Website

Tach!
wenn ich dich richtig verstanden soll das Programm folgendermaßen aussehen:
Ich habe die Activity "EInkaufszettel anlegen" und dazu eine Activity "irgendeinAdapter extends irgendeinAdapter" plus eine Klasse "Daten".

Du musst bei deinen Design-Entscheidungen immer beachten, dass ein im Speicher zu haltender Datenbestand nicht einfach so an ein Activity-Objekt gebunden werden kann, wenn noch weitere Activitys mitspielen, die zu einer Änderung dieses Datenbestandes beitragen sollen. Denn den anderen Activitys wird nicht garantiert, dass dein Activity-Objekt mit dem Datenbestand am Leben ist. Man muss sogar damit rechnen, dass es wegen Platzmangel weggeräumt werden kann. Ein vom Activity Lifecycle unabhängiger Datenbestand lässt sich nur in einer statischen Eigenschaft einer Activity-Klasse ablegen. (Es geht auch ein anderer Ort, der nicht vom AL beeinflusst wird. Das ListAdapter-Object beispielsweise lebt auch nur im Kontext eines Activity-Objects, weil es da instantiiert wurde.)

Mal kurz zu deiner Null Pointer Exception. Du hast zwar die Zeile markiert, in der sie auftritt, hast aber nicht dazugesagt, was genau null ist und nicht hätte sein sollen. Vermutlich ist es DBAdmin.this, aber um sicher zu gehen solltest du sowas immer mit dem Debugger prüfen. DBAdmin ist jedenfalls ein Ding, das hier im Code zum ersten Mal (an zwei Stellen) verwendet wird. Ich kann nicht wissen, wo das herkommt und was es hätte enthalten sollen. Eine Erklärung oder Mutmaßung, warum das null ist, kann ich ohne den beteiligten Code zu kennen auch nicht geben.

Aus der Klasse Daten würde ich mir in der AdapterActivity eine ArrayList erzeugen und die nicht markierten Elemente in die Liste eintragen.
Das laden in die DB soll aber erst erfolgen, nach dem der Button "speichern" (und dieses Widget ist nicht Bestandteil der ListView, also auch nicht im Adapter definiert) gedrückt wurde.

So ganz eindeutig kann ich den Aufbau noch nicht erkennen, aber ich befürchte, dass die ListView-Activity nur zum Datensammeln und Anzeigen der Checkboxen dienen soll und der Button zum Starten des DB-Eintragens in einer anderen Activity angesiedelt ist. Dann hast du genau das oben geschilderte Problem, dass du nicht problemlos auf den Datenbestand in einem Activity-Object zugreifen kannst. Für dieses grundlegende Strukturproblem sehe ich zwei Lösungsansätze. Den einen hab ich ja schon erwähnt, der hat die ListView als zentrale Activity und somit auch die Datenhaltung bei sich. Der andere Ansatz ist, die Daten statisch in einer der Klassen abzulegen.

Normalerweise poste ich keinen Code, sondern versuche die Lösung oder den Weg dorthin zu beschreiben, so dass dem Probleminhaber noch genug Eigeninitiative aufbringen muss und dabei aus seinen Fehlern lernen kann. Aber hier kommt mal ein Proof-of-Concept. Weggelassen ist jeweils die Import-Liste, das bekommt Eclipse ja selbst hinzugefügt.

So könnte die Daten-Klasse aussehen. Zu sehen ist eine Eigenschaft namens selected. Das ist quick&dirty zur Vereinfachung. Eigentlich sollte nicht das Item sich selbst als selektiert zu erkennen geben, das ist nicht seine Kernaufgabe. Besser wäre es, Referenzen auf die selektierten Items in einer eigenen Liste abzulegen.

1public class Data {
2 public String article;
3 public int amount;
4 public boolean selected;
5
6 public Data(String article, int amount) {
7 this.article = article;
8 this.amount = amount;
9 this.selected = false;
10 }
11}

Das ist der Datenadapter für die ListView.

1public class DataAdapter extends ArrayAdapter<Data> implements OnCheckedChangeListener {
2
3 // derzeit nur für das Holen des LayoutInflater verwendet.
4 // den bekommt man zur Not auch über LayoutInflater.from(context)
5 TestListViewActivity activity;
6
7 public DataAdapter(TestListViewActivity activity, ArrayList<Data> data) {
8 super(activity, 0, data);
9 this.activity = activity;
10 }
11
12 @Override
13 public View getDropDownView(int position, View convertView, ViewGroup parent) {
14 return getCustomView(position, convertView, parent);
15 }
16
17 @Override
18 public View getView(int position, View convertView, ViewGroup parent) {
19 return getCustomView(position, convertView, parent);
20 }
21
22 private View getCustomView(int position, View convertView, ViewGroup parent) {
23 Data item = getItem(position);
24 View row = activity.getLayoutInflater().inflate(R.layout.data_item, parent, false);
25
26 // View for article
27 // View for amount
28 // text steht stellvertretend für die eigentlichen Views
29 TextView text = (TextView) row.findViewById(R.id.textView);
30 text.setText(item.article + ": " + item.amount);
31
32 CheckBox check = (CheckBox) row.findViewById(R.id.checkBox);
33 check.setTag(item);
34 check.setOnCheckedChangeListener(this);
35
36 return row;
37 }
38
39 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
40 ((Data) buttonView.getTag()).selected = isChecked;
41 }
42}

Und eine Activity, die den Datenadapter verwendet und die Datenhaltung mit einer ArrayList realisiert.

1public class TestListViewActivity extends ListActivity {
2 private ArrayList<Data> data = new ArrayList<Data>();
3
4 @Override
5 public void onCreate(Bundle savedInstanceState) {
6 super.onCreate(savedInstanceState);
7 setContentView(R.layout.main);
8
9 // Dummy-Daten
10 data.add(new Data("test 1", 1));
11 data.add(new Data("test 2", 2));
12
13 setListAdapter(new DataAdapter(this, data));
14
15 Button button = (Button) findViewById(R.id.button);
16 button.setOnClickListener(new OnClickListener() {
17
18 public void onClick(View v) {
19 for (Data item : data) {
20 if (item.selected)
21 Toast.makeText(TestListViewActivity.this, item.article + ": " + item.amount,
22 Toast.LENGTH_SHORT).show();
23 }
24 }
25 });
26 }
27}

Dazu kommen noch zwei Layout-Dateien. Die eine ist die data_item.xml, die für das Beispiel hier nur eine TextView und eine CheckBox in einem LinearLayout enthält. Nix besonderes, deswegen nicht aufgeführt. Die andere ist main.xml und entspricht dem ersten Listing (Screen Layout) aus der ListActivity-Dokumentation zuzüglich eines Buttons. Allerdings bekommen alle drei Kinder des LinearLayout layout_height=wrap_content und kein layout_weight, sonst ist der Button nicht zu sehen.

Ansonsten, denke ich, ist der Code ziemlich einfach und ohne große Überraschungen gehalten. Die selektierten Einträge werden lediglich getoastet, da würde dann dein DBMS-Handling hingehören.

(Und diese Forumssoftware hier könnte mal ein .code { white-space:pre} im Stylesheet vertragen, damit der Browser die Einrückungen nicht mehr unterschlägt).


Felix

Antworten