In a Xamarin Android application, I have an Activity that calls an async method (a network operation) in a RetainInstance
fragment so that the operation doesn't stop on configuration changes. After the operation is complete, the UI is changed, a progress dialog is dismissed, a new fragment is inserted into the layout, etc.
It works correctly, even if the activity is destroyed and re-created on configuration changes. However, if the activity is paused when the async method completes, UI operations throw IllegalStateException: Can not perform this action after onSaveInstanceState
exception. This happens if the user turns off the screen or switches to another application while the network operation is running.
Is there a way to make the async method continue normally if the activity is not paused. But if the activity is paused, wait until the activity is resumed before continuing?
Alternatively, what is the proper way to handle async operations that complete while the activity is paused?
The code:
using System;
using System.Threading.Tasks;
using Android.App;
using Android.OS;
using Android.Widget;
namespace AsyncDemo {
[Activity(Label = "AsyncDemo", MainLauncher = true, Icon = "@drawable/icon")]
public class MainActivity : Activity {
const string fragmentTag = "RetainedFragmentTag";
const string customFragmentTag = "CustomFragmentTag";
const string dialogTag = "DialogFragmentTag";
protected override void OnCreate(Bundle savedInstanceState) {
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.Main);
var retainedFragment = FragmentManager.FindFragmentByTag(fragmentTag) as RetainedFragment;
if (retainedFragment == null) {
retainedFragment = new RetainedFragment();
FragmentManager.BeginTransaction()
.Add(retainedFragment, fragmentTag)
.Commit();
}
Button button = FindViewById<Button>(Resource.Id.myButton);
button.Click += delegate {
button.Text = "Please wait...";
var dialogFragment = new DialogFragment(); // Substitute for a progress dialog fragment
FragmentManager.BeginTransaction()
.Add(dialogFragment, dialogTag)
.Commit();
Console.WriteLine("Starting task");
retainedFragment.doIt();
};
}
void taskFinished() {
Console.WriteLine("Task finished, updating the UI...");
var button = FindViewById<Button>(Resource.Id.myButton);
button.Text = "Task finished";
var dialogFragment = FragmentManager.FindFragmentByTag(dialogTag) as DialogFragment;
dialogFragment.Dismiss(); // This throws IllegalStateException
var customFragment = new CustomFragment();
FragmentManager.BeginTransaction()
.Replace(Resource.Id.container, customFragment, customFragmentTag)
.Commit(); // This also throws IllegalStateException
}
class RetainedFragment : Fragment {
public override void OnCreate(Bundle savedInstanceState) {
base.OnCreate(savedInstanceState);
RetainInstance = true;
}
public void doIt() {
doItAsync();
}
public async Task doItAsync() {
try {
await Task.Delay(3000); // substitute for the real operation
(Activity as MainActivity).taskFinished();
} catch (Exception e) {
Console.WriteLine(e);
}
}
}
}
}
The log:
Starting task
Task finished, updating the UI...
Java.Lang.IllegalStateException: Exception of type 'Java.Lang.IllegalStateException' was thrown.
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () [0x0000b] in /Users/builder/data/lanes/1978/f98871a9/source/mono/mcs/class/corlib/System.Runtime.ExceptionServices/ExceptionDispatchInfo.cs:61
at Android.Runtime.JNIEnv.CallVoidMethod (IntPtr jobject, IntPtr jmethod) [0x00062] in /Users/builder/data/lanes/1978/f98871a9/source/monodroid/src/Mono.Android/src/Runtime/JNIEnv.g.cs:554
at Android.App.DialogFragment.Dismiss () [0x00043] in /Users/builder/data/lanes/1978/f98871a9/source/monodroid/src/Mono.Android/platforms/android-22/src/generated/Android.App.DialogFragment.cs:284
at AsyncDemo.MainActivity.taskFinished () [0x00039] in /Users/csdvirg/workspaces/xamarin/AsyncDemo/AsyncDemo/MainActivity.cs:52
at AsyncDemo.MainActivity+RetainedFragment+<doItAsync>c__async0.MoveNext () [0x00094] in /Users/csdvirg/workspaces/xamarin/AsyncDemo/AsyncDemo/MainActivity.cs:73
--- End of managed exception stack trace ---
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1323)
at android.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1341)
at android.app.BackStackRecord.commitInternal(BackStackRecord.java:597)
at android.app.BackStackRecord.commit(BackStackRecord.java:575)
at android.app.DialogFragment.dismissInternal(DialogFragment.java:292)
at android.app.DialogFragment.dismiss(DialogFragment.java:258)
at mono.java.lang.RunnableImplementor.n_run(Native Method)
at mono.java.lang.RunnableImplementor.run(RunnableImplementor.java:29)
at android.os.Handler.handleCallback(Handler.java:733)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:146)
at android.app.ActivityThread.main(ActivityThread.java:5756)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:515)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1291)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1107)
at dalvik.system.NativeStart.main(Native Method)