Receive result from DialogFragment

AndroidDialogAndroid FragmentsFragmentDismiss

Android Problem Overview


I am using DialogFragments for a number of things: choosing item from list, entering text.

What is the best way to return a value (i.e. a string or an item from a list) back to the calling activity/fragment?

Currently I am making the calling activity implement DismissListener and giving the DialogFragment a reference to the activity. The Dialog then calls the OnDimiss method in the activity and the activity grabs the result from the DialogFragment object. Very messy and it doesn't work on configuration change (orientation change) as the DialogFragment loses the reference to the activity.

Thanks for any help.

Android Solutions


Solution 1 - Android

Use myDialogFragment.setTargetFragment(this, MY_REQUEST_CODE) from the place where you show the dialog, and then when your dialog is finished, from it you can call getTargetFragment().onActivityResult(getTargetRequestCode(), ...), and implement onActivityResult() in the containing fragment.

It seems like an abuse of onActivityResult(), especially as it doesn't involve activities at all. But I've seen it recommended by official google people, and maybe even in the api demos. I think it's what g/setTargetFragment() were added for.

Solution 2 - Android

As you can see here there is a very simple way to do that.

In your DialogFragment add an interface listener like:

public interface EditNameDialogListener {
    void onFinishEditDialog(String inputText);
}

Then, add a reference to that listener:

private EditNameDialogListener listener;

This will be used to "activate" the listener method(s), and also to check if the parent Activity/Fragment implements this interface (see below).

In the Activity/FragmentActivity/Fragment that "called" the DialogFragment simply implement this interface.

In your DialogFragment all you need to add at the point where you'd like to dismiss the DialogFragment and return the result is this:

listener.onFinishEditDialog(mEditText.getText().toString());
this.dismiss();

Where mEditText.getText().toString() is what will be passed back to the calling Activity.

Note that if you want to return something else simply change the arguments the listener takes.

Finally, you should check whether the interface was actually implemented by the parent activity/fragment:

@Override
public void onAttach(Context context) {
    super.onAttach(context);
    // Verify that the host activity implements the callback interface
    try {
        // Instantiate the EditNameDialogListener so we can send events to the host
        listener = (EditNameDialogListener) context;
    } catch (ClassCastException e) {
        // The activity doesn't implement the interface, throw exception
        throw new ClassCastException(context.toString()
                + " must implement EditNameDialogListener");
    }
}

This technique is very flexible and allow calling back with the result even if your don;t want to dismiss the dialog just yet.

Solution 3 - Android

There is a much simpler way to receive a result from a DialogFragment.

First, in your Activity, Fragment, or FragmentActivity you need to add in the following information:

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    // Stuff to do, dependent on requestCode and resultCode
    if(requestCode == 1) { // 1 is an arbitrary number, can be any int
         // This is the return result of your DialogFragment
         if(resultCode == 1) { // 1 is an arbitrary number, can be any int
              // Now do what you need to do after the dialog dismisses.
         }
     }
}

The requestCode is basically your int label for the DialogFragment you called, I'll show how this works in a second. The resultCode is the code that you send back from the DialogFragment telling your current waiting Activity, Fragment, or FragmentActivity what happened.

The next piece of code to go in is the call to the DialogFragment. An example is here:

DialogFragment dialogFrag = new MyDialogFragment();
// This is the requestCode that you are sending.
dialogFrag.setTargetFragment(this, 1);     
// This is the tag, "dialog" being sent.
dialogFrag.show(getFragmentManager(), "dialog");

With these three lines you are declaring your DialogFragment, setting a requestCode (which will call the onActivityResult(...) once the Dialog is dismissed, and you are then showing the dialog. It's that simple.

Now, in your DialogFragment you need to just add one line directly before the dismiss() so that you send a resultCode back to the onActivityResult().

getTargetFragment().onActivityResult(getTargetRequestCode(), resultCode, getActivity().getIntent());
dismiss();

That's it. Note, the resultCode is defined as int resultCode which I've set to resultCode = 1; in this case.

That's it, you can now send the result of your DialogFragment back to your calling Activity, Fragment, or FragmentActivity.

Also, it looks like this information was posted previously, but there wasn't a sufficient example given so I thought I'd provide more detail.

EDIT 06.24.2016 I apologize for the misleading code above. But you most certainly cannot receive the result back to the activity seeing as the line:

dialogFrag.setTargetFragment(this, 1);

sets a target Fragment and not Activity. So in order to do this you need to use implement an InterfaceCommunicator.

In your DialogFragment set a global variable

public InterfaceCommunicator interfaceCommunicator;

Create a public function to handle it

public interface InterfaceCommunicator {
    void sendRequestCode(int code);
}

Then when you're ready to send the code back to the Activity when the DialogFragment is done running, you simply add the line before you dismiss(); your DialogFragment:

interfaceCommunicator.sendRequestCode(1); // the parameter is any int code you choose.

In your activity now you have to do two things, the first is to remove that one line of code that is no longer applicable:

dialogFrag.setTargetFragment(this, 1);  

Then implement the interface and you're all done. You can do that by adding the following line to the implements clause at the very top of your class:

public class MyClass Activity implements MyDialogFragment.InterfaceCommunicator

And then @Override the function in the activity,

@Override
public void sendRequestCode(int code) {
    // your code here
}

You use this interface method just like you would the onActivityResult() method. Except the interface method is for DialogFragments and the other is for Fragments.

Solution 4 - Android

Well its too late may be to answer but here is what i did to get results back from the DialogFragment. very similar to @brandon's answer. Here i am calling DialogFragment from a fragment, just place this code where you are calling your dialog.

FragmentManager fragmentManager = getFragmentManager();
            categoryDialog.setTargetFragment(this,1);
            categoryDialog.show(fragmentManager, "dialog");

where categoryDialog is my DialogFragment which i want to call and after this in your implementation of dialogfragment place this code where you are setting your data in intent. The value of resultCode is 1 you can set it or use system Defined.

            Intent intent = new Intent();
            intent.putExtra("listdata", stringData);
            getTargetFragment().onActivityResult(getTargetRequestCode(), resultCode, intent);
            getDialog().dismiss();
       

now its time to get back to to the calling fragment and implement this method. check for data validity or result success if you want with resultCode and requestCode in if condition.

 @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);        
        //do what ever you want here, and get the result from intent like below
        String myData = data.getStringExtra("listdata");
Toast.makeText(getActivity(),data.getStringExtra("listdata"),Toast.LENGTH_SHORT).show();
    }

Solution 5 - Android

For anyone still reading this: setTargetFragment() has been deprecated. It is now recommended to use the FragmentResultListener API like this:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setFragmentResultListener("requestKey") { key, bundle ->
        val result = bundle.getString("resultKey")
        // Do something with the result...
    }

    ...

    // Somewhere show your dialog
    MyDialogFragment.newInstance().show(parentFragmentManager, "tag")
}

Then in your MyDialogFragment set the result:

button.setOnClickListener{
    val result = "some string"
    setFragmentResult("requestKey", bundleOf("resultKey" to result))
    dismiss()
}

Solution 6 - Android

Different approach, to allow a Fragment to communicate up to its Activity:

1) Define a public interface in the fragment and create a variable for it

public OnFragmentInteractionListener mCallback;

public interface OnFragmentInteractionListener {
    void onFragmentInteraction(int id);
}

2) Cast the activity to the mCallback variable in the fragment

try {
    mCallback = (OnFragmentInteractionListener) getActivity();
} catch (Exception e) {
    Log.d(TAG, e.getMessage());
}

3) Implement the listener in your activity

public class MainActivity extends AppCompatActivity implements DFragment.OnFragmentInteractionListener  {
     //your code here
}

4) Override the OnFragmentInteraction in the activity

@Override
public void onFragmentInteraction(int id) {
    Log.d(TAG, "received from fragment: " + id);
}

More info on it: https://developer.android.com/training/basics/fragments/communicating.html

Solution 7 - Android

One easy way I found was the following: Implement this is your dialogFragment,

  CallingActivity callingActivity = (CallingActivity) getActivity();
  callingActivity.onUserSelectValue("insert selected value here");
  dismiss();

And then in the activity that called the Dialog Fragment create the appropriate function as such:

 public void onUserSelectValue(String selectedValue) {

        // TODO add your implementation.
      Toast.makeText(getBaseContext(), ""+ selectedValue, Toast.LENGTH_LONG).show();
    }

The Toast is to show that it works. Worked for me.

Solution 8 - Android

I'm very surprised to see that no-one has suggested using local broadcasts for DialogFragment to Activity communication! I find it to be so much simpler and cleaner than other suggestions. Essentially, you register for your Activity to listen out for the broadcasts and you send the local broadcasts from your DialogFragment instances. Simple. For a step-by-step guide on how to set it all up, see here.

Solution 9 - Android

Or share ViewModel like showed here:

public class SharedViewModel extends ViewModel {
    private final MutableLiveData<Item> selected = new MutableLiveData<Item>();

    public void select(Item item) {
        selected.setValue(item);
    }

    public LiveData<Item> getSelected() {
        return selected;
    }
}


public class MasterFragment extends Fragment {
    private SharedViewModel model;
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        itemSelector.setOnClickListener(item -> {
            model.select(item);
        });
    }
}

public class DetailFragment extends Fragment {
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        model.getSelected().observe(this, { item ->
           // Update the UI.
        });
    }
}

https://developer.android.com/topic/libraries/architecture/viewmodel#sharing_data_between_fragments

Solution 10 - Android

In my case I needed to pass arguments to a targetFragment. But I got exception "Fragment already active". So I declared an Interface in my DialogFragment which parentFragment implemented. When parentFragment started a DialogFragment , it set itself as TargetFragment. Then in DialogFragment I called

 ((Interface)getTargetFragment()).onSomething(selectedListPosition);

Solution 11 - Android

In Kotlin

    // My DialogFragment
class FiltroDialogFragment : DialogFragment(), View.OnClickListener {
	
    var listener: InterfaceCommunicator? = null

    override fun onAttach(context: Context?) {
        super.onAttach(context)
        listener = context as InterfaceCommunicator
    }

    interface InterfaceCommunicator {
        fun sendRequest(value: String)
    }	

    override fun onClick(v: View) {
        when (v.id) {
            R.id.buttonOk -> {    
		//You can change value             
                listener?.sendRequest('send data')
                dismiss()
            }
            
        }
    }
}

// My Activity

class MyActivity: AppCompatActivity(),FiltroDialogFragment.InterfaceCommunicator {

    override fun sendRequest(value: String) {
	// :)
	Toast.makeText(this, value, Toast.LENGTH_LONG).show()
    }
}
 

I hope it serves, if you can improve please edit it. My English is not very good

Solution 12 - Android

if you want to send arguments and receive the result from second fragment, you may use Fragment.setArguments to accomplish this task

static class FirstFragment extends Fragment {
    final Handler mUIHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
            case 101: // receive the result from SecondFragment
                Object result = msg.obj;
                // do something according to the result
                break;
            }
        };
    };

    void onStartSecondFragments() {
        Message msg = Message.obtain(mUIHandler, 101, 102, 103, new Object()); // replace Object with a Parcelable if you want to across Save/Restore
                                                                               // instance
        putParcelable(new SecondFragment(), msg).show(getFragmentManager().beginTransaction(), null);
    }
}

static class SecondFragment extends DialogFragment {
    Message mMsg; // arguments from the caller/FirstFragment

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onViewCreated(view, savedInstanceState);
        mMsg = getParcelable(this);
    }

    void onClickOK() {
        mMsg.obj = new Object(); // send the result to the caller/FirstFragment
        mMsg.sendToTarget();
    }
}

static <T extends Fragment> T putParcelable(T f, Parcelable arg) {
    if (f.getArguments() == null) {
        f.setArguments(new Bundle());
    }
    f.getArguments().putParcelable("extra_args", arg);
    return f;
}
static <T extends Parcelable> T getParcelable(Fragment f) {
    return f.getArguments().getParcelable("extra_args");
}

Solution 13 - Android

On a dialog Fragment

class AbcDialogFragment(private val ondata: (data: String) -> Unit) :  DialogFragment() {}

Code to show the dialog from fragment/Activity

val abcDialogFragment = AbcDialogFragment(ondata = {data->  })
                
abcDialogFragment.show(requireActivity().supportFragmentManager, "TAG")

and in the dialog fragment, you can invoke the onData when dialog fragment is closed or any click listeners.

Solution 14 - Android

TL;DR - use this AppDialog class to both pass data into DialogFragment as well as get result out of it.

Detailed explanation:

  • Premise - Fragments get destroyed and recreated on config changes. View models hang around. When using a Dialog, it is recommended to wrap it in DialogFragment so that when the user rotates device and changes orientation the Dialog will not unexpectedly disappear (the DialogFragment will re-create it and re-display it).
  • Limitation (hence this question) - The way the DialogFragment works is it takes a class that it will need to re-instantiate on configuration changes - that means one can't have constructor parameters to the subclass to pass parameters, and typically one needs to make custom callbacks through a view model to pass back result of dialog. That typically means a new subclass for every dialog.
  • The solution - To help with all this, this custom AppDialog fragment comes to the rescue - the parameters are stored in-memory (similar to view model, you can think of it as a tiny custom view model that holds T in memory and uses it to re-create the dialog on config changes) until the dialog fragment is dismissed. The proper way to call back would be through a view model. If the fragment that shows the AppDialog, then you probably already have a view model and you can reference it from the lambda used to create the dialog - that means additional strong reference to the view model until the dialog fragment is dismissed.
  • Example - see the examples where a simple Dialog is refactored to use this AppDialog utility class to both receive a parameter and do a callback to viewModel to notify of result.

The helper class:


class AppDialog<T>: DialogFragment() {
    companion object {

        fun<T> buildDialog(params: T? = null, builder: AppDialogLambda<T>): AppDialog<T> {

            // Setup arguments
            val args = Bundle()
            args.putInt("key", pushDialogArgs(params, builder))

            // Instantiate
            val fragment = AppDialog<T>()
            fragment.arguments = args
            return fragment
        }

        // --------------------
        // Dialog Arguments

        private var lastKey: Int = 0
        private val dialogArgs = mutableMapOf<Int, Pair<Any?, AppDialogLambda<*>>>()

        private fun pushDialogArgs(params: Any?, builder: AppDialogLambda<*>): Int {
            dialogArgs[lastKey] = params to builder
            return lastKey++
        }

        private fun getDialogArgs(key: Int): Pair<Any?, AppDialogLambda<*>> {
            return dialogArgs[key]!!
        }

        private fun deleteDialogArgs(key: Int) {
            dialogArgs.remove(key)
        }
    }

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        // Get arguments
        val argKey = requireArguments().getInt("key")
        val (params, builder) = getDialogArgs(argKey)

        // We are getting back our arguments we passed AppDialog.buildDialog and
        // the type is guaranteed to be the same. Silence this warning
        @Suppress("UNCHECKED_CAST")
        return (builder as AppDialogLambda<T>)(this, params as T?)
    }

    override fun onDismiss(dialog: DialogInterface) {
        super.onDismiss(dialog)
        val argKey = requireArguments().getInt("key")
        deleteDialogArgs(argKey)
    }
}

Example usage (after):

        val info = mapOf("message" to "${error.description}\n\nPlease check your Internet connection and try again.")
        AppDialog.buildDialog(info) { fragment, params ->
            fragment.isCancelable = false // since we are in a DialogFragment
            AlertDialog.Builder(fragment.context)
                .setTitle("Terms Of Service Failed To Load")
                .setMessage(params!!["message"])
                .setPositiveButton("Retry") { _, _ ->
                    // Update the view model instead of calling UserTOSFragment directly 
                    // as the fragment may be destroyed and recreated
                    // on configuration changes. The viewModel will stay alive.
                    viewModel.onTermsOfServiceReload()
                }
                .setNegativeButton("Cancel") { _, _ ->
                    viewModel.onTermsOfServiceDeclined()
                    fragment.findNavController().popBackStack()
                }.create()
        }.show(parentFragmentManager, "TOS Failed Dialog")

Example usage (before): Without using DialogFragment (for illustration purposes, don't do this, this is bad practice as the dialog will be destroyed on config changes), code inside UserTOSFragment.kt - note code used to call directly the UserTOSFragment.loadContent() on retry. This has to be rewritten to instead call viewModel.onTermsOfServiceDeclined() in the above example:

        AlertDialog.Builder(context)
            .setTitle("Terms Of Service Failed To Load")
            .setMessage("${error.description}\n\nPlease check your Internet connection and try again.")
            .setPositiveButton("Retry") { _, _ ->
                loadContent()
            }
            .setCancelable(false)
            .setNegativeButton("Cancel") { _, _ ->
                viewModel.onTermsOfServiceDeclined()
                findNavController().popBackStack()
            }
            .show()

Solution 15 - Android

Just to have it as one of the options (since no one mentioned it yet) - you could use an event bus like Otto. So in the dialog you do:

bus.post(new AnswerAvailableEvent(42));

And have your caller (Activity or Fragment) subscribe to it:

@Subscribe public void answerAvailable(AnswerAvailableEvent event) {
   // TODO: React to the event somehow!
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJames CrossView Question on Stackoverflow
Solution 1 - AndroidTimmmmView Answer on Stackoverflow
Solution 2 - AndroidAssaf GamlielView Answer on Stackoverflow
Solution 3 - AndroidBrandonView Answer on Stackoverflow
Solution 4 - Androidvikas kumarView Answer on Stackoverflow
Solution 5 - AndroidBram StokerView Answer on Stackoverflow
Solution 6 - AndroidlcompareView Answer on Stackoverflow
Solution 7 - AndroidZeWolfe15View Answer on Stackoverflow
Solution 8 - AndroidAdil HussainView Answer on Stackoverflow
Solution 9 - AndroidMalachiaszView Answer on Stackoverflow
Solution 10 - AndroidLanitkaView Answer on Stackoverflow
Solution 11 - AndroidIrvin JoaoView Answer on Stackoverflow
Solution 12 - AndroidYessyView Answer on Stackoverflow
Solution 13 - AndroidAyush SthView Answer on Stackoverflow
Solution 14 - AndroidEvtim GeorgievView Answer on Stackoverflow
Solution 15 - AndroidDennis KView Answer on Stackoverflow