[android] Callback to a Fragment from a DialogFragment

Question: How does one create a callback from a DialogFragment to another Fragment. In my case, the Activity involved should be completely unaware of the DialogFragment.

Consider I have

public class MyFragment extends Fragment implements OnClickListener

Then at some point I could do

DialogFragment dialogFrag = MyDialogFragment.newInstance(this);
dialogFrag.show(getFragmentManager, null);

Where MyDialogFragment looks like

protected OnClickListener listener;
public static DialogFragment newInstance(OnClickListener listener) {
    DialogFragment fragment = new DialogFragment();
    fragment.listener = listener;
    return fragment;
}

But there is no guarantee that the listener will be around if the DialogFragment pauses and resumes through its lifecycle. The only guarantees in a Fragment are those passed in through a Bundle via setArguments and getArguments.

There is a way to reference the activity if it should be the listener:

public Dialog onCreateDialog(Bundle bundle) {
    OnClickListener listener = (OnClickListener) getActivity();
    ....
    return new AlertDialog.Builder(getActivity())
        ........
        .setAdapter(adapter, listener)
        .create();
}

But I don't want the Activity to listen for events, I need a Fragment. Really, it could be any Java object that implements OnClickListener.

Consider the concrete example of a Fragment that presents an AlertDialog via DialogFragment. It has Yes/No buttons. How can I send these button presses back to the Fragment that created it?

The answer is


Updated:

I made a library based on my gist code that generates those casting for you by using @CallbackFragment and @Callback.

https://github.com/zeroarst/callbackfragment.

And the example give you the example that send a callback from a fragment to another fragment.

Old answer:

I made a BaseCallbackFragment and annotation @FragmentCallback. It currently extends Fragment, you can change it to DialogFragment and will work. It checks the implementations with the following order: getTargetFragment() > getParentFragment() > context (activity).

Then you just need to extend it and declare your interfaces in your fragment and give it the annotation, and the base fragment will do the rest. The annotation also has a parameter mandatory for you to determine whether you want to force the fragment to implement the callback.

public class EchoFragment extends BaseCallbackFragment {

    private FragmentInteractionListener mListener;

    @FragmentCallback
    public interface FragmentInteractionListener {
        void onEcho(EchoFragment fragment, String echo);
    }
}

https://gist.github.com/zeroarst/3b3f32092d58698a4568cdb0919c9a93


I am getting result to Fragment DashboardLiveWall(calling fragment) from Fragment LiveWallFilterFragment(receiving fragment) Like this...

 LiveWallFilterFragment filterFragment = LiveWallFilterFragment.newInstance(DashboardLiveWall.this ,"");

 getActivity().getSupportFragmentManager().beginTransaction(). 
 add(R.id.frame_container, filterFragment).addToBackStack("").commit();

where

public static LiveWallFilterFragment newInstance(Fragment targetFragment,String anyDummyData) {
        LiveWallFilterFragment fragment = new LiveWallFilterFragment();
        Bundle args = new Bundle();
        args.putString("dummyKey",anyDummyData);
        fragment.setArguments(args);

        if(targetFragment != null)
            fragment.setTargetFragment(targetFragment, KeyConst.LIVE_WALL_FILTER_RESULT);
        return fragment;
    }

setResult back to calling fragment like

private void setResult(boolean flag) {
        if (getTargetFragment() != null) {
            Bundle bundle = new Bundle();
            bundle.putBoolean("isWorkDone", flag);
            Intent mIntent = new Intent();
            mIntent.putExtras(bundle);
            getTargetFragment().onActivityResult(getTargetRequestCode(),
                    Activity.RESULT_OK, mIntent);
        }
    }

onActivityResult

@Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        if (resultCode == Activity.RESULT_OK) {
            if (requestCode == KeyConst.LIVE_WALL_FILTER_RESULT) {

                Bundle bundle = data.getExtras();
                if (bundle != null) {

                    boolean isReset = bundle.getBoolean("isWorkDone");
                    if (isReset) {

                    } else {
                    }
                }
            }
        }
    }

You should define an interface in your fragment class and implement that interface in its parent activity. The details are outlined here http://developer.android.com/guide/components/fragments.html#EventCallbacks . The code would look similar to:

Fragment:

public static class FragmentA extends DialogFragment {

    OnArticleSelectedListener mListener;

    // Container Activity must implement this interface
    public interface OnArticleSelectedListener {
        public void onArticleSelected(Uri articleUri);
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
            mListener = (OnArticleSelectedListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString() + " must implement OnArticleSelectedListener");
        }
    }
}

Activity:

public class MyActivity extends Activity implements OnArticleSelectedListener{

    ...
    @Override
    public void onArticleSelected(Uri articleUri){

    }
    ...
}

The Communicating with Other Fragments guide says the Fragments should communicate through the associated Activity.

Often you will want one Fragment to communicate with another, for example to change the content based on a user event. All Fragment-to-Fragment communication is done through the associated Activity. Two Fragments should never communicate directly.


According to the official documentation:

Fragment#setTargetFragment

Optional target for this fragment. This may be used, for example, if this fragment is being started by another, and when done wants to give a result back to the first. The target set here is retained across instances via FragmentManager#putFragment.

Fragment#getTargetFragment

Return the target fragment set by setTargetFragment(Fragment, int).

So you can do this:

// In your fragment

public class MyFragment extends Fragment implements OnClickListener {
    private void showDialog() {
        DialogFragment dialogFrag = MyDialogFragment.newInstance(this);
        // Add this
        dialogFrag.setTargetFragment(this, 0);
        dialogFrag.show(getFragmentManager, null);
    }
    ...
}

// then

public class MyialogFragment extends DialogFragment {
    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        // Then get it
        Fragment fragment = getTargetFragment();
        if (fragment instanceof OnClickListener) {
            listener = (OnClickListener) fragment;
        } else {
            throw new RuntimeException("you must implement OnClickListener");
        }
    }
    ...
}

Maybe a bit late, but may help other people with the same question like I did.

You can use setTargetFragment on Dialog before showing, and in dialog you can call getTargetFragment to get the reference.


I was facing a similar problem. The solution that I found out was :

  1. Declare an interface in your DialogFragment just like James McCracken has explained above.

  2. Implement the interface in your activity (not fragment! That is not a good practice).

  3. From the callback method in your activity, call a required public function in your fragment which does the job that you want to do.

Thus, it becomes a two-step process : DialogFragment -> Activity and then Activity -> Fragment


TargetFragment solution doesn't seem the best option for dialog fragments because it may create IllegalStateException after application get destroyed and recreated. In this case FragmentManager couldn't find the target fragment and you will get an IllegalStateException with a message like this:

"Fragment no longer exists for key android:target_state: index 1"

It seems like Fragment#setTargetFragment() is not meant for communication between a child and parent Fragment, but rather for communication between sibling-Fragments.

So alternative way is to create dialog fragments like this by using the ChildFragmentManager of the parent fragment, rather then using the activities FragmentManager:

dialogFragment.show(ParentFragment.this.getChildFragmentManager(), "dialog_fragment");

And by using an Interface, in onCreate method of the DialogFragment you can get the parent fragment:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    try {
        callback = (Callback) getParentFragment();
    } catch (ClassCastException e) {
        throw new ClassCastException("Calling fragment must implement Callback interface");
    }
}

Only thing left is to call your callback method after these steps.

For more information about the issue, you can check out the link: https://code.google.com/p/android/issues/detail?id=54520


I followed this simple steps to do this stuff.

  1. Create interface like DialogFragmentCallbackInterface with some method like callBackMethod(Object data). Which you would calling to pass data.
  2. Now you can implement DialogFragmentCallbackInterface interface in your fragment like MyFragment implements DialogFragmentCallbackInterface
  3. At time of DialogFragment creation set your invoking fragment MyFragment as target fragment who created DialogFragment use myDialogFragment.setTargetFragment(this, 0) check setTargetFragment (Fragment fragment, int requestCode)

    MyDialogFragment dialogFrag = new MyDialogFragment();
    dialogFrag.setTargetFragment(this, 1); 
    
  4. Get your target fragment object into your DialogFragment by calling getTargetFragment() and cast it to DialogFragmentCallbackInterface.Now you can use this interface to send data to your fragment.

    DialogFragmentCallbackInterface callback = 
               (DialogFragmentCallbackInterface) getTargetFragment();
    callback.callBackMethod(Object data);
    

    That's it all done! just make sure you have implemented this interface in your fragment.


I solved this in an elegant way with RxAndroid. Receive an observer in the constructor of the DialogFragment and suscribe to observable and push the value when the callback being called. Then, in your Fragment create an inner class of the Observer, create an instance and pass it in the constructor of the DialogFragment. I used WeakReference in the observer to avoid memory leaks. Here is the code:

BaseDialogFragment.java

import java.lang.ref.WeakReference;

import io.reactivex.Observer;

public class BaseDialogFragment<O> extends DialogFragment {

    protected WeakReference<Observer<O>> observerRef;

    protected BaseDialogFragment(Observer<O> observer) {
        this.observerRef = new WeakReference<>(observer);
   }

    protected Observer<O> getObserver() {
    return observerRef.get();
    }
}

DatePickerFragment.java

public class DatePickerFragment extends BaseDialogFragment<Integer>
    implements DatePickerDialog.OnDateSetListener {


public DatePickerFragment(Observer<Integer> observer) {
    super(observer);
}

@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
    // Use the current date as the default date in the picker
    final Calendar c = Calendar.getInstance();
    int year = c.get(Calendar.YEAR);
    int month = c.get(Calendar.MONTH);
    int day = c.get(Calendar.DAY_OF_MONTH);

    // Create a new instance of DatePickerDialog and return it
    return new DatePickerDialog(getActivity(), this, year, month, day);
}

@Override
public void onDateSet(DatePicker view, int year, int month, int dayOfMonth) {
        if (getObserver() != null) {
            Observable.just(month).subscribe(getObserver());
        }
    }
}

MyFragment.java

//Show the dialog fragment when the button is clicked
@OnClick(R.id.btn_date)
void onDateClick() {
    DialogFragment newFragment = new DatePickerFragment(new OnDateSelectedObserver());
    newFragment.show(getFragmentManager(), "datePicker");
}
 //Observer inner class
 private class OnDateSelectedObserver implements Observer<Integer> {

    @Override
    public void onSubscribe(Disposable d) {

    }

    @Override
    public void onNext(Integer integer) {
       //Here you invoke the logic

    }

    @Override
    public void onError(Throwable e) {

    }

    @Override
    public void onComplete() {

    }
}

You can see the source code here: https://github.com/andresuarezz26/carpoolingapp


Kotlin guys here we go!

So the problem we have is that we created an activity, MainActivity, on that activity we created a fragment, FragmentA and now we want to create a dialog fragment on top of FragmentA call it FragmentB. How do we get the results from FragmentB back to FragmentA without going through MainActivity?

Note:

  1. FragmentA is a child fragment of MainActivity. To manage fragments created in FragmentA we will use childFragmentManager which does that!
  2. FragmentA is a parent fragment of FragmentB, to access FragmentA from inside FragmentB we will use parenFragment.

Having said that, inside FragmentA,

class FragmentA : Fragment(), UpdateNameListener {
    override fun onSave(name: String) {
        toast("Running save with $name")
    }

    // call this function somewhere in a clickListener perhaps
    private fun startUpdateNameDialog() {
        FragmentB().show(childFragmentManager, "started name dialog")
    }
}

Here is the dialog fragment FragmentB.

class FragmentB : DialogFragment() {

    private lateinit var listener: UpdateNameListener

    override fun onAttach(context: Context) {
        super.onAttach(context)
        try {
            listener = parentFragment as UpdateNameListener
        } catch (e: ClassCastException) {
            throw ClassCastException("$context must implement UpdateNameListener")
        }
    }

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        return activity?.let {
            val builder = AlertDialog.Builder(it)
            val binding = UpdateNameDialogFragmentBinding.inflate(LayoutInflater.from(context))
            binding.btnSave.setOnClickListener {
                val name = binding.name.text.toString()
                listener.onSave(name)
                dismiss()
            }
            builder.setView(binding.root)
            return builder.create()
        } ?: throw IllegalStateException("Activity can not be null")
    }
}

Here is the interface linking the two.

interface UpdateNameListener {
    fun onSave(name: String)
}

That's it.


The correct way of setting a listener to a fragment is by setting it when it is attached. The problem I had was that onAttachFragment() was never called. After some investigation I realised that I had been using getFragmentManager instead of getChildFragmentManager

Here is how I do it:

MyDialogFragment dialogFragment = MyDialogFragment.newInstance("title", "body");
dialogFragment.show(getChildFragmentManager(), "SOME_DIALOG");

Attach it in onAttachFragment:

@Override
public void onAttachFragment(Fragment childFragment) {
    super.onAttachFragment(childFragment);

    if (childFragment instanceof MyDialogFragment) {
        MyDialogFragment dialog = (MyDialogFragment) childFragment;
        dialog.setListener(new MyDialogFragment.Listener() {
            @Override
            public void buttonClicked() {

            }
        });
    }
}

Examples related to android

Under what circumstances can I call findViewById with an Options Menu / Action Bar item? How to implement a simple scenario the OO way My eclipse won't open, i download the bundle pack it keeps saying error log getting " (1) no such column: _id10 " error java doesn't run if structure inside of onclick listener Cannot retrieve string(s) from preferences (settings) strange error in my Animation Drawable how to put image in a bundle and pass it to another activity FragmentActivity to Fragment A failure occurred while executing com.android.build.gradle.internal.tasks

Examples related to android-fragments

FragmentActivity to Fragment How to start Fragment from an Activity How to use data-binding with Fragment In android how to set navigation drawer header image and name programmatically in class file? Android Fragment onAttach() deprecated How to convert any Object to String? Activity, AppCompatActivity, FragmentActivity, and ActionBarActivity: When to Use Which? Difference and uses of onCreate(), onCreateView() and onActivityCreated() in fragments java.lang.IllegalStateException: Fragment not attached to Activity java.lang.NullPointerException: Attempt to invoke virtual method 'int android.view.View.getImportantForAccessibility()' on a null object reference

Examples related to callback

When to use React setState callback How to send an HTTP request with a header parameter? javascript function wait until another function to finish What is the purpose of willSet and didSet in Swift? How to refactor Node.js code that uses fs.readFileSync() into using fs.readFile()? Aren't promises just callbacks? How do I convert an existing callback API to promises? How to access the correct `this` inside a callback? nodeJs callbacks simple example Callback after all asynchronous forEach callbacks are completed

Examples related to android-dialogfragment

passing argument to DialogFragment Callback to a Fragment from a DialogFragment How to create a Custom Dialog box in android? How to set DialogFragment's width and height? How to correctly dismiss a DialogFragment? Android DialogFragment vs Dialog Full Screen DialogFragment in Android Show dialog from fragment? How to prevent a dialog from closing when a button is clicked