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.
Activity
and the DialogFragment
might be recreated. Using the Activity
passed to onAttach(Activity activity)
is the proper and recommended way.
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.
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.
Activity
's and FragmentActivity
's but if is the caller a Fragment
?
Fragment
.
Fragment
then you can do a few things: 1. Pass the fragment as a reference (Might not be a good idea because you might cause memory leaks). 2. Use the FragmentManager
and call findFragmentById
or findFragmentByTag
it will get the fragments that exist in your activity. I hope it helped. Have a great day!
setTargetFragment()
and getTargetFragment()
are magic.
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
.
dialogFrag.setTargetFragment(this, 1)
from an Activity, but this method receives a Fragment as first argument, so this couldn't be casted. Am I right ?
@Override public void onAttach(Context context) { super.onAttach(context); yourInterface = (YourInterface) context; }
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();
}
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()
}
parentFragmentManager
is important. It's easy to send childFragmentManager
by accident which doesn't trigger setFragmentResultListener lambda
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
public void onAttach
of the fragment and do the activity casting there
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.
Interface
rather than hard-coupling with concrete classes.
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.
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
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);
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
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");
}
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.
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()
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!
}
Success story sharing
setTargetFragment
is now deprecated, but the replacementFragmentManager.setFragmentResultListener
(described in Pass data between fragments is still in alpha.