简体   繁体   中英

Correctly retaining listeners on orientation changes

Consider the following scenario:

  1. I have an Activity with UI elements that launches a DialogFragment when clicked
  2. The DialogFragment has a listener interface that the Activity provides an implementation of. Say for example, the Activity is an image editor and the DialogFragment selects a contrast - the dialog would have a OnContrastChangedListener that the Activity implements
  3. The Activity has implemented this interface to update views in its UI. Continuing the image editor example, the Activity implements the OnContrastChangedListener to update its preview view - something like this:

     contrastDialog.setOnContrastChangedListener(new OnContrastChangedListener { @Override public void OnContrastChanged(int newContrast) { getPreviewView().updateWithContrast(newContrast); } }); 
  4. The orientation is changed, and everything is recreated and the listener saved and restored correctly using the methods recommended here (listener is saved in a Fragment and restored when the lifecycle is restoring state).

The problem is the listener interface now does not work. The function getPreviewView() is now returning null even though when called anywhere else in the Activity it returns the correct value

Excuse the poor terminology (my knowledge in compiling and bytecode is limited), but I can grasp what has happened. The interface has been compiled with the getPreviewView() version that returned the preview view that was destroyed on the orientation change, and this has since been released / garbage collected / is now null .

My question is, is there a way in Java to make the interface compile expecting the values / functions to change - much like the volatile keyword in C (I am expecting there isn't)? In that case, what's the best approach for getting around this type of situation? I have considered the following:

  • Create the DialogFragment (and its interface ) in the code that is rerun when the Activity is recreated. This is fine for things like OnClickListeners for Buttons as they are definitely created. But this DialogFragment is only created when a button is pressed, so this approach means every dialog for the screen is created each time the Activity is - this seems wasteful given they may not even be run
  • Create all possible interfaces for the Activity every time and save them in member variables, then use these interfaces when the DialogFragments are requested to be created by the event. Same comments as above - seems wasteful creating every possible interface just in case it is run.
  • Keep some hacky "open dialog state" member variables in the Activity that guide the recreation of the interfaces . Hacky and creates a tie between the Activity and the DialogFragment which isn't great practice.

As you can see, all options involve recreation that is wasteful to some extent - is there a way to reuse the existing interface implementation?

EDIT: Options 1 and 2 won't work because they need a link to the existing Dialog . This is all doable but it is leaning more and more towards the hacked together option of having 'current Dialog' variables, getting the DialogFragment with FragmentManager when the activity is restarted, casting it appropriately based on the 'current Dialog' variable, recreating the listener. Is there a less messy way?

the onAttach onDetach method is good and I like using it, sometimes, when I know there will be more developers in the code I don't even cast it blindly, but I do a check like this:

if(activity instanceof MyInterface){
   interface = (MyInterface) activity;
} else{
   thrown new RuntimeException("Dear colleague, this fragment was meant to have the activity implementing MyInterface or else a bunch of other stuff won't work, please go back to your code and add the interface");
}

but as a different resort, you can also re-set the interface when the fragment is recreated. For example, on the activity onCreate

if(savedInstanceState != null){
    mDialogFrag = getSupportFragmentManager().findFragmentByTag(MyDialogFrag.TAG);
    if(mDialogFrag != null)
       mDialogFrag.setListener(... the interface   ...);
}

I know that's also not the best separation of objects, but the fact is that getPreviewView() NEEDS the current activity to proper operate, so you NEED to pass this reference again when everybody gets destroyed n rebuilt.

Those are just different ways of doing it.

If the activity will always have the implementation of the interface, would it be possible to set your listener in the DialogFragment's onAttach()? This will ensure that when it is destroyed and recreated, it will have the most up-to-date reference, like:

public void onAttach(Activity activity) {
    super.onAttach(activity);
    contrastChangedListener = (OnContrastChangedListener)activity;
}

public void onDetach() {
    super.onDetach();
    contrastChangedListener = null;

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM