BottomSheetDialogFragment - listen to dismissed by user event
Asked Answered
L

10

67

How can I listen to a FINAL dismissal of a BottomSheetDialogFragment? I want to save user changes on the final dismissal only...

I tried following:

Method 1

This only fires, if the dialog is dismissed by swiping it down (not on back press or on touch outside)

@Override
public Dialog onCreateDialog(Bundle savedInstanceState)
{
    Dialog d = super.onCreateDialog(savedInstanceState);
    d.setOnShowListener(new DialogInterface.OnShowListener() {
        @Override
        public void onShow(DialogInterface dialog) {

            BottomSheetDialog d = (BottomSheetDialog) dialog;   
            FrameLayout bottomSheet = (FrameLayout) dialog.findViewById(android.support.design.R.id.design_bottom_sheet);

            BottomSheetBehavior behaviour = BottomSheetBehavior.from(bottomSheet);
            behaviour.setState(BottomSheetBehavior.STATE_EXPANDED);
            behaviour.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
                @Override
                public void onStateChanged(@NonNull View bottomSheet, int newState) {
                    if (newState == BottomSheetBehavior.STATE_HIDDEN)
                    {
                        // Bottom Sheet was dismissed by user! But this is only fired, if dialog is swiped down! Not if touch outside dismissed the dialog or the back button
                        Toast.makeText(MainApp.get(), "HIDDEN", Toast.LENGTH_SHORT).show();
                        dismiss();
                    }
                }

                @Override
                public void onSlide(@NonNull View bottomSheet, float slideOffset) {

                }
            });
        }
    });
    return d;
}

Method 2

This does not allow me to distinguish between a final dismissal and one that is coming from a screen rotation or activity recreation...

 @Override
public void onDismiss(DialogInterface dialog)
{
    super.onDismiss(dialog);
    // this works fine but fires one time too often for my use case, it fires on screen rotation as well, although this is a temporarily dismiss only
    Toast.makeText(MainApp.get(), "DISMISSED", Toast.LENGTH_SHORT).show();
}

Question

How can I listen to an event that indicates, that the user has finished the dialog?

Loft answered 15/11, 2016 at 18:11 Comment(0)
L
110

Although all similar questions on SO suggest using onDismiss I think following is the correct solution:

@Override
public void onCancel(DialogInterface dialog)
{
    super.onCancel(dialog);
    Toast.makeText(MainApp.get(), "CANCEL", Toast.LENGTH_SHORT).show();
}

This fires if:

* the user presses back
* the user presses outside of the dialog

This fires NOT:

* on screen rotation and activity recreation

Solution

Combine onCancel and BottomSheetBehavior.BottomSheetCallback.onStateChanged like following:

public class Dailog extends BottomSheetDialogFragment
{
    @Override
    public void onCancel(DialogInterface dialog)
    {
        super.onCancel(dialog);
        handleUserExit();
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState)
    {
        Dialog d = super.onCreateDialog(savedInstanceState);
        d.setOnShowListener(new DialogInterface.OnShowListener() {
            @Override
            public void onShow(DialogInterface dialog) {
                BottomSheetDialog d = (BottomSheetDialog) dialog;
                FrameLayout bottomSheet = (FrameLayout) d.findViewById(android.support.design.R.id.design_bottom_sheet);
                BottomSheetBehavior behaviour = BottomSheetBehavior.from(bottomSheet);
                behaviour.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
                    @Override
                    public void onStateChanged(@NonNull View bottomSheet, int newState) {
                        if (newState == BottomSheetBehavior.STATE_HIDDEN)
                        {
                            handleUserExit();
                            dismiss();
                        }
                    }

                    @Override
                    public void onSlide(@NonNull View bottomSheet, float slideOffset) {

                    }
                });
            }
        });
        return d;
    }

    private void handleUserExit()
    {
        Toast.makeText(MainApp.get(), "TODO - SAVE data or similar", Toast.LENGTH_SHORT).show();
    }
}
Loft answered 16/11, 2016 at 9:18 Comment(2)
I wonder why needs to call dismiss() on onStateChanged(). When missed dismiss() , sliding down bottom sheet not fully working.Illiberal
Thanks for onCancel(). Warning! This and similar solutions would work, if you didn't disable cancellation of BottomSheet (see #42154821). If you set bottomSheetDialogFragment.setCancelable(false);, listeners won't be called!Punster
P
13

If you extended from BottomSheetDialogFragment() just override in your class

 override fun onDismiss(dialog: DialogInterface) {
        super.onDismiss(dialog)
        //Code here
    }

This will trigger when onBackPress and when you dismiss the dialog by clicking outside of it.

Make sure to NOT set your dialog as cancelable because this will not fire

Panchito answered 15/3, 2020 at 18:52 Comment(0)
H
9
bottomSheetDialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
   @Override
   public void onDismiss(DialogInterface dialog) {
       toast("dismissed");
   }
});
Hartnett answered 12/2, 2020 at 5:27 Comment(4)
Code-only answers are discouraged. Please provide an explanation why and how your answer solves the problem.Pilocarpine
overriding onDismiss or onCancel is better.Bard
It seems there's no such method (setOnDismissListener) in BottomSheetDialogFragment.Bifarious
you can use bottomSheet.dialog?.setOnDismissListenerWindbag
M
5

i achieved this using this simple trick

val bottomSheetDialog = FeedbackFormsFragment.createInstance()
bottomSheetDialog.show((activity as FragmentActivity).supportFragmentManager, BOTTOM_SHEET)


// add some delay to allow the bottom sheet to be visible first so that the dialog is not null

                Handler().postDelayed({
                    bottomSheetDialog.dialog?.setOnDismissListener {

                       // add code here
                    }
                }, 1000)
Melee answered 18/1, 2019 at 10:7 Comment(0)
P
5

While the method of @prom85 works, there is a different one. If you want to dismiss a BottomSheetDialogFragment in one case and retain in the other, it won't work. It will close the dialog in all cases.

For instance, if you entered text inside EditText of BottomSheetDialogFragment and occasionally clicked outside, it would close the dialog without any warning. I tried https://medium.com/@anitas3791/android-bottomsheetdialog-3871a6e9d538, it works, but in another scenario. When you drag the dialog down, it will dismiss it. If you click outside, it won't show any alert message and won't dismiss the dialog.

So, I used a nice advice of @shijo from https://mcmap.net/q/183539/-prevent-dismissal-of-bottomsheetdialogfragment-on-touch-outside.

Add these lines to onActivityCreated method (or any other life cycle method after onCreateView).

@Override 
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    View touchOutsideView = getDialog().getWindow()
        .getDecorView()
        .findViewById(android.support.design.R.id.touch_outside);
    touchOutsideView.setOnClickListener(yourClickListener);
}

In my case in yourClickListener I check the text and show an alert or dismiss the dialog:

private fun checkAndDismiss() {
    if (newText == oldText) {
        dismissAllowingStateLoss()
    } else {
        showDismissAlert()
    }
}

When you create BottomSheetDialogFragment, don't call setCancelable(false) as in https://mcmap.net/q/183539/-prevent-dismissal-of-bottomsheetdialogfragment-on-touch-outside or these methods probably won't work. And maybe don't set <item name="android:windowCloseOnTouchOutside">false</item> in styles or setCanceledOnTouchOutside(false) in onCreateDialog.


I also tried several ways to override cancel behavior, but they didn't succeed.

<style name="BottomSheetDialogTheme" parent="Theme.Design.Light.BottomSheetDialog">
    <item name="android:windowCloseOnTouchOutside">false</item>
</style>

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setStyle(STYLE_NORMAL, R.style.BottomSheetDialogTheme)
}

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
    val dialog = super.onCreateDialog(savedInstanceState)

    dialog.setOnShowListener {
        val bottomSheet = dialog.findViewById<View>(
            android.support.design.R.id.design_bottom_sheet) as? FrameLayout
        val behavior = BottomSheetBehavior.from(bottomSheet)
        behavior.state = BottomSheetBehavior.STATE_EXPANDED
        behavior.setBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
            override fun onSlide(bottomSheet: View, slideOffset: Float) {
            }

            override fun onStateChanged(bottomSheet: View, newState: Int) {
                //showing the different states.
                when (newState) {
                    BottomSheetBehavior.STATE_HIDDEN -> dismiss() //if you want the modal to be dismissed when user drags the bottomsheet down
                    BottomSheetBehavior.STATE_EXPANDED -> {
                    }
                    BottomSheetBehavior.STATE_COLLAPSED -> {
                    }
                    BottomSheetBehavior.STATE_DRAGGING -> {
                    }
                    BottomSheetBehavior.STATE_SETTLING -> {
                    }
                }
            }
        })
        dialog.setOnCancelListener {
            // Doesn't matter what you write here, the dialog will be closed.
        }
        dialog.setOnDismissListener {
            // Doesn't matter what you write here, the dialog will be closed.
        }
    }

    return dialog
}

override fun onCancel(dialog: DialogInterface?) {
    // Doesn't matter what you write here, the dialog will be closed.
    super.onCancel(dialog)
}

override fun onDismiss(dialog: DialogInterface?) {
    // Doesn't matter what you write here, the dialog will be closed.
    super.onDismiss(dialog)
}
Punster answered 17/4, 2019 at 11:52 Comment(0)
A
1

This code works for me:

bottomSheetDialogFragment.getDialog().setOnDismissListener(dialog -> {
                // code goes here
            });

Note, that you should call it after showing bottomSheetDialogFragment (showNow in my case), otherwise getDialog() will return you null.

Anking answered 10/12, 2021 at 22:22 Comment(1)
not working manDisheveled
G
1
LoctSetupDialog loctSetupDialog = new LoctSetupDialog();
        loctSetupDialog.show(requireActivity().getSupportFragmentManager(),"loctSetup");

        loctSetupDialog.getLifecycle().addObserver((LifecycleEventObserver) (source, event) -> {
            if(event == Lifecycle.Event.ON_DESTROY)
            {
                refreshLoctName();
            }
        });
Gynecologist answered 28/10, 2022 at 6:21 Comment(2)
use lifecycle..Gynecologist
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.Jeffereyjefferies
T
0

In an AppCompatActivity you can use the following technique:

    val mgr = supportFragmentManager
    val callback = object: FragmentManager.OnBackStackChangedListener {
        var count = 0
        override fun onBackStackChanged() {
            // We come here twice, once when the sheet is opened, 
            // once when it's closed.
            if (++count >= 2) {
                mgr.removeOnBackStackChangedListener(this)
                doWhatNeedsToBeDoneWhenTheSheetIsClosed()
            }
        }
    }
    mgr.addOnBackStackChangedListener(callback)

Be sure to do the above just before you call show on the sheet.

Tarpley answered 21/3, 2020 at 10:20 Comment(0)
E
0

You can add a LifecycleObserver that will be notified when the LifecycleOwner changes state lıke that:

val yourBottomSheet = BottomSheet()
            
yourBottomSheet.lifecycle.addObserver(LifecycleEventObserver { source, event ->
      // events Lifecycle.Event.ON_CREATE, Lifecycle.Event.ON_DESTROY...
})
Eleanoraeleanore answered 27/2, 2023 at 17:16 Comment(0)
E
0

BottomSheetDialogFragment extends DialogFragment, that already implements two interfaces: DialogInterface.OnCancelListener and DialogInterface.OnDismissListener.

You can to listen to the closing of the dialogue:

override fun onCancel(dialog: DialogInterface) {
    super.onCancel(dialog)
    // you code here
}

and

override fun onDismiss(dialog: DialogInterface) {
    super.onDismiss(dialog)
    // you code here
}

If user close dialog clicking outside a dialog, or user swipe the dialog down, or user click back button, also if you close dialog programmatically calling

dialog.?cancel()

then both onCancel() and onDismiss() will be invoked.

But if you close dialog programmatically calling method

dismiss()

then only onDismiss() will be invoked.

Expatiate answered 30/11, 2023 at 13:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.