DialogFragment ignores OnBackPressedDispatcher
Asked Answered
C

5

14

I'm trying to handle the back button in a BottomSheetDialogFragment, which is a DialogFragment, using 'androidx.activity:activity-ktx:1.1.0-alpha01' and 'androidx.fragment:fragment-ktx:1.2.0-alpha01'.

handleOnBackPressed() is not called and the DialogFragment is dismissed. The OnBackPressedCallback is enabled when the back button is pressed.

I think the DialogFragment is intercepting the back button press, because the ComponentActivity never calls mOnBackPressedDispatcher.onBackPressed();

Is there a way to override the DialogFragment handling of back button press?

Choate answered 23/7, 2019 at 8:39 Comment(0)
C
3

I found a solution, but I hope the library will take care of this usecase.

Create a custom BottomSheetDialog:

class BackPressBottomSheetDialog(context: Context, @StyleRes theme: Int, 
private val callback: BackPressBottomSheetDialogCallback) :
        BottomSheetDialog(context, theme) {

    override fun onBackPressed() {
        if (callback.shouldInterceptBackPress()) callback.onBackPressIntercepted()
        else super.onBackPressed()
    }
}

And its interface

interface BackPressBottomSheetDialogCallback {
    fun shouldInterceptBackPress(): Boolean
    fun onBackPressIntercepted()
}

Then in your BottomSheetDialogFragment

private val dialogCallback = object : BackPressBottomSheetDialogCallback {
      override fun shouldInterceptBackPress(): Boolean {
        //TODO should you intercept the back button?
      }

      override fun onBackPressIntercepted() {
        //TODO what happens when you intercept the back button press
      }
}

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
    return BackPressBottomSheetDialog(requireContext(), theme, dialogCallback)
}
Choate answered 23/7, 2019 at 9:5 Comment(0)
S
21

It's really hard to understand what Google Android Dev making. Anyway, i found a solution without using interfaces.

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
    return object : Dialog(requireContext(), theme) {
        override fun onBackPressed() {
            // handle back press
        }
    }
}

Just override the onCreateDialog and also override onBackPressed inside it.

Seabrook answered 15/1, 2020 at 9:4 Comment(0)
S
7

One more solution:

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
    return super.onCreateDialog(savedInstanceState).also {
        val componentDialog = it as ComponentDialog
        componentDialog.onBackPressedDispatcher.addCallback(this) {
            val backPressedConsumed = yourLegacyLogic()
            if (backPressedConsumed.not()) {
                isEnabled = false
                requireActivity().onBackPressedDispatcher.onBackPressed()
                isEnabled = true
            }
        }
    }
}

Explanation

Back-pressed events are passed into the dialog of the DialogFragment, therefore you should register OnBackpressedCallback for the dialog.

Disabling the callback (isEnabled = false) allows you to prevent the consumption of the next back-pressed event. Once you disabled the callback you're retriggering the back pressed-event (calling requireActivity().onBackPressed()) and it will be consumed by someone else, e.g. your navigation will pop the back stack.

After that, you should enable the callback (isEnabled = true) to receive the next back-pressed events.

Sarene answered 28/12, 2022 at 8:35 Comment(0)
R
4

My solution for Kotlin:

Override below method in BottomSheetDialogFragment.

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
    return super.onCreateDialog(savedInstanceState).apply {
        setOnKeyListener { _: DialogInterface, keyCode: Int, keyEvent: KeyEvent ->
            if (keyCode == KeyEvent.KEYCODE_BACK && keyEvent.action == KeyEvent.ACTION_UP) {
                // your code
                // return true if you want keep dialog visible
                // return false if you want to dismiss dialog anyway
                return@setOnKeyListener true
            }
            return@setOnKeyListener false
        }
    }
}
Rita answered 15/12, 2021 at 18:29 Comment(0)
C
3

I found a solution, but I hope the library will take care of this usecase.

Create a custom BottomSheetDialog:

class BackPressBottomSheetDialog(context: Context, @StyleRes theme: Int, 
private val callback: BackPressBottomSheetDialogCallback) :
        BottomSheetDialog(context, theme) {

    override fun onBackPressed() {
        if (callback.shouldInterceptBackPress()) callback.onBackPressIntercepted()
        else super.onBackPressed()
    }
}

And its interface

interface BackPressBottomSheetDialogCallback {
    fun shouldInterceptBackPress(): Boolean
    fun onBackPressIntercepted()
}

Then in your BottomSheetDialogFragment

private val dialogCallback = object : BackPressBottomSheetDialogCallback {
      override fun shouldInterceptBackPress(): Boolean {
        //TODO should you intercept the back button?
      }

      override fun onBackPressIntercepted() {
        //TODO what happens when you intercept the back button press
      }
}

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
    return BackPressBottomSheetDialog(requireContext(), theme, dialogCallback)
}
Choate answered 23/7, 2019 at 9:5 Comment(0)
M
0

We should be using AppCompatDialog instead. It has an OnBackPressDispatcher:

// DialogFragment

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
  return AppCompatDialog(requireContext(), theme).apply {
    onBackPressedDispatcher.addCallback(
      owner = this,
      onBackPressedCallback = object : OnBackPressedCallback(enabled = true) {
        override fun handleOnBackPressed() {
          // do some back stuff when back press happens              
        }
      },
    )
  }
}

AppCompatDialogFragment does this for you, but you must downcast, so I'm not sure it's better.

// AppCompatDialogFragment

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
  return (super.onCreateDialog(/* savedInstanceState = */ savedInstanceState) as ComponentDialog).apply {
    onBackPressedDispatcher.addCallback(
      owner = this,
      onBackPressedCallback = object : OnBackPressedCallback(enabled = true) {
        override fun handleOnBackPressed() {
          // do some back stuff when back press happens              
        }
      },
    )
  }
}
Moneymaker answered 23/4, 2024 at 22:23 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.