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
}
Related
I have an AlertDialog that I want to display at least once to the user and then continuously display the dialog to the user even after the user clicks "ok" until a certain condition is met.
Here's the code structure I have so far for the AlertDialog:
do {
val dialogShow: AlertDialog.Builder = AlertDialog.Builder(this#MainActivity)
dialogShow.setCancelable(false)
dialogShow.setMessage("Message")
.setPositiveButton(
"ok",
object : DialogInterface.OnClickListener {
override fun onClick(dialogInterface: DialogInterface, i: Int) {
if (checkCondition()) {
conditionMet = true
} else {
// Keep looping
}
}
})
.setNegativeButton(
"cancel",
object : DialogInterface.OnClickListener {
override fun onClick(dialogInterface: DialogInterface, i: Int) {
conditionMet = true
return
}
})
dialogShow.show()
} while (conditionMet == false)
The problem now that I am facing is the AlertDialog will display once, but then never again. Even if conditionMet = false it still won't continue to display. How do I keep displaying the same AlertDialog in a loop?
By wrapping the show code in a loop, you're showing it continuously. What you probably want to do it re-show the dialog if it is dismissed. So something like this pseudocode:
fun showObtrusiveDialog() {
...
dialog.setPositiveButton {
if(shouldStillBeObtrusive()) showObtrusiveDialog()
...
}.setNegativeButton {
...
}
dialog.show()
}
An alternate way to handle this would be to disable the buttons until you're ready to allow the dialog to be closed by the user. Here's an extension function you could call when your condition changes:
fun AlertDialog.setAllButtonsState(enabled: Boolean) {
arrayOf(DialogInterface.BUTTON_POSITIVE, DialogInterface.BUTTON_NEGATIVE, DialogInterface.BUTTON_NEUTRAL)
.forEach { getButton(it)?.setEnabled(enabled) }
}
So you can call this to disabled them before you show it, and call it again when your condition changes. You'll need to keep the dialog in a property so you can access it from wherever your condition is being changed.
When I click on the back button, I want to close the connection in service and go back to previous activity.
Code in second activity:
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
if (keyCode == KeyEvent.KEYCODE_BACK) {
if(mBound){
val dialog = ConfirmDialog()
dialog.show(supportFragmentManager, "confirm")
//should wait here until "yes" button is pressed and if so, run the code
mService.closeConnection()
finish()
}
return false
}
return super.onKeyDown(keyCode, event)
}
When the dialog opens the code continues and closes the connection and goes back to previous activity. I want to wait until I click the "yes" button and then continue code below. I know I should have the code inside dialog, but I can't bind the service to the dialog, so I can't stop the connection from there.
Dialog:
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val builder = AlertDialog.Builder(requireActivity())
builder.setView(view)
.setTitle("Confirm")
.setMessage("Do you want to exit?")
.setNegativeButton("no", DialogInterface.OnClickListener(){ _: DialogInterface, _: Int ->
//TODO
})
.setPositiveButton("yes", DialogInterface.OnClickListener(){ _: DialogInterface, _: Int ->
//TODO
})
return builder.create()
}
Again, what I want is simple, when clicking the back button, I can confirm or decline, if I confirm, close the connection in service and go back to previous activity. I guess there is an easier way to do this
I believe your ConfirmDialog() function returns an AlertDialog in which case you can access the components of your alerdialog view this way and close the connection on button click:
dialog.(your_yes_button_id).setOnClickListener {
}
the above code should go below the following:
dialog.show(supportFragmentManager, "confirm")
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
}
}
I'm having a problem with AlertDialog: if I want to finish the current activity after I open a new one, I get crashed with the following error:
E/WindowManager: android.view.WindowLeaked: Activity com.myapp.ShowsActivity has leaked window DecorView#2435213[ShowsActivity] that was originally added here
This happens when I want to log out a user. The AuthLogic.logout() method gets called (I exported it to a standalone class to have a more readable code) which triggers a "are you sure?" alert dialog which then redirects to login activity.
Logout button logic:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_shows)
// some code . . .
btnLogout.setOnClickListener {
AuthLogic.logout(this)
finish() // *1
}
}
AuthLogic.logout():
fun logout(context: Context) {
val builder = AlertDialog.Builder(context)
builder.setTitle(R.string.are_you_sure)
builder.setMessage(R.string.confirm_msg_logout)
builder.setPositiveButton(R.string.confirm) { dialogInterface: DialogInterface, i: Int ->
// aditional logout logic
context.startActivity(AuthActivity.newStartIntent(context))
}
builder.setNegativeButton(R.string.cancel) { di: DialogInterface, _: Int -> }
builder.show()
}
I noticed that the error pops up if I finish my activity on *1 comment. If I remove that line, no error gets shown, but this causes a problem because I can go back to the previous activity and I don't want that. I already tried using dialogInterface.dismiss() on setPositiveButton lambda method, but no success. Any ideas?
EDIT! solved!:)
I rewrote AuthLogic.logout():
fun logout(context: Context) {
val builder = AlertDialog.Builder(context)
builder.setTitle(R.string.are_you_sure)
builder.setMessage(R.string.confirm_msg_logout)
builder.setPositiveButton(R.string.confirm) { dialogInterface: DialogInterface, i: Int ->
// logout logic ...
dialogInterface.dismiss()
context.startActivity(AuthActivity.newStartIntent(context))
(context as Activity).finish()
}
builder.setNegativeButton(R.string.cancel) { di: DialogInterface, _: Int -> }
builder.show()
}
and removed the finish() line seen on *1.
Thanks to #Johan Kovalski for the tip.
Try to dimiss your alert dialog before finish the activity.
yourAlertDialog.dimiss();
Dialog Window is created with activity Context. Activity has to cleanup windows that it owns. You should destroy dialog first, then activity. Or use DialogFragment.
Log you mentioned in question tells you that activity cannot be properly destroyed (as you requested with finish()) because dialog still holds reference to it.
I'd like to show a custom dialog when a user clicks back button in Kotlin.
I tried this code but it doesn't work, when I click the back button the custom dialog shows and then disappears
override fun onBackPressed() {
super.onBackPressed()
onPause()
creatAlertDialog()
}
fun creatAlertDialog() {
var dialogs = Dialog(this#MainActivity)
dialogs.requestWindowFeature(Window.FEATURE_NO_TITLE)
dialogs.setCancelable(false)
dialogs.setContentView(R.layout.back_press)
dialogs.btn_yes.setOnClickListener {
finish()
}
dialogs.btn_no.setOnClickListener {
dialogs.dismiss()
}
dialogs.show()
}
Delete super.onBackPressed() from your onBackPressed() callback. This way you'll avoid your super class to call its onBackPressed() method and your activity will not be destroyed.
override fun onBackPressed() {
creatAlertDialog()
// whatever you want here
}