Since this one keeps coming up time and time again I figured I would write a quick blog about it. The dreaded: Java.Lang.IllegalStateException: Can not perform this action after onSaveInstanceState. that keeps crashing your application. What is it, why is it happening, and how do you fix it.
This actually can happen in non-Xamarin.Forms Android applications, but it seems to keep coming up in the Forums and in GitHub due to the heavy use of async/await, plugins, and developers attempting to navigate too quickly. First here is an example of this crash happening:
private async void Button_OnClicked(object sender, EventArgs e)
{
await CrossMedia.Current.Initialize();
//Go off and pick a photo via plugin/new activity
var file = await CrossMedia.Current.PickPhotoAsync();
//Crash will happen right here
await Navigation.PopAsync(true);
//some code here maybe
}
In this instance the code PickPhotoAsync
will launch a new intent and Android activity to pick the photo and then return it. This doesn’t have to be just the pick photo situation, as it is very similar with handling permissions, taking a photo, picking a document, or launching a login browser page.
When the file is returned we attempt to navigate right after. This could have been a Pop or it could have been a Push on the navigation stack, it doesn’t matter as it will crash for the same reason. That is because everything is happening so fast in this block of code that the activity that was launched has not actually completely finished and is still visibile to some extent. The Xamarin.Forms fragment has not returned from the onSaveInstanceState and has not re-hydrated everything just yet, so BOOM Crash!
In normal Android applications this usually doesn’t happen too much as launching a new activity or swapping a fragment takes a bit of time, but could still totally happen. Often though if you are using standard Activity calls you will actualy override the OnActivityResult method and then do the navigation so it wouldn’t happen at all, but Xamarin.Forms doesn’t really have that so the plugin returns before the activity is killed completely and we try to navigate so fast that we could have a race condition.
So how do we solve it? Well first, don’t try to navigate directly after a call like this. Even after doing a login with something like MSAL or Azure Mobile Apps calling a navigate would end up crashing the app. The easy workaround though is to actually put a small delay in your application ensuring that the UI actually re-hydrates correctly:
private async void Button_OnClicked(object sender, EventArgs e)
{
await CrossMedia.Current.Initialize();
//Go off and pick a photo via plugin/new activity
var file = await CrossMedia.Current.PickPhotoAsync();
//Small delay
await Task.Delay(100);
//NO MORE CRASHES! Yay
await Navigation.PopAsync(true);
//some code here maybe
}
If you are inside of a Page, you can loop into the OnAppearing of the page, which meas your page should be re-hydrated and back on top, similar to the OnStart of an Android Activity. Some libraries can add a bit of delay after calling Finish();, but before calling their callback, but really I would leave it up to the app developers to handle this.
Anyways, there you have it! :)