I have a Bottom Sheet Dialog Fragment which contains four Fragment with ViewPager.
I want to call a method when onBackPressed clicked in Bottom Sheet Dialog Fragment. Implemented OnBackPressedCallback in my OnCreateView but it is not triggered. Any one have a idea why it is not called?
val callback = object : OnBackPressedCallback(true */ true means that the callback is enabled /*) {
override fun handleOnBackPressed() {
// Show your dialog and handle navigation
LogUtils.d("Bottom Sheet -> Fragment BackPressed Invoked")
}
}
// note that you could enable/disable the callback here as well by setting callback.isEnabled = true/false
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, callback)
I found this thread while looking for a solution to the same problem that exists in DialogFragment. The answers are in the comments above, but for completeness here is the information aggregated:
Solution
In your DialogFragment override onCreateDialog and set an OnKeyListener:
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 onBackPressed logic here -->
return#setOnKeyListener true
}
return#setOnKeyListener false
}
}
}
Explanation
From an issue raised against requireActivity().onBackPressedDispatcher.addCallback not working for DialogFragments (https://issuetracker.google.com/issues/149173280):
Dialogs are separate windows that always sit above your activity's window. This means that the dialog will continue to intercept the system back button no matter what state the underlying FragmentManager is in, or what code you run in your Activity's onBackPressed() - which is where the OnBackPressedDispatcher plugs into.
Essentially the onBackPressedDispatcher is the wrong tool for the job when using any component that utilises Dialogs because of how they behave within an Application and exist outside (on top) of Activities.
#ITJscott has explained very well.
in case any one struggling in understanding/ implementing kotlin code here is JAVA code snippet for the same.
#NonNull
#Override
public Dialog onCreateDialog(#Nullable Bundle savedInstanceState) {
Dialog mDialog = super.onCreateDialog(savedInstanceState);
mDialog.setOnKeyListener((dialog, keyCode, event) -> {
if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
// <-- Your onBackPressed logic here -->
requireActivity().onBackPressed();
return true;
}
return false;
});
return mDialog;
}
This behaviour can also occur if you've set bottom sheet to be non-cancelable using .
So, to avoid this, you can use below code which detects certain events like keypad entry or back press. If you want to perform other action on other events, you can add the code here.
bottomSheetDialog.setOnKeyListener { _, keyCode, _ ->
if (keyCode == KeyEvent.KEYCODE_BACK) {
onBackPressed()
return#setOnKeyListener true
} else {
return#setOnKeyListener false
}
}
Related
I'm a total newbie, and my app has a main activity and a test-taking activity. Normally pressing back button in the test activity takes you back to main activity. This is fine, but I want to add a confirmation dialog asking if they really want to abandon the test first. So far I have the following in the test activity:
override fun onBackPressed() {
var exit = ExitTestDialogFragment()
exit.show(supportFragmentManager,"exit")
}
class ExitTestDialogFragment : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let {
val builder = AlertDialog.Builder(it)
builder.setTitle("Leave Test?")
builder.setMessage("Your score will be lost.")
.setPositiveButton("OK",
DialogInterface.OnClickListener { dialog, id ->
// This is where I'd like to return to Main Activity
})
.setNegativeButton("Cancel",
DialogInterface.OnClickListener { dialog, id ->
dialog.dismiss()// User cancelled the dialog
})
// Create the AlertDialog object and return it
builder.setCancelable(false)
builder.create()
} ?: throw IllegalStateException("Activity cannot be null")
}
}
I can't seem to figure out how how do what would normally be the Activity's super.onBackPressed() from the dialog fragment. Like I said, I'm super new to android, so may need a bit of an ELI5 answer.
call finish() or this.finish() inside your DialogInterface.OnClickListener. Method finish() will destroy current activity that call it, in this case its test-taking activity
You should call mainActivity from your dialog positive Button.
.setPositiveButton("OK",
DialogInterface.OnClickListener { dialog, id ->
// here you can get your current activity
//then dismiss your dialog and finish current activity
//call context.finish or activity.finish here. It will
//finish this activity
//and will take you to the previous activity (in your case
//to mainActivity)
})
If you need any further help feel free to mention it in the comments
Add this in the container dialog
dialog?.setOnKeyListener { dialog, keyCode, event ->
if (keyCode == KeyEvent.KEYCODE_BACK && event.action == KeyEvent.ACTION_UP) {
handleBack() // your code
return#setOnKeyListener true
} else
return#setOnKeyListener false
}
I have a form and want to get a confirmation message from the user before the user leaves it.
i want provide custom back button when user touch this button:
i try this:
val onBackPressedCallback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
}
}
requireActivity().onBackPressedDispatcher.addCallback(this,onBackPressedCallback)
but only seems to work for providing custom back behavior to the built-in software/hardware back button and not the back arrow button
How can I do this?
Use onSupportNavigateUp, and replace yourCurrentFragmentID to your current fragment id. All these should be done in MainActivity.
navController = findNavController(R.id.nav_host_fragment)
setupActionBarWithNavController(navController!!)
override fun onSupportNavigateUp(): Boolean {
return when(navController?.currentDestination?.id) {
R.id.yourCurrentFragmentID -> {
showDialog()
true
}
else -> navController?.navigateUp()!!
}
}
Edit
If your fragment already use onOptionsItemSelected, you can handle the logic by checking itemId.
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val id = item.itemId
if (id == R.id.save) {
// your save button code logic
}else if(id ==android.R.id.home){
// display confirmation message
}
return super.onOptionsItemSelected(item)
}
Use this code to set Activity on backpress.
override fun onBackPressed() {
if (isDiscardChanges) {
discardDialog()
} else {
super.onBackPressed()
}
}
If you only want to go back from Activity, then you can add within AndroidManifest.xml child class Activity.
android:parentActivityName="Parent_Activity_Name
I'm currently running into a strange problem, similar to here Shared element transition when using ActionBar Back button but with a fragment to fragment shared element transition. It works fine when using the back button. As soon as the toolbar home is triggered it's just blinking. So calling finishAfterTransition() is no option here.
I call the same method both times. The base for back navigation
override fun onBackPressed() {
if (coordinator != null) {
coordinator!!.back()
} else {
finish()
}
}
and the intercepted toolbar home click.
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
return when (item?.itemId) {
android.R.id.home -> {
onBackPressed()
true
}
else -> return super.onOptionsItemSelected(item)
}
}
Update: Now I noted like one out of ten tries it works correctly.
I hope anyone of you guys has a clue why this happens.
Regards coffeelord
I have an activity with two fragments.
The second one is called when I click on something to the first.
What I want is this : if i click on "back" button, I want to go back to the first fragment (that is working), but I want to set the visibility to VISIBLE on an element (if the first fragment is called with back press only)
How do I do that ?
I tried something like this (in my main fragment), I've found the idea in another topic, but this is trigger always in my main activity :
override fun onResume() {
super.onResume()
view?.isFocusableInTouchMode = true
view?.requestFocus()
view?.setOnKeyListener { v, keyCode, event ->
if(event.action == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK){
Log.i(TAG, "backpress pressed")
return#setOnKeyListener true
}
return#setOnKeyListener false
}
}
Temporary solution :
I've created a companion object with a value true or false and I change it everytime I need it, but it's temporary only.
Assuming your second Fragment replaces the first (i.e. using FragmentTransaction#replace), your first Fragment (we'll call them FragmentA and FragmentB) will be paused (i.e. onPause() will be called on FragmentA).
When you press the back button, the backstack will be popped, and FragmentA will be resumed (i.e. onResume() will be called).
What I would recommend, is to save a boolean flag in FragmentA, and set it to true when you show FragmentB. Then, in FragmentA#onResume, you can check if the flag is set to true, and set it back to false while handing the case that you wanted.
For example, something like:
private const val STATE_WAITING_FOR_FRAGMENT_B = "state_waiting_b"
class FragmentA : Fragment() {
private var isWaitingForFragmentB: Boolean = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (savedInstanceState != null) {
isWaitingForFragmentB = savedInstanceState.getBoolean(STATE_WAITING_FOR_FRAGMENT_B)
}
}
override fun onResume() {
super.onResume()
if (isWaitingForFragmentB) {
isWaitingForFragmentB = false
// handle your view state here
}
}
override fun onSaveInstanceState(savedInstanceState: Bundle) {
super.onSaveInstanceState(savedInstanceState)
savedInstanceState.putBoolean(
STATE_WAITING_FOR_FRAGMENT_B,
isWaitingForFragmentB
)
}
private fun showFragmentB() {
isWaitingForFragmentB = true
// do fragment transaction here
}
}
I'm not good at grammar.
First fragment do not call resume function when returning.
You must create callback with interface.
A good approach should be passing some flag, on the second fragment, by activity intent and to capture it on the first Fragment on onResume()
If you need extra info, just let me know
I have Extension of EditText where I'm listenig to events from keyboard of EditText. I need to know when user press any button for showing (or not showing) error. So I make Observable for keys (rxbinding2) and I'm getting any press but when I press back button and cursor still in this EditText method onBackPressed doesn`t work.
How to filter onBack pressed?
fun EditText.changeWithFormatting(formatter: (String) -> String): Observable<String> {
return Observable.merge(
afterTextChangeEvents()
.map { editableText },
keys()
.filter { it.action == KeyEvent.ACTION_UP }
.map { editableText }
)
.map { changeText(formatter(it.toString())) }
}
Just listen for the keydown event. This should trigger before it gets handed to your UI element. super will pass it to the parent to go normal flow. I can't remember if it's return true or false, but I think it's true for handled.
#Override
public boolean onKeyDown(int keyCode, KeyEvent event)
{
if ((keyCode == KeyEvent.KEYCODE_BACK))
{
onBackPressed();
return true;
}
return super.onKeyDown(keyCode, event);
}