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
Related
I have a DialogFragment that should show from a parent fragment. After showing My DialogFragment, It will do seconds of work and dismiss the dialog.
Everything is OK, but when the screen rotates during its work, my app crashes on calling dialogFragment.dismiss() :
java.lang.IllegalStateException: Fragment MyDialogFragment ... not associated with a fragment manager.
Here is my code...
Parent Fragment :
class MyFragment: Fragment() {
...
fun showDialog(){
val dlg = MyDialogFragment()
dlg.show(childFragmentManager, "dlg")
MainScope().launch {
// Some time-consuming work...
delay(10000)
dlg.dismiss()
}
}
}
Dialog Fragment :
class MyDialogFragment : DialogFragment()
{
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let {
val inflater = requireActivity().layoutInflater
val v=inflater.inflate(R.layout.my_dialog, null)
val builder = AlertDialog.Builder(it)
builder.setView(v)
.setTitle("title")
.setNegativeButton("cancel") { dialog, id ->
dialog?.cancel()
}
builder.create()
} ?: throw IllegalStateException("Activity cannot be null")
}
}
What I've tried :
using dlg.retainInstance=true, will fix the problem but seems to be incorrect because of depreciation.
using:
val d=childFragmentManager.findFragmentByTag("dlg") as MyDialogFragment
d.dismiss()
instead of dlg.dismiss(), That does not work and crashes with Fragment MyFragment ... has not been attached yet.
I followed the official documentation to create a Dialog prompt on my app. It was working, but when I added the listener to process the data from the calling Fragment, I got a crash. It cannot instantiate the context as a listener. Here is some of my code :
The calling fragment :
class ConfigWifiFragment : Fragment(), BleManagerListener, WifiPasswordDialogFragment.NoticeDialogListener {
companion object {
fun newInstance() = ConfigWifiFragment()
}
private var _binding: FragmentConfigWifiBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
private var dialogPassword = WifiPasswordDialogFragment()
//... some code
private fun connectToWifiNetwork(ssid: String)
{
Log.i("Connection", ssid)
Log.i("Context", context.toString())
getFragmentManager()?.let { dialogPassword.show(it, "passwordWifi") }
}
override fun onDialogPositiveClick(dialog: DialogFragment) {
TODO("Not yet implemented")
Log.i("ConfigWifi", dialog.config_wifi_password.text.toString())
}
override fun onDialogNegativeClick(dialog: DialogFragment) {
TODO("Not yet implemented")
Log.i("ConfigWifi", "not ok")
}
}
The dialog:
import android.app.AlertDialog
import android.app.Dialog
import android.content.Context
import android.content.DialogInterface
import android.os.Bundle
import android.util.Log
import androidx.fragment.app.DialogFragment
class WifiPasswordDialogFragment : DialogFragment() {
internal lateinit var listener: NoticeDialogListener
interface NoticeDialogListener {
fun onDialogPositiveClick(dialog: DialogFragment)
fun onDialogNegativeClick(dialog: DialogFragment)
}
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 NoticeDialogListener // CRASH
} catch (e: ClassCastException) {
// The activity doesn't implement the interface, throw exception
throw ClassCastException((context.toString() +
" must implement NoticeDialogListener"))
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let {
val builder = AlertDialog.Builder(it)
// Get the layout inflater
val inflater = requireActivity().layoutInflater;
// Inflate and set the layout for the dialog
// Pass null as the parent view because its going in the dialog layout
builder.setView(inflater.inflate(R.layout.dialog_wifi_password, null))
// Add action buttons
.setPositiveButton(R.string.validation,
DialogInterface.OnClickListener { dialog, id ->
// sign in the user ...
Log.i("dialog", "ok")
listener.onDialogPositiveClick(this)
})
.setNegativeButton(R.string.cancel,
DialogInterface.OnClickListener { dialog, id ->
//getDialog().cancel()
Log.i("dialog", "Cancel")
listener.onDialogNegativeClick(this)
})
builder.create()
} ?: throw IllegalStateException("Activity cannot be null")
}
}
The code crash in that line: listener = context as NoticeDialogListener, it cannot get the context as a NoticeDialogListener even if it's implemented in the calling fragment (and then the exception is thrown). I checked the ID of the context before showing the dialogue prompt, and the one inside; it's the same.
The only difference between my code and the example is I'm using a Fragment and not a FragmentActivity. How could I make works my code in my case?
I found a solution, I don't know if it's the more elegant way, but here is it:
1st: I removed the onAttach function in my DialogFragment
2nd: I created a method to set the listener :
fun setListener(_listener : WifiPasswordDialogListener)
{
listener = _listener
}
3rd: I called that method just before the show method:
private fun connectToWifiNetwork(ssid: String)
{
Log.i("Connection", ssid)
Log.i("Context", context.toString())
dialogPassword.setListener(this)
getFragmentManager()?.let { dialogPassword.show(it, "passwordWifi") }
}
I can now access to the data of my DialogBox from the calling fragment like this :
override fun onDialogPositiveClick(dialog: DialogFragment) {
Log.i("ConfigWifi", dialog.dialog?.config_wifi_password?.text.toString())
}
How does one handle button clicks in a Dialog fragment. I would like to run method in fragment once a dialog button is clicked, the example given in dev docs works for activities but not fragments.
MyAlertDialog
class MyAlertDialog : DialogFragment() {
var listener: MyListener
interface MyListener {
onPositiveClick(dialog: DialogFragment)
}
override fun onAttach(context: Context) {
super.onAttach(context)
try {
listener = context as MyListener
} catch (e: ClassCastException) {
throw ClassCastException((context.toString() + " must implement MyDailogListener")
}
}
override fun onCreateDialog(savedInstanceState:Bundle?): Dialog {
return activity?.let {
val builder = AlertDialog.Builder(it)
builder.setMessage("Do you want to reset the score.")
.setPositiveButton("Confirm",
DialogInterface.OnClickListener { dialog, id ->
...
})
builder.create()
} ?: throw IllegalStateException("Activity cannot be null")
}
}
MyFragment
class MyFragment : Fragment(), MyAlertDialog.MyListener {
...
fun launchAlertDialog() {
val dailog = MyAlertDialog().also {
it.show(requireActivity().supportFragmentManager, "DialogInfoFragment")
}
}
override fun onDialogPostiveCLick(dialog: DialogFragment) {
Log.i(TAG, "Listener returns a postive click")
}
}
The DialogFragment is launched from the activity not from the fragment despite being called in the fragment. Linking the interface this way will only work if you are connecting to an activity.
If you are only updating data, you could pass the ViewModel into the DialogFragment through the constructor.
Fragment
fun launchAlertDialog() {
val fragment = MyFragment(viewModel)
fragment.show(requireActivity().supportFragmentManager, "MyAlertDialog")
}
DialogFragment
class MyAlertDialog(val viewModel: MainViewModel) : DialogFragment() {
overrideOnCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let {
val builder = AlertDialog.Builder(it)
builder.setMessage(...)
.setPositiveButton("My Text", DialogInterface.OnClickListener { dialog, id ->
viewModel.updateMyData()
})
builder.create()
}
} ?: throw IllegalStateException("Activity cannot be null")
}
You could also use this approach here to run a function in the Fragment.
https://stackoverflow.com/a/65116644/3943340
Keep in mind that setTargetFragment is now deprecated, as of API 28(could be wrong on the API).
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 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)
}
}