Issue
I am displaying an error message from an AsyncTask to my Fragment but when my app is on background the app is crashing.
the code is below:
import android.support.v4.app.FragmentManager;
public class AppUtil {
public static void showErrorDialog(FragmentManager fm, int messageResourceId) {
if (fm != null && !fm.isDestroyed()) {
InfoDialogFragment.newInstance("", messageResourceId).show(fm, "ERROR");
}
}
public static boolean isEmpty(String s) {
return s == null || s.trim().length() == 0;
}
}
import android.app.ProgressDialog;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
public class BlankFragment extends Fragment implements ILoginable,
View.OnClickListener {
private ProgressDialog pd ;
public BlankFragment() {
// Required empty public constructor
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View rootView = inflater.inflate(R.layout.fragment_blank,
container, false);
rootView.findViewById(R.id.pressBT).setOnClickListener(this);
return rootView;
}
@Override
public void starProgress(int messageId) {
if (pd == null) {
pd = new ProgressDialog(getContext(), R.string.loading);
pd.show();
}
}
@Override
public void endProgress() {
if(pd != null && isAdded()) {
pd.dismiss();
pd = null;
}
}
@Override
public void displayError(int messageId) {
if (isAdded()) {
AppUtil.showErrorDialog(getFragmentManager(), R.string.app_name);
}
}
@Override
public void loginResult(boolean success) {
}
@Override
public void onClick(View v) {
if (R.id.pressBT == v.getId()) {
new LoginAsyncTask(this).execute("x");
}
}
}
public interface ILoginable {
void starProgress(int messageId);
void endProgress();
void displayError(int messageId);
void loginResult(boolean success);
}
import android.os.AsyncTask;
import java.lang.ref.WeakReference;
public class LoginAsyncTask extends AsyncTask<String, Void, String> {
private WeakReference<ILoginable> reference;
public LoginAsyncTask(ILoginable iLoginable) {
reference = new WeakReference<>(iLoginable);
}
@Override
protected void onPreExecute() {
reference.get().starProgress(R.string.loading);
}
@Override
protected String doInBackground(String... strings) {
return "xx";
}
@Override
protected void onPostExecute(String s) {
if (reference.get() != null) {
reference.get().endProgress();
reference.get().displayError(R.string.hello_blank_fragment);
}
reference.clear();
}
}
As you can see, this is a simple behavior for a basic asyncTask. However I can not figure out why the isAdded method returns true when the app is on background and also why the app is crashing with the message:
*** Process: com.roscasend.android.testadd, PID: 2891
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:2053)
at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:2079)
at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:678)
at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:632)
at android.support.v4.app.DialogFragment.show(DialogFragment.java:143)
at com.roscasend.android.testadd.AppUtil.showErrorDialog(AppUtil.java:17)
at com.roscasend.android.testadd.BlankFragment.displayError(BlankFragment.java:51)
at com.roscasend.android.testadd.LoginAsyncTask.onPostExecute(LoginAsyncTask.java:37)
at com.roscasend.android.testadd.LoginAsyncTask.onPostExecute(LoginAsyncTask.java:15)***
Best Regards, Aurelian
Solution
IllegalStateException: Can not perform this action after onSaveInstanceState
The Android FragmentManager uses the savedInstanceState
bundle to track which fragments are currently shown to the user. If state is saved and you try to perform a fragment transaction, the system crashes because the transaction would never be seen by the user.
Newer versions of the support library offer an isStateSaved()
method on the FragmentManager that you can use to check whether or not you're in the safe window to show the dialog.
@Override
public void displayError(int messageId) {
FragmentManager fm = getFragmentManager();
if (!fm.isStateSaved()) {
AppUtil.showErrorDialog(fm, R.string.app_name);
}
}
However, this still leaves you with a problem. If your AsyncTask finishes and tries to show an error when state has already been saved, the user will never see the error.
You can have the app record that it tried to show an error, and then also perform a check when your app resumes to see if it should show the error now. We need a place to save the information that we tried to show a dialog, and it can't just be a boolean
on the activity/fragment (because that would be subject to the same state saving problem). I recommend SharedPreferences
to store the info.
Add this code to your fragment:
private static final String SHOW_DIALOG = "SHOW_DIALOG";
private SharedPreferences getSharedPreferences() {
return getActivity().getPreferences(Context.MODE_PRIVATE);
}
And then use this code to show the dialog:
@Override
public void displayError(int messageId) {
FragmentManager fm = getFragmentManager();
if (!fm.isStateSaved()) {
AppUtil.showErrorDialog(getFragmentManager(), R.string.app_name);
} else {
getSharedPreferences()
.edit()
.putBoolean(SHOW_DIALOG, true)
.apply();
}
}
And this code to show the dialog when the app resumes:
@Override
public void onResume() {
super.onResume();
SharedPreferences prefs = getSharedPreferences();
boolean triedToShowInBackground = prefs.getBoolean(SHOW_DIALOG, false);
if (triedToShowInBackground) {
AppUtil.showErrorDialog(getFragmentManager(), R.string.app_name);
prefs.edit().remove(SHOW_DIALOG).apply();
}
}
Answered By - Ben P.
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.