Resume nach FilePicker oder anderer Activity in Xamarin.Forms

Unter Android bin ich auf folgendes Problem gestoßen, welches unter Xamarin Forms dessen Ursache anfangs für mich nicht gleich ersichtlich war:

 

Um genau zu sein verwendete ich den CrossFilePicker um den User ein File aussuchen zu lassen. Danach wurden die Daten aus dem File erfolgreich geladen oder gespeichert und ein Success oder Error Dialog via UserDialogs angezeigt.

Allerdings wurde der Dialog danach oft einfach nicht angezeigt oder die App zeigte ein recht eigenwilliges Verhalten (als würde eine Exception irgendwo geschluckt). Als ich dann vom Emulator und ohne Breakpoints das ganze auf meinem Handy startete bekamm ich doch einaml folgende Fehlermeldung:

WindowLeakedException.png

Auf der Suche nach "has leaked window com.android.internal.policy.PhoneWindow$DecorView" stellte sich recht schnell heraus, dass dies genau dann passiert, wenn man aus einer inaktiven oder geschlossenen Activity einen Dialog öffnen möchte.

Nun durch den FilePicker oder andere Dialoge, wird unter Android die App pausiert und erst danach mit dem Rückgabewert wieder geöffnet. So auch beim CrossFilePicker. Gott sei dank kümmert sich Xamarin.Forms und auch Android inzwischen darum, was alles persistiert werden muss um eine App wieder genau in dem Zustand zu erwecken in dem sie davor war. Leider muss man allerdings auf ein paar Dinge aufpassen.

So kann es wie beim CrossFilePicker passieren, dass der Callback des PickerTasks zurückkehrt bevor die App und dessen Activity wieder vollständig "wiedererweckt" wurde und alle Referenzen auf Activity und co wieder aktuell sind - und so entsteht auch dieser Fehler.

Und es erklärt auch warum es mit Breakpoints dazwischen wieder funktioniert denn der Debugger syncronisiert alle Threads auf um die Diagnose und Quickwatch Anzeige zu befüllen und damit ist auch die App sicher wieder in einem synchronisierten gültigem Zustand - ohne Breakpoints und Debugger läuft der Task aber schneller weiter als die App alles wieder aufgeweckt hat.

Wenn man allerdings in der App.cs auf die OnSuspend und OnResume eine statische "Paused" Property setzt und wieder aufhebt, kann man leicht darauf reagieren und nach dem awaitable-Call des Pickers (oder was man auch immer man erledigt haben möchte) einfach mittels eines kurzen Checks prüfen ob die App noch pausiert ist, bevor man den nächsten Dialog aufmachen möchte.

 

Das schaut dann in etwa so aus:

 

in der App.cs:

        public static bool Paused { get; set; } = false;

        protected override void OnSleep()
        {
            Paused = true;
        }

        protected override void OnResume()
        {
            Paused = false;
        }

und an den ensprechenden Stellen im Code:

while (App.Paused) await Task.Delay(100);

Es gibt vielleicht noch elegantere Methoden aber diese ist schnell und funktioniert. Wenn ihr allerdings Anregungen habt freue ich mich über Antworten (auf Facebook, Twitter & Co)