I have a dialog fragment that initializes Google plus views, sometimes those views fail so I'd like to kill the dialog at that point, before it's displayed to the user.
How can I end the dialog creation process? returning null from onCreateDialog which returns a Dialog object crushes the program.
If you'd like to dismiss DialogFragment within onCreateDialog you can do the following:
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
setShowsDialog(false);
dismiss();
return null;
}
No need to override onActivityCreated().
Solved it using the onActivityCreated() Fragment callback which is called after OnCreateDialog(). I return a valid Dialog from onCreateDialog() but flag with dismiss that the dialog should be dismissed.
public void onActivityCreated (Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if(dismiss) {
this.dismiss();
}
}
The other answers are a bit outdated and one didn't work for me (wrote there my comments about it), so here's what I think is updated and working:
On the onCreateDialog callback, have your logic of when it succeeds. If failed, return some default dialog (won't be used anyway) while adding to the lifecycle's onStart callback to dismiss the DialogFragment (use dismiss or dismissAllowingStateLoss):
fun Lifecycle.runOnStarted(runnable: () -> Unit) {
addObserver(object : DefaultLifecycleObserver {
override fun onStart(owner: LifecycleOwner) {
super.onStart(owner)
runnable.invoke()
}
})
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
//create the builder of the dialog, and then check if need to dismiss
if (needToDismiss) {
lifecycle.runOnStarted{
dismissAllowingStateLoss()
}
return builder.create()
}
Alternative, using kotlin coroutines and without using the helper function I've made:
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
//create the builder of the dialog, and then check if need to dismiss
if (needToDismiss) {
lifecycleScope.launch {
whenStarted {
dismissAllowingStateLoss()
}
}
return builder.create()
}
Or you can use this helper function that uses kotlin coroutines to make it a bit shorter:
fun LifecycleOwner.runOnStarted(runnable: () -> Unit) {
lifecycleScope.launch {
whenStarted{
runnable.invoke()
}
}
}
The usage would be as short as before:
runOnStarted{
dismissAllowingStateLoss()
}
Note that I use onStart callback instead of onCreate. The reason is that for some cases, onStart works, while onCreate won't.
Related
I'm using following approach to create bottomsheet dialog, dialog is creating but I want to dismiss on backpress for that all setup I'm using the code below. But not doing able to dismiss dialog.
class MainActivity : BaseClass(), View.OnClickListener {
private lateinit var bottomSheetDialog: BottomSheetDialog
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
bottomSheetDialog=BottomSheetDialog(this)
create.setOnClickListener {
createBottomSheetDialog()
}
}
override fun onBackPressed() {
if (bottomSheetDialog.isShowing){
bottomSheetDialog.dismiss()
}
super.onBackPressed()
}
private fun createBottomSheetDialog(){
bottomSheetDialog.setContentView(R.layout.bottom_sheet_dialog)
bottomSheetDialog.setCancelable(false)
bottomSheetDialog.show()
}
}
I have been tried using this.bottomSheetDialog.dismiss()
but not working I also have try dismissing the dialog without if statement and without using super.onBackPressed() but not working.
you can simply remove this line from your code
bottomSheetDialog.setCancelable(false)
You can get more details about this here. I guess you need to stop dismiss when clicked anywhere outside the fragment so you can add this :-
bottomSheetDialog.setCanceledOnTouchOutside(false)
It will allow you to dismiss onBackPressed but it won't allow you to dismiss if clicked anywhere outside of your bottom sheet.
Also no need to write this in your Main activity :-
override fun onBackPressed() {
if (bottomSheetDialog.isShowing){
bottomSheetDialog.dismiss()
}
super.onBackPressed()
}
Bottom Sheet dialogue will itself dismiss onBackPressed.
Try this:
override fun onBackPressed() {
if (bottomSheetDialog.isShowing){
bottomSheetDialog.dismiss()
} else {
super.onBackPressed()
}
}
Try setting global variable for activity dialog: BottomSheetDialog? = null
Then in the function for displaying dialog give it a value and show it. Then in onCreate (or whenever you want) just use global variable as a reference eg.:
onBackPressed(){
if(dialog != null){
dialog.dismiss()}}
Using Anko library is pretty easy, but when i rotate screen, my dialog dismisses. The only way how to avoid this is to use child of DialogFragment() with method show(fm, TAG).
So we need to override method onCreateDialog(savedInstanceState: Bundle?): Dialog which returns Dialog instance. But Anko's alert{ }.build() returns DialogInterface instance
So, is there any way to use anko in this situation?
alert {
message = "Message"
positiveButton("OK") {
//stuff
}
negativeButton("NOT OK") {
//stuff
}
}.show()
EDIT
So, that what I did.
I've created abstract BaseDialogFragment:
abstract class BaseDialogFragment : DialogFragment() {
abstract val ankoAlert: AlertBuilder<DialogInterface>
protected var dialogListener: DialogListener? = null
protected val vm by lazy {
act.getViewModel(DialogViewModel::class)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
dialogListener = parentFragment as? DialogListener
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog =
(ankoAlert.build() as? Dialog)
?: error("Anko DialogInterface is no longer backed by Android Dialog")
}
Then I've created some dialogs, like that:
class MyDialogFragment : BaseDialogFragment() {
companion object {
fun create() = MyDialogFragment ()
}
override val ankoAlert: AlertBuilder<DialogInterface>
get() = alert {
negativeButton(R.string.app_canceled) {
dialogListener?.onDismiss?.invoke()
}
customView = createCustomView(vm.data)
}
fun createCustomView(data: Data): View {
//returning view
}
}
Also my DialogListener is sort of that:
interface DialogListener {
var onDismiss: () -> Unit
val onClick: (Data) -> Unit
var onPostClick: (Data) -> Unit
}
And finally, in parent fragment we can use:
MyDialogFragment.create().show(childFragmentManager, MyDialogFragment::class.java.simpleName)
Hope it will help somebody.
From the Android Documentation, Dialog implements DialogInterface. So all known subclasses of Dialog including AlertDialog implement that interface.
You can cast and return the result from the build as follows:
return alert {
message = "Message"
positiveButton("OK") {
//stuff
}
negativeButton("NOT OK") {
//stuff
}
}.build() as Dialog
This will work but if Anko ever changes its implementation you will get a ClassCastException. To get a cleaner error you can use the following.
val dialogInterface = alert {
message = "Message"
positiveButton("OK") {
//stuff
}
negativeButton("NOT OK") {
//stuff
}
}.build()
return (dialogInterface as? Dialog) ?: error("Anko DialogInterface is no longer backed by Android Dialog")
This gives you a more explicit error, but most likely won't be needed.
If you don't have dynamic UI then you can use android:configChanges="orientation" in your activity in manifest, look likes:
<activity android:name=".MainActivity"
android:configChanges="orientation">
...
</activity>
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?
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.
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)
}
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
}
}
}
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.
I want to show a progress bar DialogFragment.
It would be shown until either it is cancelled or dismissed.
It can be cancelled if the user either presses back button or touches outside of the dialog, and is dismissed if the user doesn't cancel it before the completion of the task.
So, I want to set listeners for both so I can respond according to the case.
The dialog is being called from a Fragment.
According to this, I can't set listeners, instead I have to override the methods.
My main problem is, I don't know how to do that in kotlin.
I have written some of the code below but it is incomplete. Please correct the code where needed.
I am trying to implement only onCancel for now. Please do tell if onDismiss is needed to be implemented in some different way.
Following the solution here,
this is how I have coded the Fragment:
class MyFragment: Fragment(), DialogInterface.OnCancelListener {
// other code
private fun myFun() {
// show progress dialog
val myDialog = DialogProgress()
myDialog.show(childFragmentManager, "null")
// todo the long task of downloading something
// myDialog.dismiss()
}
override fun onCancel(dialog: DialogInterface?) {
// User canceled the dialog
Toast.makeText(activity, "Process canceled by user!", Toast.LENGTH_SHORT).show()
// todo
}
}
And this is my DialogFragment code:
class DialogProgress: DialogFragment() {
override fun onCancel(dialog: DialogInterface?) {
super.onCancel(dialog)
// need help here
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
super.onCreateDialog(savedInstanceState)
// show progress dialog
val v = activity!!.layoutInflater.inflate(R.layout.dialog_progress, null)
v.findViewById<TextView>(R.id.progress_message).text = "Fetching data"
return activity!!.let {
val builder = AlertDialog.Builder(it, R.style.ThemeOverlay_AppCompat_Dialog)
builder
.setView(progressView)
.create()
}
}
}
For the above code where I need help, I don't know how to convert the following Java code from the link of solution given above into kotlin:
#Override
public void onDismiss(final DialogInterface dialog) {
super.onDismiss(dialog);
Fragment parentFragment = getParentFragment();
if (parentFragment instanceof DialogInterface.OnDismissListener) {
((DialogInterface.OnDismissListener) parentFragment).onDismiss(dialog);
}
}
Note that this is for onDismiss, I want it for onCancel.
The Java code can be simply converted into kotlin code by pasting it into the Android Studio and a pop-up should appear.
This is the conversion of the java code:
override fun onCancel(dialog: DialogInterface?) {
super.onCancel(dialog)
val parentFragment = parentFragment
if (parentFragment is DialogInterface.OnCancelListener) {
(parentFragment as DialogInterface.OnCancelListener).onCancel(dialog)
}
}
My bottomSheet behaves correctly except in this situation. When I return to the activity via 'back button', I want the bottomSheet to collapse and I thought the code below would do the trick, but it doesn't work. What could be the cause ? (I confirmed with debugger that it reaches the statement)
#Override
public void onBackPressed() {
mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
}
the same line works fine when it returns via finish():
if (resultCode == Activity.RESULT_OK) {
mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
}
If your dialog has setCancelable(true), back button will not trigger the onbackpressed(), you can try this
I have a class call BottomSheetFragmentDialog which is extend from BottomSheetDialogFragment and I have setCanceledOnTouchOutside(false) inside the onCreateDialog Method also overrdie the onCancel()
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val dialog = super.onCreateDialog(savedInstanceState)
dialog.setCanceledOnTouchOutside(false)
return dialog
}
override fun onCancel(dialog: DialogInterface) {
super.onCancel(dialog)
Toast.makeText(context, "Break Point Here", Toast.LENGTH_SHORT).show()
}
you can use this code onBackPressed() methode
behavior.setState(BottomSheetBehavior.STATE_HIDDEN);
Simpler solution.
Don't have to override onBackPressed method, just have to remove setCancelable(true) and add bottomSheetDialog.setCanceledOnTouchOutside(false) when you initialise the bottomSheetDialog.