How to override onCancel in Dialog Fragment in Android in kotlin? - android

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)
}
}

Related

Kotlin Dialog On Dismiss Listener

I created a custom dialog and I need to do something back in the fragment that called that dialog, when the dialog is dismissed. I tried a number of things I translated from Java but most didn't work or were deprecated. Any suggestions on how to do this would be appreciated.
DialogFragment:
class MyDialogFragment : DialogFragment() {
onAccept() {
//do some things
onDismiss()
}
companion object {
private const val TAG = "My Dialog Fragment"
fun show(
) {
MyDialogFragment().apply {
//args
}.show(fragment.parentFragmentManager, TAG)
}
}
}
In the fragment it just called as follows:
class doStuffFragment : AppFragment {
fun showDialog(){
MyDialogFragment.show(this)
}
}
You can override onCancel() method in your corresponding Dialog Fragment.
you can get more details by Clicking here.
override fun onCancel(dialog: DialogInterface) {
super.onCancel(dialog)
//Your code goes here.
}
If you are using Fragment to show dialog, you should use childFragmentManager, that will provide new FragmentManager inside Fragment.
class SomeFragment : Fragment() {
fun showDialogFromFragment() {
DialogFragment()
.show(this.childFragmentManager, TAG)
}
}
Beside that, in case of you are wanna show dialog inside Activity, supportFragmentManager is your choice.
class SomeActivity : AppCompatActivity() {
fun showDialogFromActivity() {
DialogFragment()
.show(this.supportFragmentManager, TAG)
}
}
I managed to do this by creating custom onDismiss listener in the Dialog and setting it in the "parent" fragment.
Note: I am using a bit different approach to creating dialog, but adding listener should work in your case too.
Adding listener to dialog:
class MyDialogFragment : DialogFragment() {
private var onDismissFunction: () -> Unit = {}
fun setOnDismissFunction(block: () -> Unit){
onDismissFunction = block
}
override fun onDismiss(dialog: DialogInterface) {
onDismissFunction()
super.onDismiss(dialog)
}
Setting the listener in the main fragment:
val dialog = MyDialogFragment()
dialog.setOnDismissFunction {
// your code
}
dialog.show(this.supportFragmentManager, MyDialogFragment.TAG)
Based my answer on this answer

Custom DialogFragment with AlertDialog returns EditText as ""

I have a custom DialogFragment that I'm using to capture user input that I will create a database entry with. I'm using EditText in an AlertDialog. I am trying to use a single activity for my application and the original tutorial I was studying was using multiple activities and intents but that seems outdated for most cases.
When I debug I find that the EditText is returning "" and is showing up as empty when I call TextUtils.isEmpty() in the MainActivity onDialogPositiveClick.
I've done a lot of combing through the forms here and I'm confused by:
1)many of the answers I find are in Java and not Kotlin
2)many mention onCreate but do not specify onCreateView vs. onCreateDialog or if there's just an onCreate that I need to override.
I have researched this and found answers that confuse me a bit about when and if I need to inflate the layout. This current itteration I didn't inflate it at all. I just set it in the AlertDialog builder.
Maybe it's the interface I'm not understanding. How am I supposed to pass information between the dialog and MainActivity? The interface seems to pass the dialog itself but I seem to be missing something when it comes to getting the EditText from the dialog.
My custom DialogFragment
class NewSongFragment : DialogFragment() {
lateinit var listener: NewSongListener
lateinit var editNewSong: EditText
lateinit var editBPM: EditText
interface NewSongListener {
fun onDialogPositiveClick(dialog: DialogFragment)
fun onDialogNegativeClick(dialog: DialogFragment)
}
/** The system calls this to get the DialogFragment's layout, regardless
of whether it's being displayed as a dialog or an embedded fragment. */
/*
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// Inflate the layout to use as dialog or embedded fragment
return inflater.inflate(R.layout.fragment_new_song, container, false)
}
*/
// Override the Fragment.onAttach() method to instantiate the NoticeDialogListener
override fun onAttach(context: Context) {
super.onAttach(context)
// Verify that the host activity implements the callback interface
try {
// Instantiate the NoticeDialogListener so we can send events to the host
listener = context as NewSongListener
} catch (e: ClassCastException) {
// The activity doesn't implement the interface, throw exception
throw ClassCastException((context.toString() +
" must implement NewSongListener"))
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let {
// Use the Builder class for convenient dialog construction
val builder = AlertDialog.Builder(it)
//add inflater
//val inflater = requireActivity().layoutInflater;
//val view = inflater.inflate(R.layout.fragment_new_song, null)
builder
.setView(R.layout.fragment_new_song)
.setCancelable(true)
.setNegativeButton(R.string.cancel,DialogInterface.OnClickListener { dialog, id ->
dialog?.cancel()
})
.setPositiveButton(R.string.button_save,
DialogInterface.OnClickListener {dialog, _ ->
listener.onDialogPositiveClick(this)
})
// Create the AlertDialog object and return it
builder.create()
} ?: throw IllegalStateException("Activity cannot be null")
}
}
My MainActivity
class MainActivity : AppCompatActivity(),NewSongFragment.NewSongListener {
private val songViewModel: SongViewModel by viewModels {
SongViewModelFactory((application as SongApplication).repository)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//create view
val recyclerView = findViewById<RecyclerView>(R.id.recycler_view)
val adapter = ItemAdapter(this,
ItemAdapter.OnClickListener { rating -> songViewModel.insertRating(rating) }
)
recyclerView.adapter = adapter
recyclerView.layoutManager = LinearLayoutManager(this)
//initialize data
songViewModel.allSongs.observe(this) { song ->
// Update the cached copy of the songs in the adapter.
song.let { adapter.submitList(it) }
}
// Use this setting to improve performance if you know that changes
// in content do not change the layout size of the RecyclerView
recyclerView.setHasFixedSize(true)
//add song button
val fab = findViewById<FloatingActionButton>(R.id.fab)
fab.setOnClickListener {
showNewSongDialog()
}
}
private fun showNewSongDialog() {
// Create an instance of the dialog fragment and show it
val dialog = NewSongFragment()
dialog.show(supportFragmentManager, "NewSongFragment")
}
override fun onDialogPositiveClick(dialog: DialogFragment) {
// User touched the dialog's positive button
val editNewSong = dialog.view?.findViewById<EditText>(R.id.newSongTitle)
val editBPM = dialog.view?.findViewById<EditText>(R.id.newSongBpm)
if(TextUtils.isEmpty(editNewSong?.text)){
}else{
val newSong = Song(editNewSong?.text.toString(),100)
songViewModel.insertSong(newSong)
val rating = Rating(System.currentTimeMillis(),newSong.songTitle, 50)
songViewModel.insertRating(rating)
}
}
override fun onDialogNegativeClick(dialog: DialogFragment) {
// User touched the dialog's negative button
}
}
You are adding the layout with a resource identifier, so your call to get the view is returning null. (Why? The view is inflated internally and just handled differently.) Since you are using the AlertDialog to collect data, you will have to add an inflated view.
I am also going to suggest that you change the interface to hide the details of the dialog; There is no reason for the main activity to know the internal structure of the dialog. It just needs the song title and BPM and maybe some other stuff. You will find the code a little easier to understand and maintain.
Here is a slight rework. This code just captures the song title, but it can easily be extended to include other data as well.
In NewSongFragment:
interface NewSongListener {
fun onDialogPositiveClick(songTitle: String)
fun onDialogNegativeClick(dialog: DialogFragment)
}
val inflater = requireActivity().layoutInflater;
val view = inflater.inflate(R.layout.fragment_new_song, null)
builder
.setView(view)
.setCancelable(true)
.setNegativeButton(R.string.cancel, DialogInterface.OnClickListener { dialog, id ->
dialog?.cancel()
})
.setPositiveButton(R.string.button_save)
{ dialog, _ ->
Log.d("Applog", view.toString())
val songTitle = view?.findViewById<EditText>(R.id.newSongTitle)?.text
listener.onDialogPositiveClick(songTitle.toString())
}
In MainActivity.kt
override fun onDialogPositiveClick(songTitle: String) {
// songTitle has the song title string
}
Android dialogs have some quirks. Here are a number of ways to do fragment/activity communication.
Because you are adding the dialog as a Fragment, you should use onCreateView to inflate the view, rather than trying to add a view in onCreateDialog.

Unable to dismiss BottomSheetDialog

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()}}

Is there any way to use Kotlin Anko Alertdialog with handling screen rotation changes?

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>

Destroy DialogFragment on onCreateDialog()

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.

Categories

Resources