ChatGPT解决这个技术问题 Extra ChatGPT

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?

You mentioned "But there is no guarantee that the listener will be around if the DialogFragment pauses and resumes through its lifecycle." I thought Fragment state get destroyed during onDestroy()? You must be right, but I am just a bit confused how to use Fragment state now. How do I reproduce the problem you mentioned, the listener is not around?
I don't see why you can't simply use OnClickListener listener = (OnClickListener) getParentFragment(); in DialogFragment instead, and your main Fragment implement the interface as you did originally.
Here is an answer to an unrelated question but it does show you how this is done in a clean way stackoverflow.com/questions/28620026/…

P
Piotr Ślesarew

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();
}
}

I think the key here is setTargetFragment and getTargetFragment. The use of onActivityResult 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.
stack level variable is not used?
will this survive a config change- rotation?
Used this. Notes: stack level was not necessary to survive rotation or sleep. Instead of onActivityResult, my fragment implements DialogResultHandler#handleDialogResult (an interface I created). @myCode, would be super helpful to show a dialog picked value being added to the Intent, and then read inside your onActivityResult. Intents are unclear to beginners.
@eternalmatt, your objection is entirely reasonable, but I think the value of onActivityResult() is that it's guaranteed to exist on any Fragment, so any Fragment can be used as the parent. If you create your own interface and have the parent Fragment implement it, then the child can only be used with parents that implement that interface. Coupling the child to that interface might come back to haunt you if you start using the child more widely later. Using the "built-in" onActivityResult() interface requires no additional coupling, so it allows you a little more flexibility.
V
Vasily Kabunov

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


This also works with the onAttach(Context context) introduced added in api 23.
THIS should be the accepted answer. Current accepted answer is buggy and fragments are not meant to be used like this.
@AhmadFadli Problem here is to get the right context (parent) for communication in between fragments. If you gonna use dialog fragment as a child of activity, then there shouldn't be any confusion. FragmentManager of Activity and getActivity() to retrieve callback is enough.
This should be the accepted answer. It is clearly explained and detailed, it's not just code thrown at people.
@lukecross ParentFragment is the fragment that creates the DialogFragment (the one that calls show()) But it looks that childFragmentManager doesn't survive reconfiguration/screen rotations...
V
Vijay Vankhede

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.


This should be best answer. Great Answer.
Also make sure to use same Fragment manager for both source and destination fragments, otherwise getTargetFragment won't work. So if you are using childFragmentManager, it won't work since the source fragment is not committed by child fragment manager. Its best to think of these 2 fragments as sibling fragments rather than parent/child fragments.
Frankly, it is better to use only target fragment pattern when communicating between 2 sibling fragment. By not having a listener, you avoid accidentally leaking the fragment1 in fragment2. When using target fragment, don't use listener/callback. Only use 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.
@Thupten when you say "leak" do you mean memory leak or "leak implementation details"? I don't think Vijay's answer will leak memory any more than using onActivityResulty for return value. Both patterns will keep reference to target fragment. If you mean leaking implementation details, then I think his pattern is even better than onActivityResult. The callback method is explicit (if named correctly). If all you get back are OK and CANCELED, the first fragment has to interpret what those mean.
Liked this one. never heard of setTargetFragment before to set the callback. Worked like a charm!
a
aleksandrbel

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.


Here is an answer to another question but it also applies to your question and is a clean solution: stackoverflow.com/questions/28620026/…
IllegalStateException to me
E
Edward Brey

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.


what about inner fragments i.e how should a fragment within another fragment communicate to host fragment
@Ravi: Each fragment should communicate with the activity that is common to all the fragments by calling getActivity().
@Chris: If fragments need ongoing communication, define an interface for each appropriate fragment to implement. The activity's job is then limited to providing fragments with interface pointers to their counterpart fragments. After that, fragments can safely communicate "directly" via the interfaces.
I think that as the uses of fragments has been expanded the original idea of not using direct fragment communication breaks down. E.g. in a navigation drawer each immediate child fragment of the activity is roughly acting as an activity. So having a fragment such as a dialogfragment communicate through the activity harms readability/flexibility IMO. In fact there doesn't seem to be any nice way to encapsulate dialogfragment to allow it to work with both activities and fragments in a reusable way.
I know this is old, but in case anyone else comes here I feel like the case talked about in that document doesn't apply when one fragment "owns" the logic that is used to determine creation and management of the DialogFragment. Its kind of weird to create a bunch of connections from the fragment to the activity when the activity isn't even sure why a Dialog is being created or under what conditions it should be dismissed. Besides that the DialogFragment is super simple, and exists only to notify the user and potentially get a response.
G
Geraldo Neto

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!


Wow, what a great answer! I wonder how long it will take for this answer to climb up the votes, I'm an active Lead Android Engineer, somehow didn't catch wind of this api, and I'd have to say this is probably now - in 2022 - the correct way, and should be the accepted answer for this question! Cheers Geraldo :-)
I'm happy it helped! Without this new API it was a big pain doing such a simple task! Cheers Vin.
Great answer, thank you.
this is the best answer.
J
James McCracken

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){

    }
    ...
}

I think you skimmed the docs too quickly. Both of these code segments are FragmentA and he is assuming an activity is an OnArticleSelectedListener, not the Fragment that started him.
I would consider what you're trying to do bad practice. The Android guidelines recommend that all fragment-to-fragment communication takes place through the activity (per developer.android.com/training/basics/fragments/…). If you really want it all to be handled within MyFragment you may want to switch to using a regular AlertDialog
I think the concern with having the fragments talk directly to each other is that in some layouts not all fragments may be loaded and as they show in the example it might be necessary to switch in the fragment. I don't think that concern is valid when talking about launching a dialog fragment from a fragment.
I have this implemented for my activities. Question: can this solution be extended such that a fragment could instantiate this dialog?
This is a good practice from an architectural perspective, and as such should be the accepted answer. Using onActivityResult leads to spaghetti architecture
S
SUPERYAO

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");
        }
    }
    ...
}

can you explain your?
In this case, we need to passing "MyFragment" reference to "MyialogFragment", and "Fragment" provides the method to do it. I added the description of the official document, it should say more clearly than I do.
You've already used newInstance, you don't need to setTargetFragment again.
setTargetFragment is deprecated
E
ElOjcar

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() {

            }
        });
    }
}

G
Gilbert

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 followed this doc : developer.android.com/guide/topics/ui/dialogs and it didn't work. Thank you so much. I hope this parentfragment thing works like expected every time :)
do not forget to set listener to null inside onDetach :)
@BekaBot Thanks for the comment. I have done some research and it appears that its not necessary to close the listeners. stackoverflow.com/a/37031951/10030693
S
Shailesh Mani Pandey

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


Z
Zar E Ahmer

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 {
                    }
                }
            }
        }
    }

A
Arst

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")
    }

N
NickUnuchek

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)
        }
    }
}

G
Gerardo Suarez

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


Funny thing about Android is there is something called the lifecycle. The base fragment or dialog fragment need to be able to preserve state (and their connection) over lifecycle events. Callbacks or observers cannot be serialized and hence have the same issue here.
o
olajide

More improved way is to just use newInstanceand 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!


a
alierdogan7

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();
    }
});