24

In my activities I frequently use this idiom:

@Override
public void onDestroy() {
    super.onDestroy();
    if (isFinishing() != true) return;
    // do some final cleanup since we're going away for good
}

Fragment has an onDestroy() method, but what is the equivalent of isFinishing()? Should I just check getActivity().isFinishing() from within the fragment's onDestroy()?

EDITED TO ADD:

Here are the callbacks (in order) I get under various circumstances, along with whether getActivity() returns null or non-null and, if non-null, the value of getActivity().isFinishing():

Scenario #1: DialogFragment is showing and user taps back button (i.e. need to release references):

onDismiss(): activity = non-null, finishing = false
onDestroy(): activity = non-null, finishing = false

Scenario #2: DialogFragment is showing and parent activity finishes (i.e. need to release references):

onDestroy(): activity = non-null, finishing = true
onDismiss(): activity = null, finishing = n/a

Scenario #3: DialogFragment is showing and OS temporarily destroys parent activity (i.e. should not release references):

onDestroy(): activity = non-null, finishing = false
onDismiss(): activity = null, finishing = n/a
jph
  • 2,181
  • 3
  • 30
  • 55
  • Generally you should use FragmentActivity to handle the processing of fragments, Fragments handle the UI. – Jesson Atherton Aug 22 '14 at 16:18
  • Well, one of the things this fragment does is spawn activities to acquire fairly large images. Multiple of them. Rather than serialize them when state needs to be saved I store them in a global location and serialize a reference to that location. Basically an index into a SparseArray. I need to release this reference when the fragment "goes away permanently", though, or it will be leaked permanently. Am going to add an edit to the original post with some information on what callbacks I get under different circumstances. – jph Aug 22 '14 at 16:34
  • The onDestroy should be sufficient to release the reference, perhaps use an interface with a custom callback of your own? – Jesson Atherton Aug 22 '14 at 16:36
  • Well, yes, I could simply make the decision that when the OS temporarily destroys the activity the fragment doesn't retain any "state" w.r.t. images that have already been acquired. That might be acceptable, though I'd prefer to save that state. – jph Aug 22 '14 at 16:40
  • Another option might be to let the parent activity handle the decision of whether to release the references. So, in the parent activity's onDestroy() method in the "final cleanup section" (i.e. isFinishing() = true), look up the fragment by its tag and, if it exists, call some "release references" method on the fragment. I was just hoping I could let the fragment handle all that work instead of forcing every activity to be aware of the fact that a cleanup method needs to be called. – jph Aug 22 '14 at 16:46

4 Answers4

19

Fragments have a method called isRemoving() which is true when the fragment is being removed from the Activity:

Return true if this fragment is currently being removed from its activity. This is not whether its activity is finishing, but rather whether it is in the process of being removed from its activity.

Kevin Coppock
  • 133,643
  • 45
  • 263
  • 274
  • 2
    Just a note for others: During orientation changes and user pressing back button, `isRemoving()=false`... Seems it's only called when one explicitly calls `.remove(Fragment)`? – bcorso Jul 10 '15 at 07:15
4

Thanks for the help guys. I ended up adding these to my DialogFragment:

private void doReleaseReferences() {
    // release the stuff
}

@Override
public void onDestroy() {
    super.onDestroy();
    if (getActivity().isFinishing())
        doReleaseReferences();
}

@Override
public void onDismiss(DialogInterface dialog) {
    super.onDismiss(dialog);
    if (getActivity() != null)
        doReleaseReferences();
}

Based on the callback behavior I added to the original question I think this serves my purposes. When the back button is pressed or the parent activity finishes the references are released; when the parent activity is destroyed by the OS with the intention of being revived later they are not released.

jph
  • 2,181
  • 3
  • 30
  • 55
1

If you would like to clean up fragment references on per basis you could use this helper method for fragment replacement

public abstract class BaseActivity extends AppCompatActivity {

    @Override
    protected void onDestroy() {
        if (isFinishing()) {
            notifyStackedFragmentsFinishing();
        }
        super.onDestroy();
    }

    protected void changeFragment(Fragment fragment) {
         notifyStackedFragmentsFinishing();
         getSupportFragmentManager().beginTransaction()
            .replace(R.id.fragment, fragment)
            .commit();
    }

    private void notifyStackedFragmentsFinishing() {
        List<Fragment> fragments = getSupportFragmentManager().getFragments();
        if (fragments != null && !fragments.isEmpty()) {
            for (Fragment fragment : fragments) {
                ((BaseFragment) fragment).releaseOnFinishing();
            }
        }
    }

 }

Now you can extend all your activities from BaseActivity and all your fragments from BaseFragment. Also if you stacking fragments you should probably extend more i.e. onBackPressed()

Kamil Seweryn
  • 2,072
  • 2
  • 17
  • 23
0

I think this kotlin extension function will do the trick. At least if you are using the "Navigation framework"

fun Fragment.isFinishing(): Boolean {
   val name = this.javaClass.name

   val thisEntry = findNavController().backQueue.find {
       val destination = it.destination
       destination is FragmentNavigator.Destination && destination.className == name
    }

    return thisEntry == null
}
Ola Melén
  • 445
  • 1
  • 3
  • 9