5

So I've been trying to understand how to solve this issue, but I can't really fix it. Right now I have one fragment with one button. When you press this button it will launch a custom DialogFragment with ok/cancel buttons.

If I press the ok button it will launch another custom DialogFragment, this time it's a ProgressDialog fragment. The problem is, when the ok/cancel dialog appears if I rotate and then press the ok button to then call the ProgressDialog fragment I get this error. If I only rotate while the progressdialog fragment is showing there's no issue at all. I'm using the support package v4. Here's the classes:

MainActivity:

public class MainActivity extends FragmentActivity implements OnFragmentAttachedListener, Callbacks{

boolean mResumed = false;

@Override
public void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    FragmentManager fragmentManager = getSupportFragmentManager();
    FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

    fragmentTransaction.add(R.id.main_id, new EmptyFragmentWithCallbackOnResume());
    fragmentTransaction.commitAllowingStateLoss();

}
@Override
public void onTaskFinished()
{
    // Hooray. A toast to our success.
    Toast.makeText(this, "Task finished!", Toast.LENGTH_LONG).show();
    // NB: I'm going to blow your mind again: the "int duration" parameter of makeText *isn't*
    // the duration in milliseconds. ANDROID Y U NO ENUM? 
}

@Override
public void OnFragmentAttached() {

} }

okcancel dialogfragment:

public class OkCancelDialogFragment<T> extends DialogFragment {

public final static String TITLE="title";

private OkCancelDialogEvents<T> buttonEvents;
private T[] params;


public OkCancelDialogFragment(String title, OkCancelDialogEvents<T> buttonEvents, T... params) {

    this.buttonEvents=buttonEvents;

    Bundle args = new Bundle();
    args.putString(TITLE, title);
    this.setArguments(args);

    this.params=params;

}


@Override
public void onCreate(Bundle savedInstanceState) {
    // TODO Auto-generated method stub
    super.onCreate(savedInstanceState);
    this.setRetainInstance(true);
}


@Override
public void onDestroyView() {
  if (getDialog() != null && getRetainInstance())
    getDialog().setDismissMessage(null);
  super.onDestroyView();
}


@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {

     String title = getArguments().getString(TITLE);
     return new AlertDialog.Builder(getActivity())
    //.setIcon(R.drawable.alert_dialog_icon)
     .setTitle(title)
     .setPositiveButton("ok",
         new DialogInterface.OnClickListener() {
             public void onClick(DialogInterface dialog, int whichButton) {
                 buttonEvents.onPositiveClick(params);
             }
         }
     )
     .setNegativeButton("cancel",
         new DialogInterface.OnClickListener() {
             public void onClick(DialogInterface dialog, int whichButton) {
                 buttonEvents.onNegativeClick();
             }
         }
     )
     .create(); }} 

progress dialog fragment:

public class TaskFragment extends DialogFragment{
// The task we are running.
GenericTask<?,?> mTask;
ProgressDialog mProgressDialog;
String title, message;

public void setTask(MyTask task)
{
    mTask = task;

    // TellsetFragment the AsyncTask to call updateProgress() and taskFinished() on this fragment.

    mTask.setFragment(this);
}


public void setTitleMessage(String title, String message){
    this.title=title;
    this.message=message;       
}


@Override
public void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);

    // Retain this instance so it isn't destroyed when MainActivity and
    // MainFragment change configuration.
    setRetainInstance(true);

}

@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {

    mProgressDialog= new ProgressDialog(getActivity());
    mProgressDialog.setTitle(title);
    mProgressDialog.setMessage(message);
    mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
    mProgressDialog.setCanceledOnTouchOutside(false);

    return mProgressDialog;
}

// This is to work around what is apparently a bug. If you don't have it
// here the dialog will be dismissed on rotation, so tell it not to dismiss.
@Override
public void onDestroyView()
{
    if (getDialog() != null && getRetainInstance())
        getDialog().setDismissMessage(null);
    super.onDestroyView();
}

// Also when we are dismissed we need to cancel the task.
@Override
public void onDismiss(DialogInterface dialog)
{
    super.onDismiss(dialog);
    // If true, the thread is interrupted immediately, which may do bad things.
    // If false, it guarantees a result is never returned (onPostExecute() isn't called)
    // but you have to repeatedly call isCancelled() in your doInBackground()
    // function to check if it should exit. For some tasks that might not be feasible.
    if (mTask != null)
        mTask.cancel(false);

    // You don't really need this if you don't want.
    if (getTargetFragment() != null)
        getTargetFragment().onActivityResult(MainFragment.TASK_FRAGMENT, Activity.RESULT_CANCELED, null);
}

@Override
public void onResume()
{
    super.onResume();
    // This is a little hacky, but we will see if the task has finished while we weren't
    // in this activity, and then we can dismiss ourselves.
    if (mTask == null)
        dismiss();
}

// This is called by the AsyncTask.
public void updateProgress(int percent)
{
    mProgressDialog.setProgress(percent);
}

// This is also called by the AsyncTask.
public void taskFinished()
{
    // Make sure we check if it is resumed because we will crash if trying to dismiss the dialog
    // after the user has switched to another app.
    if (isResumed())
        dismiss();

    // If we aren't resumed, setting the task to null will allow us to dimiss ourselves in
    // onResume().
    mTask = null;

    // Tell the fragment that we are done.
    if (getTargetFragment() != null)
        getTargetFragment().onActivityResult(MainFragment.TASK_FRAGMENT, Activity.RESULT_OK, null);
}

}

mainFragment:

public class MainFragment extends Fragment implements OkCancelDialogEvents<Void>, OnClickListener{
// This code up to onDetach() is all to get easy callbacks to the Activity. 
private Callbacks mCallbacks = sDummyCallbacks;

private static Callbacks sDummyCallbacks = new Callbacks()
{
    public void onTaskFinished() { }
};

@Override
public void onAttach(Activity activity)
{
    super.onAttach(activity);
    if (!(activity instanceof Callbacks))
    {
        throw new IllegalStateException("Activity must implement fragment's callbacks.");
    }
    mCallbacks = (Callbacks) activity;
}

@Override
public void onDetach()
{
    super.onDetach();
    mCallbacks = sDummyCallbacks;
}

// Save a reference to the fragment manager. This is initialised in onCreate().
private FragmentManager mFM;

// Code to identify the fragment that is calling onActivityResult(). We don't really need
// this since we only have one fragment to deal with.
static final int TASK_FRAGMENT = 0;

// Tag so we can find the task fragment again, in another instance of this fragment after rotation.
static final String TASK_FRAGMENT_TAG = "task";

@Override
public void onCreate(Bundle savedInstanceState)
{
    //this.setRetainInstance(true);
    super.onCreate(savedInstanceState);
    // At this point the fragment may have been recreated due to a rotation,
    // and there may be a TaskFragment lying around. So see if we can find it.
    mFM = getFragmentManager();
    // Check to see if we have retained the worker fragment.
    TaskFragment taskFragment = (TaskFragment)mFM.findFragmentByTag(TASK_FRAGMENT_TAG);

    if (taskFragment != null)
    {
        // Update the target fragment so it goes to this fragment instead of the old one.
        // This will also allow the GC to reclaim the old MainFragment, which the TaskFragment
        // keeps a reference to. Note that I looked in the code and setTargetFragment() doesn't
        // use weak references. To be sure you aren't leaking, you may wish to make your own
        // setTargetFragment() which does.
        taskFragment.setTargetFragment(this, TASK_FRAGMENT);
    }

}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState)
{
    return inflater.inflate(R.layout.fragment_main, container, false);
}

@Override
public void onActivityCreated(Bundle savedInstanceState) {

    super.onActivityCreated(savedInstanceState);
    if(savedInstanceState!=null)
        Toast.makeText(getActivity(), savedInstanceState.getString("documents"), Toast.LENGTH_SHORT).show();
}

@Override
public void onSaveInstanceState(Bundle outState) {

    super.onSaveInstanceState(outState);
    outState.putString("documents", "teste");
}

@Override
public void onViewCreated(View view, Bundle savedInstanceState)
{
    super.onViewCreated(view, savedInstanceState);

    // Callback for the "start task" button. I originally used the XML onClick()
    // but it goes to the Activity instead.
    view.findViewById(R.id.taskButton).setOnClickListener(this);
}

@Override
public void onClick(View v)
{

    OkCancelDialogFragment<Void> dialog = new OkCancelDialogFragment<Void>("Teste", this);
    dialog.setTargetFragment(this, 2);
    dialog.show(getFragmentManager(), "basic_dialog");


}

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data)
{
    if (requestCode == TASK_FRAGMENT && resultCode == Activity.RESULT_OK)
    {
        // Inform the activity. 
        mCallbacks.onTaskFinished();
    }
}


@Override
public void onPositiveClick(Void... params) {

    // We only have one click listener so we know it is the "Start Task" button.

    // We will create a new TaskFragment.
    TaskFragment taskFragment = new TaskFragment();
    // And create a task for it to monitor. In this implementation the taskFragment
    // executes the task, but you could change it so that it is started here.

    MyTask task=new MyTask();
    task.execute("one","two");

    taskFragment.setTask(task);
    taskFragment.setTitleMessage("File Download", "Downloading...");
    // And tell it to call onActivityResult() on this fragment.
    taskFragment.setTargetFragment(this, TASK_FRAGMENT);
    // Show the fragment.
    // I'm not sure which of the following two lines is best to use but this one works well.
    taskFragment.show(mFM, TASK_FRAGMENT_TAG);
    //mFM.beginTransaction().add(taskFragment, TASK_FRAGMENT_TAG).commit();

}

@Override
public void onNegativeClick() {


}}

Here's the errors:

   12-12 11:24:52.144: E/AndroidRuntime(2451): FATAL EXCEPTION: main
12-12 11:24:52.144: E/AndroidRuntime(2451): java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
12-12 11:24:52.144: E/AndroidRuntime(2451):     at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1327)
12-12 11:24:52.144: E/AndroidRuntime(2451):     at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1338)
12-12 11:24:52.144: E/AndroidRuntime(2451):     at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:595)
12-12 11:24:52.144: E/AndroidRuntime(2451):     at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:574)
12-12 11:24:52.144: E/AndroidRuntime(2451):     at android.support.v4.app.DialogFragment.show(DialogFragment.java:127)
12-12 11:24:52.144: E/AndroidRuntime(2451):     at com.example.progressdialog.MainFragment.onPositiveClick(MainFragment.java:149)
12-12 11:24:52.144: E/AndroidRuntime(2451):     at com.example.progressdialog.MainFragment.onPositiveClick(MainFragment.java:1)
12-12 11:24:52.144: E/AndroidRuntime(2451):     at com.example.progressdialog.OkCancelDialogFragment$1.onClick(OkCancelDialogFragment.java:56)
12-12 11:24:52.144: E/AndroidRuntime(2451):     at com.android.internal.app.AlertController$ButtonHandler.handleMessage(AlertController.java:196)
12-12 11:24:52.144: E/AndroidRuntime(2451):     at android.os.Handler.dispatchMessage(Handler.java:99)
12-12 11:24:52.144: E/AndroidRuntime(2451):     at android.os.Looper.loop(Looper.java:123)
12-12 11:24:52.144: E/AndroidRuntime(2451):     at android.app.ActivityThread.main(ActivityThread.java:4627)
12-12 11:24:52.144: E/AndroidRuntime(2451):     at java.lang.reflect.Method.invokeNative(Native Method)
12-12 11:24:52.144: E/AndroidRuntime(2451):     at java.lang.reflect.Method.invoke(Method.java:521)
12-12 11:24:52.144: E/AndroidRuntime(2451):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:858)
12-12 11:24:52.144: E/AndroidRuntime(2451):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
12-12 11:24:52.144: E/AndroidRuntime(2451):     at dalvik.system.NativeStart.main(Native Method)
user
  • 86,916
  • 18
  • 197
  • 190
Maxrunner
  • 1,955
  • 4
  • 24
  • 41
  • Is there any reason for using `setRetainInstance` in your fragments? – user Dec 12 '12 at 14:20
  • Well i'm only using the setReatainInstance on the dialogFragments. Wont they dissapear on rotation if they're set to false? – Maxrunner Dec 12 '12 at 15:40
  • You could try and see for yourself :). The setRetainInstance means the fragment's instance will be retained after a configuration change so the fragment will not be re instantiated again. – user Dec 12 '12 at 15:44
  • yes, i know that, but i've been trying to see the solutions, and i dont understand them fully. This one, http://stackoverflow.com/questions/11900785/illegalstateexception-fragment-support-library, says that i should override the onResumeFragments method but i call the dialogfragment from the main fragment. – Maxrunner Dec 12 '12 at 16:01
  • The error ends up in this line: taskFragment.show(mFM, TASK_FRAGMENT_TAG); its on the onPositiveClick from the okcancel dialog. – Maxrunner Dec 12 '12 at 17:18
  • The exception comes from your code, I've made a simple test and what you're trying to do works(at least how I understood from your question). Can you provide a runnable sample project with code that throws that exception so I can look at it(something basic)? – user Dec 12 '12 at 17:20
  • What i've done right now is using one dialogfragment for everything, but i will try to give the last code. How can i put it here? – Maxrunner Dec 13 '12 at 16:37
  • Anyway, Luksprog, can you post your test too? I'll try to post the sample project in here when i get home tonight. regards, – Maxrunner Dec 13 '12 at 16:49
  • You could put it anywhere, pastebin, gist etc. See my small test here https://github.com/luksprog/DroidPlayground/blob/master/src/com/luksprog/dp/fragment/NestingDialogFragmentsSample.java – user Dec 13 '12 at 18:36
  • Here it is: https://www.dropbox.com/s/b96ab3xf9rcjjoe/DefaultProgressDialog.7z – Maxrunner Dec 14 '12 at 00:13

1 Answers1

1

That seems to be a bug with the compatibility package(which isn't yet solved). Anyway, you could avoid that bug by modifying your code to better handle the communication between the fragments. I've modified your sample project(which can be found here). Regarding this, I don't know how simple is your sample, but if all the callbacks point back to the activity then you should let the activity class handle the fragments(starting the dialogs for example) and the communication between them, as it "sees" and knows the state of all the fragments in it.

user
  • 86,916
  • 18
  • 197
  • 190
  • Hi there Luksprog. I'm checking your code alteration and it seems the main problem was with the constructor for the okcanceldialog, can you confirm this?you say i should use the bundle for passing params to it?was this really the main issue?Also I didn't understood the comment in the call to the second dialog fragment. can you elaborate? regards, – Maxrunner Dec 15 '12 at 16:24
  • @Maxrunner No, the error was due to a bug. I've made some alterations because your fragments weren't properly built and didn't communicate well. You shouldn't have a `Fragment` with constructors with parameters because android will not be able to instantiate that fragment when it needs(you didn't see this because you used `setRetainInstance(true)`). Yes, you should use a `Bundle` to pass the parameters, if you have anything heavy I've added an extra method to the `OkCancelDialogEvents` interface which the `OkCancelDialogFragment` can call on `buttonevents` to get extra data. – user Dec 15 '12 at 16:43
  • @Maxrunner You shouldn't create the task in the `MainFragment`, instead you should pass the data(that the task needs) to the `TaskFragment` and let it figure it out what to do with it. Also, I've seen that all callbacks finally point to the `FragmentActivity` class so you may want the activity handle the fragments, by passing the data and showing them. – user Dec 15 '12 at 16:45
  • Ok,so you're saying that the Asynctask(MyTask) should be created by the TaskFragment and not on the MainFragment? THe callbacks to the activity are there because of sample reasons. i dont really need it. – Maxrunner Dec 15 '12 at 19:14
  • @Maxrunner Yes, the `AsyncTask` should be built and managed by the `TaskFragment` because it is virtually tied to it(there is no reason for other parts of you code to mess with this). From the `MainFragment` will just pass the required data or objects for the `AsyncTask` and from that moment the `TaskFragment` will take care of it. – user Dec 16 '12 at 06:23
  • Thanks. Still what was the deciding factor for this bug? – Maxrunner Dec 16 '12 at 13:48
  • @Maxrunner I don't know. Maybe you'll get an answer on the bug tracker(if I'm not mistaken you posted a question there). – user Dec 16 '12 at 16:59
  • Do you mean the https://code.google.com site? Anyway if you set the mainfragment onCreate() with the setRetainInstance to true it will give the same error – Maxrunner Dec 18 '12 at 19:01
  • @Maxrunner Yes I was referring to the code.google.com. It could throw that exception but I don't see why you would use `setRetainInstance` on that fragment. `setRetainInstance` should be used in select cases like for example, when having an invisible fragment which has the task of loading data and should survive a configuration change. – user Dec 18 '12 at 19:47
  • Also forgot to ask why the change in the onDismiss method of the TaskFragment? Soz for the late reply... – Maxrunner Dec 21 '12 at 15:52
  • @Maxrunner If I remember right I put the `mResumingCold` flag so we don't trigger the `onActivityResult` more than once for a task that finished already and(presumably) already called the `taskFinished()` method). The `mEndStatus` is a flag to decide if the task finished alright(the only place that flag is set to `false` is on the `taskFinished` callback, for any other case when the dialog is dismissed the `onActivityResult` will be called with RESULT_CANCEL). This is what I remembered , it may not be correct in all situation. – user Dec 21 '12 at 16:41
  • Forgot to ask you something, in the TaskFragment you never put the mTask variable to null, isn't that a problem since the Asynctask as a reference to the TaskFragment too? – Maxrunner Jan 04 '13 at 17:33
  • @Maxrunner Please start a new question if you have questions, we can't comment forever on the same question and we are out of the scope of the original question. Why set `mTask` to `null`? It isn't a problem because of a possible memory leak as the `TaskFragment` is set to retain it's instance so the `AsyncTask` always has the reference to the correct TaskFragment. The circular reference TaskFragment-AsyncTask isn't a problem. And from the code I assume you can't start a new AsyncTask without closing TaskFragment which will also clean `mTask`. – user Jan 04 '13 at 18:51