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?
OnClickListener listener = (OnClickListener) getParentFragment();
in DialogFragment instead, and your main Fragment implement the interface as you did originally.
Activity involved is completely unaware of the DialogFragment.
Fragment class:
public class MyFragment extends Fragment {
int mStackLevel = 0;
public static final int DIALOG_FRAGMENT = 1;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
mStackLevel = savedInstanceState.getInt("level");
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("level", mStackLevel);
}
void showDialog(int type) {
mStackLevel++;
FragmentTransaction ft = getActivity().getFragmentManager().beginTransaction();
Fragment prev = getActivity().getFragmentManager().findFragmentByTag("dialog");
if (prev != null) {
ft.remove(prev);
}
ft.addToBackStack(null);
switch (type) {
case DIALOG_FRAGMENT:
DialogFragment dialogFrag = MyDialogFragment.newInstance(123);
dialogFrag.setTargetFragment(this, DIALOG_FRAGMENT);
dialogFrag.show(getFragmentManager().beginTransaction(), "dialog");
break;
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
switch(requestCode) {
case DIALOG_FRAGMENT:
if (resultCode == Activity.RESULT_OK) {
// After Ok code.
} else if (resultCode == Activity.RESULT_CANCELED){
// After Cancel code.
}
break;
}
}
}
}
DialogFragment class:
public class MyDialogFragment extends DialogFragment {
public static MyDialogFragment newInstance(int num){
MyDialogFragment dialogFragment = new MyDialogFragment();
Bundle bundle = new Bundle();
bundle.putInt("num", num);
dialogFragment.setArguments(bundle);
return dialogFragment;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity())
.setTitle(R.string.ERROR)
.setIcon(android.R.drawable.ic_dialog_alert)
.setPositiveButton(R.string.ok_button,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
getTargetFragment().onActivityResult(getTargetRequestCode(), Activity.RESULT_OK, getActivity().getIntent());
}
}
)
.setNegativeButton(R.string.cancel_button, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
getTargetFragment().onActivityResult(getTargetRequestCode(), Activity.RESULT_CANCELED, getActivity().getIntent());
}
})
.create();
}
}
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.
Create interface like DialogFragmentCallbackInterface with some method like callBackMethod(Object data). Which you would calling to pass data. Now you can implement DialogFragmentCallbackInterface interface in your fragment like MyFragment implements DialogFragmentCallbackInterface 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); 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.
onActivityResult(request code, resultcode, intent)
to return result to fragment1. From fragment1, setTargetFragment()
and from fragment2, use getTargetFragment()
. When using parent/child fragment to fragment or activity to fragment, you can use listener or callback since there isn't any danger of leaking parent in the child fragment.
setTargetFragment
before to set the callback. Worked like a charm!
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.
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.
A recommended approach is to use the new Fragment Result API.
By using it, you do not need to override onAttach(context) nor setTargetFragment(), which is now deprecated.
1 - Add a result listener on parent Fragment's onCreate
:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
childFragmentManager.setFragmentResultListener("requestKey", this) { key, bundle ->
val result = bundle.getString("bundleKey")
}
}
2- On child Fragment, set the result (on a button click listener, for instance):
button.setOnClickListener {
val result = "resultSample"
setFragmentResult("requestKey", bundleOf("bundleKey" to result))
}
More info on the docs: https://developer.android.com/guide/fragments/communicate#fragment-result
Hope it helps!
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){
}
...
}
FragmentA
and he is assuming an activity is an OnArticleSelectedListener
, not the Fragment that started him.
MyFragment
you may want to switch to using a regular AlertDialog
According to the official documentation:
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.
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");
}
}
...
}
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() {
}
});
}
}
Update: Please note that there are easier ways of doing this using view models which I can share if someone is interested.
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:
FragmentA is a child fragment of MainActivity. To manage fragments created in FragmentA we will use childFragmentManager which does that! 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.
I was facing a similar problem. The solution that I found out was :
Declare an interface in your DialogFragment just like James McCracken has explained above. Implement the interface in your activity (not fragment! That is not a good practice). 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
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 {
}
}
}
}
}
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
this is work for me
i think you can set callback in display method in your fragment,
**in my fragment**
val myDialogFragment=MyDialogFragment()
myDialogFragment.display(fragmentManager!!,this)
//my fragment implement CallbackDialogFragment so set this for display method
**in dialog fragment**
lateinit var callBackResult: CallbackDialogFragment
fun display(fragmentManager: FragmentManager, callback: CallbackDialogFragment) {
callBackResult = callback
show(fragmentManager,"dialogTag")
}
Full example how to use setFragmentResultListener
:
Parent fragment MainFragment.kt
:
import android.os.Bundle
import android.view.View
import android.widget.Button
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import androidx.fragment.app.setFragmentResult
import androidx.fragment.app.setFragmentResultListener
class MainFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val showDialogButton = view.findViewById<Button>(R.id.dialog_button)
showDialogButton.setOnClickListener {
showMyDialog()
}
}
private fun showMyDialog() {
MyDialogFragment.showOn(this) { bundle ->
/*here handle bundle result*/
}
}
}
your dialog:
import android.os.Bundle
import android.view.View
import android.widget.Button
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import androidx.fragment.app.setFragmentResult
import androidx.fragment.app.setFragmentResultListener
class MyDialogFragment : DialogFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val submitButton = view.findViewById<Button>(R.id.submitButton)
submitButton.setOnClickListener {
parentFragment?.setFragmentResult(KEY_CALLBACK_BUNDLE, buildResultBundle())
}
}
private fun buildResultBundle(): Bundle {
val bundle = Bundle()
/*here build your result bundle for parent fragment*/
return bundle
}
companion object {
const val TAG: String = "MyDialogFragment"
private const val KEY_CALLBACK_BUNDLE: String = "KEY_CALLBACK_BUNDLE"
fun showOn(fragment: Fragment, callback: (Bundle) -> Unit) {
val dialog = MyDialogFragment()
fragment.setFragmentResultListener(KEY_CALLBACK_BUNDLE) { requestKey: String, bundle: Bundle ->
if (requestKey == KEY_CALLBACK_BUNDLE) {
callback(bundle)
}
}
dialog.show(fragment.childFragmentManager, TAG)
}
}
}
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
More improved way is to just use newInstance
and interface
.
Here is a Fragment that needs DailogFragment
public class Fragment extends Fragment implements returnPinInterface {
....
....
public View onCreateView(@NotNull LayoutInflater inflater, ViewGroup
container,Bundle savedInstanceState) {
// A simple call to show DialogFragment
btnProceed.setOnClickListener(v -> {
fragment = DailogFragment.newInstance(this);
fragment.show(getChildFragmentManager(),null );
fragment.setCancelable(false);
});
//Grab whatever user clicked/selected/ or typed
@Override
public void onPinReturn(String s) {
Log.d("ReturnedPin", s);
}
}
Here comes your DialogFragment
public class PinDialogFragment extends DialogFragment {
//Create a static variable to help you receive instance of fragment you
//passed
public static Fragment fragm;
// Create new Instance and grab the object passed in Fragment up
//there
public static PinDialogFragment newInstance(Fragment frag) {
PinDialogFragment fragment = new PinDialogFragment();
fragm = frag;
return fragment;
}
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_pin, container, false);
//
btn.setOnClickListener(btn ->
listener.onReturnPin("1234"));
return v;
}
//Use the Fragm to instantiate your Interface
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (fragm instanceof ReturnPinInterface) {
listener = (ReturnPinInterface) fragm;
} else {
throw new RuntimeException("you must implement ReturnPinInterface");
}
}
}
Enjoy!
Using onAttach
is advised in official Android documentation. So, we can make use of that method.
Make sure that your parent fragment implements a listener e.g. OnPopupButtonClickListener
:
public interface OnPopupButtonClickListener {
void onPositiveButtonClicked();
void onNegativeButtonClicked();
}
In your parent fragment show your DialogFragment
instance using getChildFragmentManager()
:
PopupDialogFragment dialogFragment = new PopupDialogFragment();
dialogFragment.show(getChildFragmentManager(), "PopupDialogFragment");
In your dialog class which extends DialogFragment
instance add this method: (Notice that we are retrieving our parent fragment via getParentFragment()
which implements our custom listener interface OnPopupButtonClickListener
@Override
public void onAttach(@NonNull @NotNull Context context) {
super.onAttach(context);
// Verify that the host activity implements the callback interface
try {
listener = (OnPopupButtonClickListener) getParentFragment();
} catch (ClassCastException e) {
// The activity doesn't implement the interface, throw exception
throw new ClassCastException(getActivity().toString()
+ " must implement OnPopupButtonClickListener");
}
}
In your dialog you can then use your listener whenever it is needed in your DialogFragment
, for example:
Button positiveButton = view.findViewById(R.id.positiveButton);
positiveButton.setOnClickListener(v -> {
if (listener != null) {
listener.onPositiveButtonClicked();
getDialog().dismiss();
}
});
Success story sharing
setTargetFragment
andgetTargetFragment
. The use ofonActivityResult
is a little unclear. It would probably be better to declare your own specific method in the Fragment caller, and use that, instead of re-purposing onActivityResult. But its all semantics at that point.