首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >NullPointerException on dismiss

NullPointerException on dismiss
EN

Stack Overflow用户
提问于 2021-01-05 23:08:56
回答 4查看 492关注 0票数 1

当我尝试关闭一个DialogFragment时,我在crashlitycs中得到了相当多的错误。这是我得到的错误:

代码语言:javascript
复制
Attempt to invoke virtual method 'android.os.Handler android.app.FragmentHostCallback.getHandler()' on a null object reference 

我得到的台词是这个showGenericError { activity?.onBackPressed() }

代码语言:javascript
复制
viewLifecycleOwner.observe(viewModel.showErrorAndExit, {
    showGenericError { activity?.onBackPressed() }
})

下面是初始化对话框的方法:

代码语言:javascript
复制
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()
    }
}

我得到错误的行是actionOnDismiss?.invoke()

最后,DialogFragment是这样的:

代码语言:javascript
复制
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
        }
    }
}

}

在DialogFragment中,错误在此处dismissLitener?.onDismiss()

正如您可以在导致错误的所有行中看到的那样,具有安全调用(?)所以我不知道为什么我得到了NullPointerException,我无法复制它,所以我不能给出关于这个问题的更多细节。

EN

回答 4

Stack Overflow用户

发布于 2021-01-29 22:41:55

不应该

代码语言:javascript
复制
    viewLifecycleOwner.observe(viewModel.showErrorAndExit, {
        showGenericError { activity?.onBackPressed() }
})

实际上是

代码语言:javascript
复制
    viewModel.showErrorAndExit.observe(viewLifecycleOwner, Observer {
        showGenericError { activity?.onBackPressed() }
})
票数 0
EN

Stack Overflow用户

发布于 2021-01-31 20:00:04

我在crashlitycs中遇到了很多错误

我想你不知道重现坠机的确切步骤,对吧?在DialogFragment仍显示的情况下重新创建活动后,可能会发生崩溃。例如: open对话框-> rotate screen -> dismiss对话框。

屏幕旋转后会发生什么?将创建新活动,DialogFragment将从旧活动分离并附加到新活动。但是您的DialogFragment在其dismissListener回调中保留了对旧活动的引用。因此,您尝试在生命周期阶段被“销毁”的活动上调用.onBackPressed()。这在Android框架中是被禁止的,这可能是你使用NullPointerException的原因。

那么,你能用它做什么呢?

我在这里看到了三种不同的解决方案:

  1. 从整洁代码的角度来看,最好的选择是不在DialogFragment中保留对活动的引用。这是一种糟糕的做法,因为即使没有崩溃,也可能导致内存泄漏。考虑使用MVVM架构模式:您可以保留对ViewModel的引用,因为它不是重新创建的,它总是相同的实例。所以在你的回调中,你可以调用像viewModel.closeScreen()这样的东西,ViewModel会找到当前的activity并对其调用.onBackPressed()
  2. 在这种情况下最好的选择可能是在Activity重新创建时找到你的DialogFragment,并更新它的回调。例如,

代码语言:javascript
复制
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    val dialog = childFragmentManager.findFragmentByTag(TAG)
    if (dialog != null) {
        dialog.setOnDismissListener {
            activity?.onBackPressed()
        }
    }
    ...
}

  1. ,最后,我不推荐做的事情(但在大多数情况下可能会修复你的崩溃)是禁止在屏幕旋转时重新创建你的活动。只需在AndroidManifest.xml中向活动的属性添加android:configChanges="orientation"即可。但不要忘记,活动可以重新创建的原因有很多,屏幕旋转只是其中之一!
票数 0
EN

Stack Overflow用户

发布于 2021-02-02 04:39:49

一种快速的解决方法是使用接口/契约,而不是直接访问托管活动,这在SOLID世界中是被禁止的操作。因此,不是:

代码语言:javascript
复制
viewLifecycleOwner.observe(viewModel.showErrorAndExit, {
showGenericError { activity?.onBackPressed() }})

使用

代码语言:javascript
复制
viewLifecycleOwner.observe(viewModel.showErrorAndExit, {
showGenericError { loosedCopuledActivity?.onBackPressTriggered() }})

然后在父活动中实现接口。

代码语言:javascript
复制
class SomeHostActivity: AppCompatActivity(), OnBackPressCallback{}

然后在某个地方定义你的界面:

代码语言:javascript
复制
interface OnBackPressCallback{
    fun onBackPressTriggered() 
}

此外,您还需要在对话框中的某个位置定义您的loosedCoupledActivity,因此我们可以这样做:

代码语言:javascript
复制
fun onActivityCreate(bluhbluh: BluhBluh){
    super.onActivityCreate(bluhbluh)
    if(requireActivity() is OnBackPressCallback){
        loosedCoupledActivity = requireActivity()
}
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/65581485

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档