Ablaufpfade bei Callbacks übersichtlich halten



  • Hallo zusammen

    Mein Problem betrifft zwar Android-Entwicklung, aber es handelt sich um eine Frage, wie sie in Java ab und zu vorkommen könnte.

    Ich habe oft das Szenario, wo ich sowas machen müsste:

    private void doActions() {
        if (!feature.isEnabled()) {
            feature.enable();
        }
        doFurtherActions();
    }
    

    Da aber in der GUI-Programmierung alles asynchron über Callbacks läuft, wird das Ganze um einiges komplizierter:

    private void doActions() {
        if (feature.isEnabled()) {
            doFurtherActions();
        } else {
            feature.enable(); // oft auch im Intent selbst implizit enthalten
                              // muss z.B. vom Benutzer bestätigt werden
            startActivityForResult(myIntent, ...); // ruft onActivityResult() Callback auf
        }        
    }
    
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (/* requestCode kommt aus doActions()-Aufruf */) {
            doFurtherActions();
        }
    }
    

    Das ist problematisch, weil es den Aufruf von doFurtherActions() über zwei Methoden verteilt, was den Code um einiges schwerer les- und nachvollziehbar macht. Es braucht relativ lange, den Ablaufpfad über mehrere Callbacks hinweg zu erkennen, und man baut sehr schnell Bugs und Inkonsistenzen ein.

    Meine Idee war daher, die Funktion doFurtherActions() als Callback an den Intent zu übergeben (als Implementierung eines Interfaces). Das Problem ist, Intent nimmt nur Serializable - oder Parcelable -Objekte, weil das Ganze auf Interprozesskommunikation ausgelegt ist. Ich arbeite aber nur innerhalb einer einzelnen Activity -Klasse. Dadurch müsste man jedes Callback irgendwie in seine serialisierbaren Grundbausteine zerstückeln; ich wüsste nicht, wie das allgemein möglich wäre.

    Natürlich gäbs noch Hacks, wie den Methodennamen doFurtherActions als String zu übergeben und in onActivityResult() über Reflection aufzurufen, oder die Methode als Membervariable zu speichern (evtl. in einer Map vom Request-Code auf das Callback-Interface). Ist allerdings relativ unschön, mit der Membervariable müsste man auch noch Threadsicherheit berücksichtigen.

    Wie würdet ihr sowas lösen? Weiss jemand, ob Android für solche Situationen einen Mechanismus vorsieht, oder gibts in Java dafür ein spezielles Idiom? Idealerweise eines ohne Hunderte Zeilen zusätzlichen Code...



  • Du arbeitest nur mit einer Activity instanz? Wenn du nur mit einer Activity arbeitrset, wieso verwendust du dann "startActivity...!
    Is mir bei mir schon etwas länger her, aber mit startactivity wechselt androiod doch die activity und die daten die von einer activity in eine andere geschoben werden müssen serialisert werden.. aber du arbeitet ja nur mit einer?!?!?



  • Das war etwas ungenau formuliert. startActivity() startet keine Activity von mir, sondern nur eine Benutzerabfrage, beispielsweise muss dieser das Einschalten von Features wie Lokalisierung, Bluetooth etc. bestätigen. Sobald das bestätigt ist, fahre ich mit dem Code in der Activity fort: doFurtherActions() .

    Wenn es nicht bestätigt werden muss (weil das Feature bereits aktiviert ist), kann ich direkt doFurtherActions() aufrufen.



  • Scheint so, als könnte ich den Intent hier ohnehin nicht zur Übergabe von Daten benutzen. Ich nahm an, bei den Benutzeranfragen würde der von mir an startActivityForResult() übergebene Intent bis zu onActivityResult() weitergereicht werden, was aber nicht der Fall ist. Dort erhalte ich nur null zurück.

    Ich habe nun die ganze App in eine explizite Finite State Machine umgebaut. Ich habe ein enum für die Zustände und eine Funktion void transition(State newState) mit einem switch , welche für jeden Zustand die richtige Methode aufruft. Das macht den ganzen Ablauf etwas übersichtlicher, und mittels Logging sehe ich alle Zustandsübergänge, was Debugging vereinfacht.

    Zum ursprünglichen Problem: Nun speichere ich den nächsten Zustand in einer Membervariable und rufe in onActivityResult() die Funktion transition() auf. Dass der nächste Zustand als Member gespeichert und nicht ans Callback übergeben wird, gefällt mir nicht so, aber wie im ersten Absatz beschrieben sehe ich momentan keine andere Möglichkeit.

    Ideen zum FSM-Design, oder weitere Vorschläge?



  • Nexus schrieb:

    Dass der nächste Zustand als Member gespeichert und nicht ans Callback übergeben wird, gefällt mir nicht so

    Das verstehe ich nicht.
    Wieso kannst/willst du denn den nächsten Zustand nicht über requestCode an den Callback übergeben?



  • Ich brauche requestCode ja, um den Aufruf zu identifizieren (mehrere Requests werden in der selben onActivityResult() -Methode unterschieden).

    Ich könnte natürlich versuchen, ein paar Bits des Request-Codes für den nächsten Zustand zu verwenden, diesen Teil später entsprechend zu extrahieren und dann als int wieder in Enum zurückzubiegen. Ist aber nicht gerade das, was ich von Java gewohnt bin 🙂



  • Hat noch jemand Ideen dazu?

    Was haltet ihr vom State-Machine-Ansatz? Ist das eine gängige Methode (die Tutorials erwähnen zumindest nichts dergleichen)? Oder wie haltet ihr den Programmfluss übersichtlich?



  • Ich verstehe das Problem leider immer noch nicht richtig...
    Du hast gesagt, dass du eine Fremd-Activity aufrufst.
    Also kann die den Zustand ja wohl nicht ändern.
    D.h. beim Aufruf von StartActivity weißt du doch schon, welche Methode in onActivityResult aufgerufen werden muss.

    Das könnte man ja leicht über den Request-Code regeln.
    Aber offensichtlich passt meine Beschreibung nicht ganz zu deinem Problem.
    Wo ist da der Hacken?



  • startActivity() kann aus mehreren Orten (in derselben Activity-Klasse) aufgerufen werden, onActivityResult() hat eine entsprechende Fallunterscheidung. Der Request-Code ist also bereits mit einer Bedeutung belegt; um darin auch noch den Zustand der FSM zu speichern, müsste ich den Request-Code bitweise zerlegen.

    Ich kann das schon tun, nur frage ich mich, was in so einem Fall die Standardvorgehensweise ist... Denn das dürfte wahrscheinlich ab und zu vorkommen. Bisher habe ich den Eindruck, dass die meisten Leute die Aufrufe hardcoden, was aber den Programmablauf schnell unübersichtlich macht.


Log in to reply