Search code examples
androidkotlindialogfragment

DialogFragment Listener


I have a DialogFragment with a listener for when a button gets clicked to call a function in my fragment.

I am getting lateinit property listener has not been initialized when I click the positive button.

DialogFragment

class CreateCollectionDialog: DialogFragment() {
    lateinit var listener: CreateCollectionDialogListener

    interface CreateCollectionDialogListener {
        fun onDialogPositiveClick(dialog: DialogFragment, collectionName: String)
        // fun onDialogNegativeClick(dialog: DialogFragment)
    }

    override fun onAttachFragment(childFragment: Fragment) {
        println("onAttachFragment")
        super.onAttachFragment(childFragment)
        listener = context as CreateCollectionDialogListener
        println(listener)
    }

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        return activity?.let {
            val builder = AlertDialog.Builder(it)
            val inflater = requireActivity().layoutInflater
            builder.setView(inflater.inflate(R.layout.dialog_collection, null))
                .setPositiveButton("Create", DialogInterface.OnClickListener { dialog, id ->
                    // Create new collection
                    var newCollectionName = view?.findViewById<EditText>(R.id.newCollectionName)?.text.toString()
                    if (!newCollectionName.equals("") && newCollectionName != null) {
                        listener.onDialogPositiveClick(this, newCollectionName)
                    }
                })
                .setNegativeButton("Cancel", DialogInterface.OnClickListener { dialog, id ->
                    // User canceled dialog
                    // listener.onDialogNegativeClick(this)
                })
            builder.create()
        }?: throw IllegalStateException("Activity cannot be null")
    }

    override fun onStart() {
        super.onStart()
        val positive: Button = (dialog as AlertDialog?)!!.getButton(AlertDialog.BUTTON_POSITIVE)
        positive.setTextColor(resources.getColor(R.color.topColor))

        val negative: Button = (dialog as AlertDialog?)!!.getButton(AlertDialog.BUTTON_NEGATIVE)
        negative.setTextColor(Color.RED)
    }
}

Fragment

class CollectionsFragment: Fragment(), CreateCollectionDialog.CreateCollectionDialogListener {
     override fun onOptionsItemSelected(item: MenuItem): Boolean {

        when (item.itemId) {
            R.id.add -> {
                val createDialog = CreateCollectionDialog()
                createDialog.show(fragmentManager!!, "")
                return true
            }
        }
        return false
    }

    override fun onDialogPositiveClick(dialog: DialogFragment, collectionName: String) {
        addNewCollection(collectionName)
    }
}

Solution

  • The simplest way to solve this problem would be to assign the listener at the time you create the dialog:

    when (item.itemId) {
        R.id.add -> {
            val createDialog = CreateCollectionDialog()
            createDialog.listener = this
            createDialog.show(fragmentManager!!, "")
            return true
        }
    }
    

    However, note that this will have problems if the activity is destroyed and recreated due to a configuration change.

    To solve that, I would leverage the concept of "target fragments":

    when (item.itemId) {
        R.id.add -> {
            val createDialog = CreateCollectionDialog()
            createDialog.setTargetFragment(this, 0)
            createDialog.show(fragmentManager!!, "")
            return true
        }
    }
    

    And now, in your other fragment, instead of having a listener field, you can just cast the targetFragment property:

    if (!newCollectionName.equals("") && newCollectionName != null) {
        val listener = targetFragment as CreateCollectionDialogListener
        listener.onDialogPositiveClick(this, newCollectionName)
    }