Wednesday, 27 January 2021

NullPointerException on dismiss DialogFragment

I get quite a lot of errors in crashlitycs when I try to dismiss a DialogFragment. This is the error that I get:

Attempt to invoke virtual method 'android.os.Handler android.app.FragmentHostCallback.getHandler()' on a null object reference 

The line were I am getting is this one showGenericError { activity?.onBackPressed() }

viewLifecycleOwner.observe(viewModel.showErrorAndExit, {
    showGenericError { activity?.onBackPressed() }
})

and here is the method that initalize the dialog:

fun showGenericError(actionOnDismiss: (() -> Unit)? = null) {
    val manager = childFragmentManager

    if (popUpErrorCard == null) {
        popUpErrorCard = PopupCard.Builder(R.string.button_try_later)?.apply {
            setDescription(R.string.error_card_description_text)
            setTitle(R.string.subscribe_error_dialog_title)
            setImage(R.drawable.channels_error_popup)
        }.build()?.apply {
            setDismissListener(object : PopupCard.DismissListener {
                override fun onDismiss() {
                    actionOnDismiss?.invoke()
                }
            })
        }
    }

    if (popUpErrorCard?.isAdded == false && popUpErrorCard?.isVisible == false && manager.findFragmentByTag(ERROR_DIALOG_TAG) == null) {
        popUpErrorCard?.show(manager, ERROR_DIALOG_TAG)
        manager.executePendingTransactions()
    }
}

The line where I am getting the error is actionOnDismiss?.invoke()

And finally the DialogFragment is this one:

class PopupCard private constructor() : DialogFragment() {

private lateinit var dialog: AlertDialog
private var negativeListener: View.OnClickListener? = null
private var positiveListener: View.OnClickListener? = null
private var dismissLitener: DismissListener? = null

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
    val builder = AlertDialog.Builder(requireActivity())
    val inflater = requireActivity().layoutInflater
    val view = inflater.inflate(R.layout.popup_card, null)

    @Suppress("UNCHECKED_CAST")
    arguments?.let args@{ bundle ->
        val negativeText: Int? = bundle.getInt(NEGATIVE_BUTTON_TEXT)

        if (negativeText != null && negativeText != 0) {
            view.negativeButton.setText(negativeText)
        } else {
            view.negativeButton.visibility = View.GONE
        }

        val image: Int? = bundle.getInt(IMAGE_RESOURCE)
        image?.let {
            view.imageHeader.setImageResource(it)
        } ?: run {
            view.imageHeader.visibility = View.GONE
        }

        val titleRes: Int? = bundle.getInt(TITLE_RES)
        val titleText: String? = bundle.getString(TITLE)
        when {
            !titleText.isNullOrBlank() -> {
                view.title.text = titleText
            }
            titleRes != null && titleRes != 0 -> {
                view.title.setText(titleRes)
            }
            else -> view.title.visibility = View.GONE
        }

        val descriptionRes: Int? = bundle.getInt(DESCRIPTION_RES)
        val descriptionText: String? = bundle.getString(DESCRIPTION)
        when {
            !descriptionText.isNullOrBlank() -> {
                view.description.text = descriptionText
            }
            descriptionRes != null && descriptionRes != 0 -> {
                view.description.setText(descriptionRes)
            }
            else -> view.description.visibility = View.GONE
        }

        val actionPair = bundle.getInt(POSITIVE_BUTTON_TEXT)
        view.positiveButton.setText(actionPair)
    }

    builder.setView(view)

    dialog = builder.create()

    view.positiveButton.setOnClickListener {
        positiveListener?.onClick(it)
        dialog.dismiss()
    }

    view.negativeButton.setOnClickListener {
        negativeListener?.onClick(it)
        dialog.dismiss()
    }

    return dialog
}

fun setOnPositiveClickListener(listener: View.OnClickListener) {
    this.positiveListener = listener
}

fun setOnNegativeClickListener(listener: View.OnClickListener) {
    this.negativeListener = listener
}

fun setDismissListener(listener: DismissListener) {
    this.dismissLitener = listener
}

override fun onDismiss(dialog: DialogInterface) {
    super.onDismiss(dialog)
    dismissLitener?.onDismiss()
}

interface DismissListener {
    fun onDismiss()
}

companion object {

    private const val NEGATIVE_BUTTON_TEXT = "PopupCard#NEGATIVE_BUTTON_TEXT"
    private const val IMAGE_RESOURCE = "PopupCard#IMAGE_RESOURCE"
    private const val TITLE = "PopupCard#TITLE"
    private const val TITLE_RES = "PopupCard#TITLE_RES"
    private const val DESCRIPTION = "PopupCard#DESCRIPTION"
    private const val DESCRIPTION_RES = "PopupCard#DESCRIPTION_RES"
    private const val POSITIVE_BUTTON_TEXT = "PopupCard#POSITIVE_BUTTON_TEXT"
}

class Builder(
    @StringRes private val positiveText: Int
) {

    private var negativeText: Int? = null
    @DrawableRes
    private var image: Int? = null
    @StringRes
    private var titleRes: Int? = null
    private var titleText: String? = null
    @StringRes
    private var descriptionRes: Int? = null
    private var descriptionText: String? = null

    fun setTitle(@StringRes title: Int): Builder {
        this.titleRes = title
        return this
    }

    fun setTitle(title: String): Builder {
        this.titleText = title
        return this
    }

    fun setDescription(@StringRes description: Int): Builder {
        this.descriptionRes = description
        return this
    }

    fun setDescription(description: String): Builder {
        this.descriptionText = description
        return this
    }

    fun setNegativeText(@StringRes negativeText: Int): Builder {
        this.negativeText = negativeText
        return this
    }

    fun setImage(@DrawableRes image: Int): Builder {
        this.image = image
        return this
    }

    fun build(): PopupCard {
        val bundle = Bundle().apply {
            negativeText?.let {
                putInt(NEGATIVE_BUTTON_TEXT, it)
            }
            image?.let {
                putInt(IMAGE_RESOURCE, it)
            }
            titleRes?.let {
                putInt(TITLE_RES, it)
            }
            titleText?.let {
                putString(TITLE, it)
            }
            descriptionRes?.let {
                putInt(DESCRIPTION_RES, it)
            }
            descriptionText?.let {
                putString(DESCRIPTION, it)
            }
            putInt(POSITIVE_BUTTON_TEXT, positiveText)
        }
        return PopupCard().apply {
            arguments = bundle
        }
    }
}

}

In the DialogFragment the error is here dismissLitener?.onDismiss()

As you can see in all of the lines that are causing the error have safe calls (?) so I don't know why I am getting NullPointerException and I was unable to reproduce it so I cannot give more details about the issue.



from NullPointerException on dismiss DialogFragment

No comments:

Post a Comment