ExpandableListView - getChildrenCount() ist null

  • Antworten:39
superSharp
  • Forum-Beiträge: 89

31.08.2016, 14:10:56 via Website

Guten Tag,

ich benutze die ExpandableListView mit diesem Adapter (http://www.androidhive.info/2013/07/android-expandable-list-view-tutorial/). In der MainActivity führt ein runnable jede Sekunde einen HTTP Request durch, wenn ein Wert zurück kommt, wird der als neues Item im Adapter angelegt und dann auf die ExpandableListView übertragen, der Code sieht so aus:

public void run() {
            String returnString = "";
            try {
                URL u = new URL(t);
                final HttpURLConnection connection = (HttpURLConnection)u.openConnection();
                BufferedInputStream bis = new BufferedInputStream(connection.getInputStream());
                byte[] content = new byte[1024];
                int bytesRead = 0;
                String strContent = "";
                while((bytesRead = bis.read(content)) != -1){
                    strContent += new String(content,0,bytesRead);
                }
                returnString = strContent;
            } catch (Exception e){
                e.printStackTrace();
            } finally {
                try {
                    if(returnString.equals("0 rows returned")) {
                        handler.postDelayed(this,1000);
                    } else {
                        JSONArray jObj = new JSONArray(returnString);
                        JSONObject obj = jObj.getJSONObject(0);
                        String val = (String) obj.get("value");
                        String da = (String) obj.get("date");

                        listDataHeader = new ArrayList<String>();
                        listDataHeader.add(latestRequestID);
                        List<String> data = new ArrayList<String>();
                        data.add(val);
                        data.add(da);

                        listDataChild = new HashMap<String, List<String>>();
                        listDataChild.put(listDataHeader.get(listDataHeader.size() - 1), data);
                        listAdapter = new ExpandableListAdapter(ma, listDataHeader, listDataChild);
                        expandableListView.setAdapter(listAdapter);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

Das klappt soweit auch, wenn ich nun auf meinem Gerät das Item aufklappen will, stürzt die App mit dieser Fehlermeldung ab:

FATAL EXCEPTION: main
Process: com.example.standardbenutzer.mytestapplication, PID: 20862
java.lang.NullPointerException: Attempt to invoke interface method 'int java.util.List.size()' on a null object reference
at com.example.standardbenutzer.mytestapplication.ExpandableListAdapter.getChildrenCount(ExpandableListAdapter.java:65)
at android.widget.ExpandableListConnector.refreshExpGroupMetadataList(ExpandableListConnector.java:563)
at android.widget.ExpandableListConnector.expandGroup(ExpandableListConnector.java:688)
at android.widget.ExpandableListView.handleItemClick(ExpandableListView.java:695)
at android.widget.ExpandableListView.performItemClick(ExpandableListView.java:655)
at android.widget.AbsListView$PerformClick.run(AbsListView.java:3231)
at android.widget.AbsListView$3.run(AbsListView.java:4165)
at android.os.Handler.handleCallback(Handler.java:815)
at android.os.Handler.dispatchMessage(Handler.java:104)
at android.os.Looper.loop(Looper.java:194)
at android.app.ActivityThread.main(ActivityThread.java:5624)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:959)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:754)

Der Fehler passiert hier:

@Override
public int getChildrenCount(int groupPosition) {
    return this._listDataChild.get(this._listDataHeader.get(groupPosition)).size();
}

Ich hab das auch schon mehrmals debugged, beim Debuggen bekomme ich die SubItems zurück, wenn ich dann F9 drücke (für "Resume Application") stürzt die App mit dem genannten Fehler ab. Teilweise fragt man sich bei Android / Java schon, ob die einen nur ärgern wollen.

Bin um jeden Tipp und Vorschlag dankbar.

Grüße

Antworten
swa00
  • Forum-Beiträge: 3.704

31.08.2016, 14:35:28 via Website

Hallo Sharp ..

Willkommen bei uns !

Schritt für Schritt - Ausschlussverfahren ....
Was macht denn das Ganze , wenn du keinen Async laufen lässt und ein paar Einträge statisch hinzufügst ?

P.S. Debuggen innerhalb einem Async ist nicht Ohne.

lg
Stefan

— geändert am 31.08.2016, 14:39:28

Liebe Grüße - Stefan
[ App - Entwicklung ]

Antworten
superSharp
  • Forum-Beiträge: 89

31.08.2016, 14:47:11 via Website

Hi,
danke für deine schnelle Antwort.

Ich benutze das onClick-Event eines Buttons um den Handler mit dem Runnable anzustoßen. Wenn ich im Event einen Eintrag manuell hinzufüge, klappt es ohne Error:

        btnSend.setOnClickListener(new View.OnClickListener(){
        public void onClick(View v){
            String t = new String("httpRequestURL");
            Request r = new Request(ma){
                @Override
                protected void onPostExecute(String result) {
                    latestRequestID = result;
                    editText.setText(result);
                    //spawnNewHandler(result, ma);
                    listDataHeader = new ArrayList<String>();
                    listDataHeader.add(latestRequestID);
                    List<String> data = new ArrayList<String>();
                    data.add("Hello");
                    data.add("World");

                    listDataChild = new HashMap<String, List<String>>();
                    listDataChild.put(listDataHeader.get(listDataHeader.size() - 1), data);
                    listAdapter = new ExpandableListAdapter(ma, listDataHeader, listDataChild);
                    expandableListView.setAdapter(listAdapter);
                }
            };
            r.execute(t);
        }
    });

Scheint also an dem nebenläufigen Runnable zu liegen. Wie gehe ich weiter vor?

— geändert am 31.08.2016, 14:47:41

Antworten
swa00
  • Forum-Beiträge: 3.704

31.08.2016, 14:56:26 via Website

Ich bin ein wenig verwirrt , wo du was drin hast ....

Irgendwie ein wenig zu verschachtelt..

Ich würde mir als ersten Schritt auf alle Fälle erst mal einen abgekabselte Asnyc Klasse erstellen , indem dein HTTP request läuft.
Den startest du z.b mit new fooTask_1().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
ABER nicht nach dem OnClick !!!! sondern irgendwo oben .

Dort auch bitte das entsprechende OnPostExecute rein. Und dann dann erst die Liste füllen / adden.

Im Moment habe ich die Vermutung , dass der AsyncTask dir UI mässig in die Quere kommt

— geändert am 31.08.2016, 14:58:31

Liebe Grüße - Stefan
[ App - Entwicklung ]

superSharp

Antworten
superSharp
  • Forum-Beiträge: 89

31.08.2016, 15:08:34 via Website

Ich würde mir als ersten Schritt auf alle Fälle erst mal einen abgekabselte Asnyc Klasse erstellen , indem dein HTTP request läuft.

Ich hab eine Klasse "Request" die von AsyncTask erbt:

public class Request extends AsyncTask<String,Void,String> {
public Request(){
}
@Override
protected String doInBackground(String... url){
    String returnString = "";
    try {
        URL u = new URL(url[0]);
        final HttpURLConnection connection = (HttpURLConnection)u.openConnection();
        BufferedInputStream bis = new BufferedInputStream(connection.getInputStream());
        byte[] content = new byte[1024];
        int bytesRead = 0;
        String strContent = "";
        while((bytesRead = bis.read(content)) != -1){
            strContent += new String(content,0,bytesRead);
        }
        returnString = strContent;
    } catch (Exception e){

    } finally {
        return returnString;
    }
}
protected void onPostExecute(String result){

}

}

Eine neue Instanz dieser Klasse erzeuge ich im onClick-Event.

Da die Runnable ja sowieso in einem extra Thread läuft (die Runnable brauche ich da ich mit dem Handler diese jede Sekunde neu aufrufen möchte) benötige ich da doch dann keinen extra AsyncTask mehr? Deswegen hab ich den HTTP Request gleich mit eingebaut.

Ich unterscheide hier zwischen zwei HTTP Requests:

Einmal den nach dem Button Click um eine "Anfrage" zu übermitteln (Requets).
Einmal im Runnable der wiederholt aufgerufen wird

ABER nicht nach dem OnClick !!!! sondern irgendwo oben .

Wie löse ich das dann mit der Userinteraktion? Soll ich dafür eine extra Funktion machen?

Im Moment habe ich die Vermutung , dass der AsyncTask dir UI mässig in die Quere kommt
Ja, denke ich auch.

— geändert am 31.08.2016, 15:11:34

Antworten
swa00
  • Forum-Beiträge: 3.704

31.08.2016, 15:13:24 via Website

Sieht gut aus , und jetzt haust du bitte dein List-Add Geraffel in die PostExecute vom "Request" rein.
Und den Task bitte so ausführen , wie ich oben beschrieben habe

Übrigens : du schreibst oben :

In der MainActivity führt ein runnable jede Sekunde einen HTTP Request durch

Wo ist denn das ???

P.S zum OnClick kommen wir später ....

— geändert am 31.08.2016, 15:14:38

Liebe Grüße - Stefan
[ App - Entwicklung ]

superSharp

Antworten
superSharp
  • Forum-Beiträge: 89

31.08.2016, 15:19:05 via Website

Wo ist denn das ???

Um den Zusammenhang verständlich zu machen, hier der ganze Code (etwas lang):

        setContentView(R.layout.activity_main);
    expandableListView = (ExpandableListView)findViewById(R.id.treeView);
    listAdapter = new ExpandableListAdapter(this,listDataHeader,listDataChild);
    expandableListView.setAdapter(listAdapter);

    final Button btnSend = (Button)findViewById(R.id.btnSendMessage);
    final EditText editText = (EditText)findViewById(R.id.txtReqID);
    final MainActivity ma = this;
    final ArrayList<String> arrList = new ArrayList<String>();
    final ArrayAdapter<String> arrAdapter = new ArrayAdapter<String>(getApplicationContext(),R.layout.simple_list_item_1,arrList);

    btnSend.setOnClickListener(new View.OnClickListener(){
        public void onClick(View v){
            String t = new String("RequestURL");
            Request r = new Request(){ //Request ist AsyncTask
                @Override
                protected void onPostExecute(String result) {
                    latestRequestID = result;
                    editText.setText(result);
                    //spawnNewHandler(result, ma); //Hier wird das Runnable erzeugt
                    //TEST CODE
                    listDataHeader = new ArrayList<String>();
                    listDataHeader.add(latestRequestID);
                    List<String> data = new ArrayList<String>();
                    data.add("Hello");
                    data.add("World");

                    listDataChild = new HashMap<String, List<String>>();
                    listDataChild.put(listDataHeader.get(listDataHeader.size() - 1), data);
                    listAdapter = new ExpandableListAdapter(ma, listDataHeader, listDataChild);
                    expandableListView.setAdapter(listAdapter);
                }
            };
            r.execute(t);
        }
    });
}

public void spawnNewHandler(String requestID, final MainActivity ma){
    final Handler handler = new Handler();
    final String t = new String("RequestURL" + latestRequestID);
    final Runnable runn = new Runnable() {
        @Override
        public void run() {
            String returnString = "";
            try {
                URL u = new URL(t);
                final HttpURLConnection connection = (HttpURLConnection)u.openConnection();
                BufferedInputStream bis = new BufferedInputStream(connection.getInputStream());
                byte[] content = new byte[1024];
                int bytesRead = 0;
                String strContent = "";
                while((bytesRead = bis.read(content)) != -1){
                    strContent += new String(content,0,bytesRead);
                }
                returnString = strContent;
            } catch (Exception e){
                e.printStackTrace();
            } finally {
                try {
                    if(returnString.equals("0 rows returned")) {
                        handler.postDelayed(this,1000);
                    } else {
                        JSONArray jObj = new JSONArray(returnString);
                        JSONObject obj = jObj.getJSONObject(0);
                        String val = (String) obj.get("value");
                        String da = (String) obj.get("date");

                        listDataHeader = new ArrayList<String>();
                        listDataHeader.add(latestRequestID);
                        List<String> data = new ArrayList<String>();
                        data.add(val);
                        data.add(da);

                        listDataChild = new HashMap<String, List<String>>();
                        listDataChild.put(listDataHeader.get(listDataHeader.size() - 1), data);
                        listAdapter = new ExpandableListAdapter(ma, listDataHeader, listDataChild);
                        expandableListView.setAdapter(listAdapter);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    };
    runn.run();
}

und jetzt haust du bitte dein List-Add Geraffel in die PostExecute vom "Request" rein.

Ist doch da drinnen, die Funktion wir nur in der MainActivity überschrieben damit ich da Zugriff auf die Controls habe, ansonsten müsste ich immer die MainActivity mit übergeben bzw. auch das Hinzufügen von Items zum Adapter wäre in der Klasse selbst nicht möglich. Oder sehe ich das falsch?

— geändert am 31.08.2016, 15:23:20

Antworten
swa00
  • Forum-Beiträge: 3.704

31.08.2016, 15:24:48 via Website

Also ich habe ehrlich gesagt meine Probleme damit, was du alles in der onPostExtecute machst und dann noch innerhalb eines Listeners

Wenn du schon dort etwas UI-mäßiges machst , kommst du wahrscheinlich um sowas nicht drum

getActivity().runOnUiThread(new Runnable()
{
public void run()
{

Aber du musst erst mal das Ganze eine wenig entkapseln ..... Wie oben beschreiben

Im PostExecute solltest du auch nur den Adapter füllen (add) , den du vorher initialisierst hast.
Mehr nicht ....

Alles andere solltest du auslagern

P.s deine Ganzen finals innerhalb OnCreate : setze die mal private im Header der Klasse

— geändert am 31.08.2016, 15:39:27

Liebe Grüße - Stefan
[ App - Entwicklung ]

superSharp

Antworten
superSharp
  • Forum-Beiträge: 89

31.08.2016, 15:41:23 via Website

Dank dieser Zeile:

r.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,t);

funktioniert jetzt auf einmal alles.

Im PostExecute solltest du auch nur den Adapter füllen (add) , den du vorher initialisierst hast.

Im PostExecute kann ich doch noch gar nichts füllen da mir die Daten nicht zur Verfügung stehen? Die bekomme ich erst im Runnable.

deine Ganzen finals innerhalb OnCreate : setze die mal private im Header der Klasse

Okay, hab ich.

swa00

Antworten
swa00
  • Forum-Beiträge: 3.704

31.08.2016, 15:46:33 via Website

Im PostExecute kann ich doch noch gar nichts füllen da mir die Daten nicht zur Verfügung stehen? Die bekomme ich erst im Runnable.

Falsch : OnPostExecute = "NachdemWasAusgeführtWurde" - dann stehen die die Daten zur Verfügung
Deshalb ist dein Abfangen vom PostExecute in dem Listener falsch ...

r.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,t);

VORSICHT : der Task wird/kann dadurch mehrfach ausgeführt werden
Ich bin da selber schon mal kräftig gegend die Wand gelaufen - sieh dir meinen letzten Post an ..

https://www.nextpit.de/forum/720637/verstaendniss-problem-asynctask-s

— geändert am 31.08.2016, 15:55:03

Liebe Grüße - Stefan
[ App - Entwicklung ]

superSharp

Antworten
superSharp
  • Forum-Beiträge: 89

31.08.2016, 15:54:49 via Website

Falsch : OnPostExecute = "NachdemWasGewesenIst" - dann stehen die die Daten zur Verfügung
Deshalb ist dein Abfangen vom PostExtecute in dem Listener falsch ...

Es steht die "RequestID" zur Verfügung aber nicht die Daten, die ich für die ListView benötige.

VORSICHT : der Task wird/kann dadurch mehrfach ausgeführt werden

Wird der AsyncTask beliebig vom System neu gestartet (würde mich ehrlich gesagt bei Android nicht wundern) oder kann ich das beeinflussen? Solange ich beeinflussen kann, ob der Task noch einmal läuft oder nicht, ist alles gut. Ich starte ja nicht den selben Task nochmal sondern eine neue Instanz (sobald eben der Button wieder gedrückt wird).

— geändert am 31.08.2016, 15:55:41

Antworten
swa00
  • Forum-Beiträge: 3.704

31.08.2016, 16:01:51 via Website

Es steht die "RequestID" zur Verfügung aber nicht die Daten, die ich für die ListView benötige.
Das sind doch auch Daten aus deinem Http ...

Wird der AsyncTask beliebig vom System neu gestartet (würde mich ehrlich gesagt bei Android nicht wundern) oder kann ich das beeinflussen?

Nein , dann hast du meinem Post nicht verstanden.
Du startest den selbst - ein einfaches zweimal Klicken auf den Knopf lässt dir zwei tasks starten usw.

Zieh dir das mal bitte in Ruhe rein .... ich orakle mal , dass du morgen ins nächste Problem rennst.
Das Thema muss wie ne "eins" in Fleisch und Blut übergehen.

http://www.vogella.com/tutorials/AndroidBackgroundProcessing/article.html

— geändert am 31.08.2016, 16:02:23

Liebe Grüße - Stefan
[ App - Entwicklung ]

superSharp

Antworten
superSharp
  • Forum-Beiträge: 89

31.08.2016, 16:07:57 via Website

Das sind doch auch Daten aus deinem Http ...

Ja schon, aber die füge ich nicht zur ListView hinzu. Ich weiß nicht, wie ich im onPostExecute auf die Daten der Runnalbe zugreifen kann, sobald diese durchgelaufen ist (außer globale Variable zu erstellen) um die ListView zu befüllen.

Das Thema muss wie ne "eins" in Fleisch und Blut übergehen.

Ich verstehe noch viele Sachen in Android / Java nicht, aber irgendwie gehts mir jetzt schon auf die Nüsse. Danke für den Link, ich werd mir das mal durchlesen.

Du startest den selbst - ein einfaches zweimal Klicken auf den Knopf lässt dir zwei tasks starten usw.

Das ist ja auch gewünscht, man soll ja mehrere Requests absenden können. Was ich dabei ganz interessant finde, ist, dass der Handler quasi an die "RequestID" gebunden ist. Jede Instanz kümmer sich nur um eine ID.

Antworten
swa00
  • Forum-Beiträge: 3.704

31.08.2016, 16:15:27 via Website

Im doInBackground sammelst du die Daten , wenn du damit fertig bist, gibst du sie zurück, dann kommen sie
in PostExecute an .. und von dort aus geht es weiter ... das ist der saubere weg , denn dann beendet sich auch der Task.

Da du den Task zuerst im Listener mit execute gestartet hast , wartet der so lange bis was da ist und du hindesrt den Listener daran, quasi freien Lauf zu lassen. (Frostig)

Es kann durchaus jetzt sein , das du den Task startest ,aber keine inet Verbindung hast , dann haust du nochmal 10 mal drauf und schon laufen bei dir 10 Tasks und keiner wird beendet ...

Ich verstehe noch viele Sachen in Android / Java nicht, aber irgendwie gehts mir jetzt schon auf die Nüsse. Danke für den Link, ich werd mir das mal durchlesen.

Wenn du aus der C-Ecke kommst - und so wie ich das schon seit über 20 Jahren machst - verflucht man Java ziemlich.. Gott sei dank ist mein Büro auf Parterre :-)

— geändert am 31.08.2016, 16:50:21

Liebe Grüße - Stefan
[ App - Entwicklung ]

superSharp

Antworten
Pascal P.
  • Admin
  • Forum-Beiträge: 11.286

31.08.2016, 18:13:42 via Website

Hier ist auch nochmal was zum nachlesen:
https://www.nextpit.de/forum/568854/tutorial-download-einer-webseite
Da hast du das Glück dass du einen Callback hast, der zurückkommt wenn die DAten geladen sind.
So wie ich das verstanden habe, lädst du Daten von zwei verschiedenen Quellen und willst diese dann zusammenbringen??
Erstmal braucht dein Code etwas struktur, die Runnable könne man auch noch durch einen zweiten oder einen einzigen allgemeinen Ladetask ersetzen. Dann hätte man das schonmal erledigt.
Es ist immer gut, Datenstrukuten und UI so gut es geht zu trennen ( leider geht es nicht immer), z.b. dass daten in der UI also auf die Activity oberfläche nur aus einem Callback gesetzt werden und nicht aus irgend einer onPostExecute aus dem async task..
Damit hat man dann eine Gundlage zum üer das eigentliche Problem nachzudenken.
Aber was willst du denn genau tun, vielleicht hilft es ja dein Vorhaben zu erklären.
Warum musst du auf die Daten der Runnable im AsyncTask zugriff haben, diese sollten doch komplett unabhänig sein.
Oder von was sprechen wir??

LG Pascal //It's not a bug, it's a feature. :) ;)

Antworten
superSharp
  • Forum-Beiträge: 89

31.08.2016, 19:49:30 via Website

Hi Pascal,

danke für deine Antwort.

Da hast du das Glück dass du einen Callback hast, der zurückkommt wenn die DAten geladen sind.

Das hatte ich so ähnlich schon mal probiert, bin dann allerdings daran gescheitert, dass die MainActivity in dem Beispiel das Interface implementieren musste. Das scheint da nicht der Fall zu sein. Probiere ich eventuell noch mal, so direkt sehe ich den Vorteil nicht da ich so was ähnliches doch auch mit "onPostExecute" habe, oder nicht?

So wie ich das verstanden habe, lädst du Daten von zwei verschiedenen Quellen und willst diese dann zusammenbringen??

Nicht ganz. Ich lade bzw. sende Daten von der selben Website, die über HTTP Daten aus meiner Datenbank zurückgibt bzw. speichert. Im Prinzip ist meine Vorstellung so:

Ein User drückt den Button in meiner App. Die App speichert über die Website per HTTP diese Anfrage in der Datenbank. Ein anderes Gerät schaut ebenfalls regelmäßig nach Anfragen und speichert gegebenenfalls die Antwort in der Datenbank, auch über die Website (das mach ich im Moment noch händisch, da das noch nicht läuft).
Die App prüft dann über die Website jede Sekunde, ob eine Antwort für die RequestID vorhanden ist. Falls ja, wird dieser Datensatz in die ListView geschrieben.

Erstmal braucht dein Code etwas struktur, die Runnable könne man auch noch durch einen zweiten oder einen einzigen allgemeinen Ladetask ersetzen. Dann hätte man das schonmal erledigt.

Also einen weiteren AsyncTask? Prinzipiell könnte der jetzige AsyncTask, der das Speichern der Anfrage übernimmt, das auch machen, da der HTTP Anfrage-String übergeben wird. Da ich nicht sicher bin, ob es Sinn macht, mehrere AsyncTasks zu erzeugen (es muss ja jede Sekunde nachgeschaut werden) hab ich die Runnable mit dem Handler genommen, da sich das recht einfach programmieren lässt. Die Runnable muss man jedesmal neu anstupsen, wenn die Daten dann vorhanden sind, verzichtet man einfach darauf und die Runnable löscht sich selber (lieg ich da richtig?). Beim AsyncTask stell ich mir das aktuell schwieriger vor.

Aber was willst du denn genau tun, vielleicht hilft es ja dein Vorhaben zu erklären.

Hab ich nun beantwortet.

Warum musst du auf die Daten der Runnable im AsyncTask zugriff haben, diese sollten doch komplett unabhänig sein.

Bin mir nicht sicher, ob ich das so geschrieben habe? Also, dass muss definitiv nicht sein. "onPostExecute" ist Teil des AsyncTask (bzw. läuft eigentlich wieder in der Main) und schiebt nur die Runnables an. Die Runnable muss nur Zugriff auf den ExpandableListAdapter haben um die entsprechenden Antworten aus der Datenbank dort zu speichern (und auf die RequestID, die der AsyncTask vorher geliefert hat).

Es kann durchaus jetzt sein , das du den Task startest ,aber keine inet Verbindung hast , dann haust du nochmal 10 mal drauf und schon laufen bei dir 10 Tasks und keiner wird beendet ...

Danke für den Hinweis, über solche Stolpersteine werde ich bestimmt noch öfters fallen. Den hab ich aber erstmal behoben in dem ich bei jedem Button-Click prüfe, ob eine Internetverbindung besteht.

Wenn du aus der C-Ecke kommst

Ich komme eher aus der Richtung C#, das gilt vllt. als abgespecktes C. Hab aber selbst darin nur 2 - 3 Jahre Erfahrung. C ist an diesem Projekt aber mehr oder weniger auch beteiligt da das zweite Geräte ein Controller ist, den ich in C programmiere (wenn die App steht).

— geändert am 31.08.2016, 19:54:33

Antworten
swa00
  • Forum-Beiträge: 3.704

31.08.2016, 20:19:31 via Website

Sharp,

da das zweite Geräte ein Controller ist, den ich in C programmiere (wenn die App steht).

Wahrscheinlich schon wieder ein Raspberry/Ardunio Geschädigter :-)

An der Stelle mal einen Tip - den ich wohlbemerkt von Pascal erhalten habe. :-)

Ich verwende für diese Sachen jetzt nur noch die ION http Library , ich habe alle meine ehemaligen Projekte (Raspberry) auf ION umgeschrieben - weit übersichtlicher und einfacher zu bewältigen ..

Drei Zeilen und schon hat man das , was man will - oder umgekehrt

Schaus dir mal an ...
https://github.com/koush/ion

P.S Weit effektiver arbeite ich allerdings zum Datenaustausch mit TCP Server/Client Socket - das ist zuverlässiger

lg
Stefan

— geändert am 31.08.2016, 20:22:55

Liebe Grüße - Stefan
[ App - Entwicklung ]

Antworten
superSharp
  • Forum-Beiträge: 89

31.08.2016, 20:24:33 via Website

Wahrscheinlich schon wieder ein Raspberry/Ardunio Geschädigter

:P

Programmiert wird ein ESP8266. Braucht wesentlich weniger Strom als ein Raspberry (kann man also mit einem Akku betreiben) und ist trotzdem wesentlich leistungsfähiger als ein Arduino (aber auch wesentlich schwächer als ein Raspberry).

Weit effektiver arbeite ich allerdings dabei mit TCP Server/Client Socket - ist zuverlässiger

Sowas gibts auf dem ESP8266 auch, allerdings ist mir das Ding alleine und die Angaben vom Hersteller schon komplex genug. Eigentlich sollte die App das einfachste werden (war wohl nix)...

Antworten
Pascal P.
  • Admin
  • Forum-Beiträge: 11.286

31.08.2016, 20:30:42 via Website

Erstens das mit der Lib und zweitens:
Pollen (aktualisieren der RequestId alle Sekunde ist TODLICH!
Da implmentiert man FCM (Firebase CloudMessaging) ein Google dienst der es dir erlaubt Push Nachrichten an dein Gerät zu senden.
Dann kannst du dir eine Nachricht senden lassen wenn deine ID verfügbar ist.
Damit ist das pollen abgeschafft.

Ich würde das so aufbauen:
1. Generellen asynctask mit Callback der dir eine Webseite holt.
2. Über ein callback bekommst du die Daten welche du verarbeiten und anzeigen kannst.
3. Wenn du unbedingt pollen willst, dann nimmst du den gleichen AsyncTask aber eine neue Instanz mit neuem Callback, sodass du die Daten anders verarbetien kannst.
Aus dem Callback kannst du ja dann Daten in die ListView schreiben.
3.1 Du implemeniertst FCM und sparst dir das pollen. Da musst du dann nurnoch das durchreichen der Daten aus dem FCM Service an deine Activity hinbekommen.

Setze das mal so einigermaßen um dann sehen wir weiter ;)
Im code oben sind auch einige unstimmigkeiten, aber wenn du jetzt deine Struktur änderst, dann auch das, also kann es später wieder anders aussehen.

PS: Programmiere viel C# und Java und ich habe da kaum Probleme. Für mich ist beides fast eine Programmiersprache xD

LG Pascal //It's not a bug, it's a feature. :) ;)

superSharp

Antworten
swa00
  • Forum-Beiträge: 3.704

31.08.2016, 20:36:23 via Website

@Pascal

PS: Programmiere viel C# und Java und ich habe da kaum Probleme. Für mich ist beides fast eine Programmiersprache xD

Aber was die Systeme damit anstellen ist unterschiedlich .. - da ist der springende Punkt :-)

Siehst ja an mir, dass ich manchmal äusserst Probleme habe, da logisch zu folgen
(z.b. man fasst Resourcen einer lib mit denen der Main zusammen - wer hat sich das denn ausgedacht ? :-)

— geändert am 31.08.2016, 20:46:07

Liebe Grüße - Stefan
[ App - Entwicklung ]

Antworten
Pascal P.
  • Admin
  • Forum-Beiträge: 11.286

31.08.2016, 20:47:46 via Website

Ja das Android Konzept ist schon noch mal was für sich, ich meinte Jetzt Java als reine Programmiersprache.
Wenn man diese im Android Umfeld einsetzt ist es nochmal was anderes.
Ist wie C# für Android mit Xamarin, das ist ja auch nicht nur reines C#

LG Pascal //It's not a bug, it's a feature. :) ;)

superSharp

Antworten
superSharp
  • Forum-Beiträge: 89

31.08.2016, 20:53:03 via Website

Pollen (aktualisieren der RequestId alle Sekunde ist TODLICH!

waaa...Warum? :'(

  1. Wenn du unbedingt pollen willst, dann nimmst du den gleichen AsyncTask aber eine neue Instanz mit neuem Callback, sodass du die Daten anders verarbetien kannst.
    Aus dem Callback kannst du ja dann Daten in die ListView schreiben.

Okay, hier meine neue Funktion:

private String recendReqID = "";
private void submitRequest(String url){
    Request.receivedDataListener rdl = new Request.receivedDataListener(){
        @Override
        public void onReceiveComplete(String result){
            recendReqID = result;
            lblRequestID.setText(result);

            Request.receivedDataListener recDListener = new Request.receivedDataListener(){
                @Override
                public void onReceiveComplete(String result){
                    if(result.equals("0 rows returned")) {
                        SystemClock.sleep(1000); //Warte für eine Sekunde
                        //Wie rufe ich jetzt hier einen neuen Request auf? Die Daten sind noch nicht vorhanden
                    } else {
                        try{
                            JSONArray jObj = new JSONArray(result);
                            JSONObject obj = jObj.getJSONObject(0);
                            String val = (String) obj.get("value");
                            String da = (String) obj.get("date");

                            listDataHeader.add(recendReqID);
                            List<String> data = new ArrayList<String>();
                            data.add(val);
                            data.add(da);

                            listDataChild.put(listDataHeader.get(listDataHeader.size() - 1), data);
                            listAdapter = new ExpandableListAdapter(ma, listDataHeader, listDataChild);
                            expandableListView.setAdapter(listAdapter);
                        } catch(Exception e){
                            e.printStackTrace();
                        }
                    }
                }
            };

            String pollRequest = new String("irgendeineWebsite" + recendReqID);
            Request pollReq = new Request(recDListener);
            pollReq.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,pollRequest);
        }
    };

    Request r = new Request(rdl);
    r.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,url);
}

Wie rufe ich jetzt hier einen neuen Request auf, der erneut überprüft, ob die Daten vorhanden sind (siehe Kommentar)?

Antworten
swa00
  • Forum-Beiträge: 3.704

31.08.2016, 21:04:55 via Website

P.S ich ziehe mich jetzt ein bischen raus, verfolge aber ... wenn drei Quasseln bringt das nichts :-)
.

Kurz nur dazu - wird dir Pascal wahrscheinlich erklären

Pollen (aktualisieren der RequestId alle Sekunde ist TÖDLICH!

Deswegen mein Tipp an Dich : TCP Client/Server - das hatte seinen guten Grund

— geändert am 31.08.2016, 21:05:42

Liebe Grüße - Stefan
[ App - Entwicklung ]

superSharp

Antworten
Pascal P.
  • Admin
  • Forum-Beiträge: 11.286

31.08.2016, 21:15:59 via Website

Wie du das aufrufst?
Schon mal was von rekursion gehört?
da wo der kommentar steht kannst du doch einfach deine private void submitRequest(String url) Methode mit der passenden URL wieder aufrufen oder?
Pass aber auf dass keine Endlosschleife entsteht :D

Zum pollen:

Da gibt es mehere Gründe:
1. Es werden dauernd Daten geschickt (falls die App mobil eingesetzt wird auf jeden Fall nicht schonend fürs Datenvolumen)
2. Es belastet das Netzwerk (wird im Heimsetz kein Problem sein, denke aber mal an ein Firmennetz an in dem Jede Applikation pollt...)
3. Es kostet Energie (Akku wird schneller leer...)
4. Es gibt bessere Lösungen wie TCP oder FCM etc...
5. Wenn du nehmen wir mal an x Geräte hast die auf den gleichen Server pollen, wieviele dürefen es dann sein bevor der Server überlastet ist oder gar abstürzt etc..

Ich weiß im privaten Bereich vernachlässigt man sowas gerne, aber ich hab meine Gründe solche Dinge auch aus Fitem-Softwareentwickler sicht zu betrachten.
Kannst du schon so machen, stören wirds auch nicht, aber beachte die Punkte oben

LG Pascal //It's not a bug, it's a feature. :) ;)

superSharpLudy

Antworten
superSharp
  • Forum-Beiträge: 89

31.08.2016, 21:48:18 via Website

Ich hab es nun mit einer zweiten Funktion gelöst da sonst die "submitRequest" nochmal durchgeführt worden wäre:

private String recendReqID = "";
private void submitRequest(String url){
    Request.receivedDataListener rdl = new Request.receivedDataListener(){
        @Override
        public void onReceiveComplete(String result){
            recendReqID = result;
            lblRequestID.setText(result);
            pollData(result);
        }
    };

    Request r = new Request(rdl);
    r.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,url);
}

private void pollData(final String reqID){
    Request.receivedDataListener recDListener = new Request.receivedDataListener(){
        @Override
        public void onReceiveComplete(String result){
            if(result.equals("0 rows returned")) {
                SystemClock.sleep(1000); //Warte für eine Sekunde
                pollData(reqID);
            } else {
                try{
                    JSONArray jObj = new JSONArray(result);
                    JSONObject obj = jObj.getJSONObject(0);
                    String val = (String) obj.get("value");
                    String da = (String) obj.get("date");

                    listDataHeader.add(recendReqID);
                    List<String> data = new ArrayList<String>();
                    data.add(val);
                    data.add(da);

                    listDataChild.put(listDataHeader.get(listDataHeader.size() - 1), data);
                    listAdapter = new ExpandableListAdapter(ma, listDataHeader, listDataChild);
                    expandableListView.setAdapter(listAdapter);
                } catch(Exception e){
                    e.printStackTrace();
                }
            }
        }
    };

    String pollRequest = new String("website" + recendReqID);
    Request pollReq = new Request(recDListener);
    pollReq.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,pollRequest);
}
  1. Es werden dauernd Daten geschickt (falls die App mobil eingesetzt wird auf jeden Fall nicht schonend fürs Datenvolumen)

Wie würde das denn mit der TCP Methode aussehen? Was ich so ergooglet habe würde das so aussehen: http://stackoverflow.com/questions/10673684/send-http-request-manually-via-socket

Pollen würde ich da ja trotzdem (wenn ich den Google Service nicht nutze)? Bräuchte das dann weniger Datenvolumen wenn man die Verbindung offen hält?

— geändert am 31.08.2016, 21:48:59

Antworten
swa00
  • Forum-Beiträge: 3.704

31.08.2016, 22:03:44 via Website

@Pascal , ich werfe mal schnell das hier rein

Nein , die Methode wäre eine REINE TCP Socket verbindung

 socket = new Socket(dstAddress, dstPort);
            ByteArrayOutputStream byteArrayOutputStream =
                    new ByteArrayOutputStream(1024);
            byte[] buffer = new byte[1024];
            int bytesRead;
            InputStream inputStream = socket.getInputStream();
            boolean connected = socket.isConnected() && !socket.isClosed();
            while ((bytesRead = inputStream.read(buffer)) != -1)
            {
                byteArrayOutputStream.write(buffer, 0, bytesRead);
                response += byteArrayOutputStream.toString("UTF-8");
            }

und auf deinem Controller den TCP-Server (NICHT HTTP) in reinem C/C++ mit accept usw.
(Das Netz ist voll von Beispielen)

Pollen würde ich da ja trotzdem

Nein ,wenn die Verbindung steht , schickt der Controller nur dann was , wenn er was hat.

Info an dich : JEDER http Request - auch wenn nichts zurück kommt hat min 1,5 k request/response - header!!

— geändert am 31.08.2016, 22:09:12

Liebe Grüße - Stefan
[ App - Entwicklung ]

superSharpLudy

Antworten
superSharp
  • Forum-Beiträge: 89

31.08.2016, 22:14:36 via Website

Nein ,wenn die Verbindung steht , schickt der Controller nur dann was , wenn er was hat.

Das würde aber nur funktionieren, wenn beide Geräte im selben Netz sind bzw. sich die App direkt mit dem Controller verbindet?

Antworten
swa00
  • Forum-Beiträge: 3.704

31.08.2016, 22:19:25 via Website

Das würde aber nur funktionieren, wenn beide Geräte im selben Netz sind bzw. sich die App direkt mit dem Controller verbindet?

öhm - wieso das ?? ist doch schnuppe, ob du eine URL oder eine IP hast ...
Sprich , ob du einen http server oder einen TCP-Socket-Server auf der Kiste betreibst, ist gleichgültig
Connect ist Connect ....

— geändert am 31.08.2016, 22:25:40

Liebe Grüße - Stefan
[ App - Entwicklung ]

superSharp

Antworten
superSharp
  • Forum-Beiträge: 89

31.08.2016, 22:27:48 via Website

Ich mach das ganze aktuell über eine Website, als man in the middle sozusagen, da das Ganze auch funktionieren soll, wenn ich nicht zu Hause bin.

Antworten
swa00
  • Forum-Beiträge: 3.704

31.08.2016, 22:36:38 via Website

Ich glaube, du stehst eben ein wenig auf dem Schlauch :-)

Wenn du nicht zu hause bist , rufst du derzeit auf : http://meinziel/blabla

das ist das Gleiche wie : socket = new Socket("http://meinziel/blabla", 80);

Nur mit dem grossen Unterschied, dass :

a) du beim connectaufbau nicht unsinnig jede sekunde 1,5K verbrätst
b) du wunderbar checken kannst, ob die sockertverbindung noch besteht und kannst reconnecten - das sind nur ein paar bytes. (Kannst ja noch Pings durch die gegend schieben)
c) und wenn du die Verbindung Aufgebaut hast, passiert vielleicht 2 stunden gar nichts - ergo , kein einziges byte über den Äther, bis der Controller was hat

Rechne mal bei deinem http - request für die zwei stunden : 3600 x 2 x 1,5 KBytes = 11MB für NIX
.
.
Oder halt FCM - wie Pascal angedacht hat - aber jede Sekunde einen httpRequest - na ja :-(

— geändert am 31.08.2016, 22:40:14

Liebe Grüße - Stefan
[ App - Entwicklung ]

superSharp

Antworten
superSharp
  • Forum-Beiträge: 89

31.08.2016, 22:42:37 via Website

Ich verstehe es tatsächlich nicht. Wie kommt die Nachricht von meinem Controller über die Website an meine App, dass der Controller was hat, ohne das ich nachfragen muss? Der Controller kann es mir nicht sagen, da er in einem anderen Netz ist, die Website bzw. die Datenbank kann es mir auch nicht sagen, da es eben eine Website ist (oder kann ich mit JavaScript auf einen bestehenden WebSocket zugreifen?). Ergo muss ich doch immer noch nachfragen "wie siehts aus"? Wäre hilfreich, wenn du mir genauer erklären könntest, wie das zwischen Controller / App und Datenbank (Website) ablaufen soll?

— geändert am 31.08.2016, 22:45:17

Antworten
swa00
  • Forum-Beiträge: 3.704

31.08.2016, 22:54:18 via Website

Ok, ich bin wohl wieder drin .. Pascal kann pause machen :-)

Ich werfe eine Gegenfrage in den Raum :

Dein Middle hat derzeit einen http-Server aufgebaut - gut und schön.
Es ist auch jetzt erst mal wurscht , wie der Middle die Daten vom Controller bekommt.

ABER : Der Middle kann auch einen TCP-Socket-Server laufen lassen halt nur nicht über http- vorgang wie oben beschrieben. (Stichwort : Datenvolumen)
Und wenn der Middle was Neues vom Controller hat , kann der ja an deine App schicken.
Du brauchst im Prinzip einen Push Service - da gibts auch noch andere Techniken auf Web basis (Node.js)

Auch die Verbindung vom Middle zum Controller kannst du jederzeit mittels TCP-Socket machen.
(Ein Relay - Das nenne ich dann Waschmaschine - Daten werden nur geschleudert)

Abgesehen davon : Es gäbe auch die Möglichkeit mittels DYNDNS direkt auf dem Controller zu landen.
Wenns mehrere sind, machste einen Port unterschied.

Bitte verstehe mich nicht falsch , es geht hier lediglich darum , dass du unnötig Datenvolumen verbrätst und
gerne schnell auf Zustände reagieren möchtest - da ist HTTP falsch an der Stelle.

— geändert am 31.08.2016, 22:59:35

Liebe Grüße - Stefan
[ App - Entwicklung ]

superSharp

Antworten
Pascal P.
  • Admin
  • Forum-Beiträge: 11.286

31.08.2016, 22:58:52 via Website

Das geht nur wenn der Server TCP an sich unterstützt.
Wenn du nur einen reinen Webserver (mit php etc..) hast dann nicht, da du da keine dauerhafte TCP Verbindung aufrecht erhalten kannst.
Wir sind bisher davon ausgeganden dein Controller ist gleichzeitig der htp server und du hast ein Portforwarding nach außen. Damit würde das nämlich gehen. Ansonsten nur noch mit FCM, oder einem Server den man als TCP Server benutzen kann.
Die frage ist wie lange und wann löuft der Poll. Sind das nur ein paar Sekunden ist das zu verkraften, aber wenn das einige Stunden sind und dann noch laufen soll wenn die App geschlossen ist (BackgroundService) dann kann es kritisch werden.
Überleg mal 1 REquest pro Sekunde und nehmen wir pro forma mal 10 kb an Daten an.
in einer Minute wären das dann 10*60=600kb und das mal 60 = 36.000kbsind ca. 36mb, somit brauchst du pro Stunde laufzeit 36MB Daten. Bei den noch hohen Datenpreisen für mobile Daten würde ich mir das schon +überlegen om mir das "wert" ist. Wie gesagt je nach dem wie lange ein Poll läuft....

@Stefan: Wie gesagt, ein normaler WebServer unterstütz kein TCP, da ich nur übers http protokoll per php etc.. kann. Dafür kann ich aber umso einfacher von einem php Server eine FCM auslösen.

— geändert am 31.08.2016, 23:00:35

LG Pascal //It's not a bug, it's a feature. :) ;)

superSharp

Antworten
superSharp
  • Forum-Beiträge: 89

31.08.2016, 23:01:03 via Website

Bitte verstehe mich nicht falsch , es geht hier lediglich darum , dass du unnötig Datenvolumen verbrätst und
gerne schnell auf Zustände reagieren möchtest - das ist HTTP falsch an der Stelle.

Ich möchte das auch gerne ändern da ich mir inzwischen bewusst bin, dass es zwar funktioniert aber es einen besseren Weg gibt.

Und wenn der Middle was Neues vom Controller hat , kann der ja an deine App schicken.

Wie genau setze ich sowas auf meinem Webserver um?

Abgesehen davon : Es gäbe auch die Möglichkeit mittels DYNDNS direkt auf dem Controller zu landen.
Wenns mehrere sind, machste einen Port unterschied.

Hm, lieber nicht. Erstmal einfach(er).

Wenn du nur einen reinen Webserver (mit php etc..) hast dann nicht, da du da keine dauerhafte TCP Verbindung aufrecht erhalten kannst.

Ich hab nur einen kostenlosen Webspace mit PHP / Datenbank und Co, geht also anscheinend nicht?`

Die frage ist wie lange und wann löuft der Poll.

Im idealen Fall ist das in 4 Sekunden pro Request erledigt. Hält sich also noch in Grenzen.

— geändert am 31.08.2016, 23:04:45

Antworten
swa00
  • Forum-Beiträge: 3.704

31.08.2016, 23:03:22 via Website

@Stefan: Wie gesagt, ein normaler WebServer unterstütz kein TCP, da ich nur übers http protokoll per php etc.. kann

Ja sorry , ich hatte bis dato immer nur dedicated - denke ich manchmal nicht dran :-(

@Sharp :
Du siehst , beide - Pascal und ich würden dir anraten , den Vorgang nochmal zu überdenken
Falls das oben untergegangen ist - Node.JS ( wenn das bei dir geht ) wäre auch noch eine Möglichkeit

— geändert am 31.08.2016, 23:04:24

Liebe Grüße - Stefan
[ App - Entwicklung ]

superSharp

Antworten
superSharp
  • Forum-Beiträge: 89

31.08.2016, 23:05:31 via Website

Node.JS ( wenn das bei dir geht )

Geht nicht, hab nur nen kostenlosen Webspace.

swa00

Antworten
Pascal P.
  • Admin
  • Forum-Beiträge: 11.286

31.08.2016, 23:08:09 via App

Dann so lassen oder FCM

Was anderes bleibt dir nicht.

LG Pascal //It's not a bug, it's a feature. :) ;)

superSharp

Antworten
swa00
  • Forum-Beiträge: 3.704

31.08.2016, 23:08:33 via Website

Dafür kann ich aber umso einfacher von einem php Server eine FCM auslösen.

Pascal ? - Beispiel bitte - liebschau ---
Das habe ich nämlich noch nicht umgesetzt .... Ich hatte eben schon nach einer C-Lösung gesucht ,
Aber PHP ginge zur Not auch :-)

EDIT : Gefunden , Ludy hat ja was schönes geschrieben - danke

— geändert am 31.08.2016, 23:39:21

Liebe Grüße - Stefan
[ App - Entwicklung ]

superSharp

Antworten
superSharp
  • Forum-Beiträge: 89

31.08.2016, 23:11:47 via Website

Dann so lassen oder FCM

Ich denke, ich lasse es erstmal so und bastel am Controller. Vielen Dank für eure Hilfe (cool)

swa00

Antworten
swa00
  • Forum-Beiträge: 3.704

31.08.2016, 23:16:23 via Website

Ich denke, ich lasse es erstmal so und bastel am Controller. Vielen Dank für eure Hilfe

Keine Ursache, das machen wir gerne - und würden uns auch über ein paar Danke - Klicks freuen :-)

P.S Sei jetzt nicht fustriert, aber Blümchenmalerei wolltest du ja eh nicht haben :-)

Liebe Grüße - Stefan
[ App - Entwicklung ]

superSharp

Antworten