31

I have a DialogFragment which creates a DatePickerDialog. I'm using a static method called newInstance to set the initial values in order to use the default empty constructor. However, how am I supposed to set the listener? After the screen rotation, when clicking in the "Done" button, the listener doesn't do anything since it does not exist.

public class DatePickerFragment extends DialogFragment {
    public static final String ARG_YEAR = "year";
    public static final String ARG_MONTH = "month";
    public static final String ARG_DAY = "day";

    private OnDateSetListener listener_;

    public static DatePickerFragment newInstance(OnDateSetListener listener, int year, int month, int day) {
        final DatePickerFragment date_picker = new DatePickerFragment();
        date_picker.setListener(listener);

        final Bundle arguments = new Bundle();
        arguments.putInt(ARG_YEAR, year);
        arguments.putInt(ARG_MONTH, month);
        arguments.putInt(ARG_DAY, day);
        date_picker.setArguments(arguments);

        return date_picker;
    }

    private void setListener(OnDateSetListener listener) {
        listener_ = listener;
    }

    @Override
    public Dialog onCreateDialog(Bundle saved_instance_state) {
        final Bundle arguments = getArguments();
        final int year = arguments.getInt(ARG_YEAR);
        final int month = arguments.getInt(ARG_MONTH);
        final int day = arguments.getInt(ARG_DAY);

        return new DatePickerDialog(getActivity(), listener_, year, month, day);
    }
}
Renato Rodrigues
  • 1,038
  • 1
  • 18
  • 35

3 Answers3

67

However, how am I supposed to set the listener?

You update the listener reference in the onCreate method of the Activity:

private OnDateSetListener mOds = new OnDateSetListener() {

    @Override
    public void onDateSet(DatePicker view, int year, int monthOfYear,
            int dayOfMonth) {
             // do important stuff
    }
};

and in the onCreate method:

if (savedInstanceState != null) {
    DatePickerFragment dpf = (DatePickerFragment) getSupportFragmentManager()
            .findFragmentByTag("theTag?");
    if (dpf != null) {
        dpf.setListener(mOds);
    }
}
user
  • 86,916
  • 18
  • 197
  • 190
  • 1
    +1, also for clarifying, the important thing is to use the dialog fragment's tag to reapply the listener. This tag is specified in the second argument of the dialog fragments `show` method. – rekaszeru Aug 02 '17 at 07:50
  • 1
    DialogFragment is not a support library fragment so instead of `DatePickerFragment dpf = (DatePickerFragment) getSupportFragmentManager() .findFragmentByTag("theTag?");` `DatePickerFragment dpf = (DatePickerFragment) getFragmentManager() .findFragmentByTag("theTag?");` should be used – Velmurugan V Oct 15 '17 at 14:49
  • @VelmuruganV DialogFragment is available in the support library https://developer.android.com/reference/android/support/v4/app/DialogFragment.html – user Oct 15 '17 at 17:16
  • 2
    oops sorry i didn't know that. Thanks for correcting me @Luksprog – Velmurugan V Oct 16 '17 at 05:10
  • It is awful that we have to deal with these things. We should all go and report an issue to Google to go and make a nice API that doesn't force us to work around everything. – milosmns Aug 01 '19 at 08:52
  • Does anyone have any update on this? Since many years we had this problem. – jettimadhuChowdary Apr 28 '22 at 10:17
9

In my opinion there is a more efficient way of doing this, using the Fragment lifecycle. You can use the Fragment lifecycle callbacks onAttach() and onDetach() to automatically cast the activity as a Listener like so:

@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);
    try {
        _listener = (OnDateSetListener)activity;
    } catch(ClassCastException e) {
        // check if _listener is null before using,
        // or throw new ClassCastException("hosting activity must implement OnDateSetListener");
    }
}

@Override
public void onDetach() {
    super.onDetach();
    _listener = null;
}

This technique is officially documented here

d370urn3ur
  • 1,736
  • 4
  • 17
  • 24
  • 1
    Yes, but in this way you cannot use the same class (let me say a DialogFragment) more times in an Activity with different Listeners... – ARLabs Dec 18 '15 at 10:34
  • 1
    Why not? Just implement whatever listener interface you want on the Activity and cast it to different listener instances in the DialogFragment. If you want to make some optional you can just have more complex ClassCast error handling. – d370urn3ur Dec 18 '15 at 10:54
  • 1
    I mean, if the same Activity needs to use the same DialogFragment two times and (as you suggested), Activity inherits from the listener, when the listener method in the activity is called you do not know which is the case... I think it's better to use two different listener instances (they cannot be the activity). What I said rely on the hypotesis that, for good design, we avoid circular dependencies and the DialogFragment define it's own listener type and it does not know the Activity specialization. – ARLabs Jan 25 '16 at 16:49
  • I don't understand what you mean: "when the listener method is called you do not know which is the case". You mean, which DialogFragment instance called the listener method? Why can't you just pass on identifying information about the DialogFragment in the listener method? I'd have to see some code to understand your use case. The fact of the matter is, a DialogFragment is already intimately tied to an Activity, and this is the easiest way to handle the most common use of DialogFragments. It's also officially documented so it's not surprising to encounter it in production apps. – d370urn3ur Jan 26 '16 at 11:15
  • @d370urn3ur, probably ARLabs means that the Activity can inflate many fragments extending the same DialogFragment. In this case a listener may vary. – CoolMind Oct 05 '16 at 12:18
  • @ARLabs, agree with you. I suppose, in this case we should add an `Intent` or `setArguments()` for each instance of `DialogFragment`. There we will pass an id of the instance (a random number that won't change after rotation). We should pass this id to a listener as a parameter. In `Activity` we will receive this id and check what `DialogFragment` has called the listener. – CoolMind Apr 03 '19 at 14:20
2

The answer of Luksprog is correct, I just want to point out, the key of the solution is the findFragmentByTag() function. Because the activity will be also recreated after screen rotation, you cannot call the setter function of its member Fragment variable, instead you should find the old fragment instance with this function.

Btw, the tag is the second parameter when you call DialogFragment.show().

cn123h
  • 2,302
  • 1
  • 21
  • 16
  • I found that if you call `DialogFragment` from `Fragment` and after screen rotation call `fragmentManager.findFragmentByTag()` in `onCreateView()`, it may return `null`. In this case try to call `childFragmentManager.findFragmentByTag()`. Or see the answer of **@d370urn3ur**. In this case you should set a callback inside an `Activity`. – CoolMind Apr 03 '19 at 14:37