You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

DialogFragment中onAttach保存监听器致Fragment重写方法无法调用

解决DialogFragment配置变更后监听器回调失效问题(不使用ViewModel)

配置变更(如屏幕旋转)时,系统会重建DialogFragment和宿主Fragment,原有的监听器引用指向旧的宿主实例,新实例未重新绑定导致回调无法触发。以下是几种无需ViewModel的解决办法:

方案一:使用FragmentResultListener(官方推荐)

这是官方替代传统监听器的方案,通过FragmentManager传递结果,无需手动维护监听器引用。

修改RedeemGiftCardDialog:

移除listener变量,在按钮点击事件中通过setFragmentResult传递结果:

class RedeemGiftCardDialog: DialogFragment() {

    private lateinit var binding: DialogRedeemGiftCardBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setStyle(STYLE_NORMAL, R.style.Theme_AppCompat_Light_Dialog_Alert)
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        binding = inflate(inflater, R.layout.dialog_redeem_gift_card)
        dialog?.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        binding.textFieldCode.editText?.showKeyboard()

        binding.buttonContinue.setOnClickListener {
            val text = binding.textFieldCode.editText?.text.toString()
            if (text.isBlank() || text.length < 6) {
                Toast.makeText(context, "Shënoni kodin për të vazhduar!", Toast.LENGTH_SHORT).show()
                return@setOnClickListener
            }
            isLoading(true)
            // 通过FragmentManager传递结果
            parentFragmentManager.setFragmentResult(
                "REDEEM_GIFT_CARD_KEY",
                bundleOf("REDEEM_CODE" to text)
            )
            dismiss() // 可选:根据需求关闭Dialog
        }
    }

    fun isLoading(loading: Boolean) {
        binding.buttonContinue.isEnabled = !loading
        binding.progressBar.isVisible = loading
        binding.buttonContinue.text = if (loading) "" else "Aktivizo"
        binding.buttonContinue.hideKeyboard()
    }
}

修改ProfileFragment:

在onViewCreated中注册FragmentResultListener接收结果:

@AndroidEntryPoint
class ProfileFragment : BaseFragment(), ResetBottomSheetListener {

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        // 注册结果监听
        parentFragmentManager.setFragmentResultListener(
            "REDEEM_GIFT_CARD_KEY",
            viewLifecycleOwner
        ) { _, result ->
            val code = result.getString("REDEEM_CODE") ?: return@setFragmentResultListener
            Log.d("Dialog","clicked")
            membershipViewModel.activateGift(code)
        }

        binding.redeemGiftCardLayout.setOnClickListener {
            val fm = activity?.supportFragmentManager ?: return@setOnClickListener
            val redeemDialog = RedeemGiftCardDialog()
            if (!redeemDialog.isAdded) {
                redeemDialog.show(fm, redeemDialog.tag)
            }
        }
    }
}

方案二:通过onAttach正确获取宿主Fragment作为监听器

利用DialogFragment的parentFragment获取宿主Fragment,确保配置变更后能绑定到新的宿主实例。

修改RedeemGiftCardDialog:

在onAttach中获取宿主Fragment并赋值给监听器,onDetach时清空引用避免内存泄漏:

class RedeemGiftCardDialog: DialogFragment() {

    private lateinit var binding: DialogRedeemGiftCardDialogBinding
    private var listener: RedeemGiftCardDialogListener? = null

    override fun onAttach(context: Context) {
        super.onAttach(context)
        // 获取宿主Fragment并强转为监听器
        val hostFragment = parentFragment
        if (hostFragment is RedeemGiftCardDialogListener) {
            listener = hostFragment
        } else {
            throw IllegalStateException("宿主Fragment必须实现RedeemGiftCardDialogListener接口")
        }
    }

    override fun onDetach() {
        super.onDetach()
        listener = null
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        binding.textFieldCode.editText?.showKeyboard()

        binding.buttonContinue.setOnClickListener {
            val text = binding.textFieldCode.editText?.text.toString()
            if (text.isBlank() || text.length < 6) {
                Toast.makeText(context, "Shënoni kodin për të vazhduar!", Toast.LENGTH_SHORT).show()
                return@setOnClickListener
            }
            isLoading(true)
            listener?.onRedeemClicked(text)
        }
    }

    fun isLoading(loading: Boolean) {
        binding.buttonContinue.isEnabled = !loading
        binding.progressBar.isVisible = loading
        binding.buttonContinue.text = if (loading) "" else "Aktivizo"
        binding.buttonContinue.hideKeyboard()
    }
}

修改ProfileFragment:

无需手动设置listener,直接显示Dialog即可:

@AndroidEntryPoint
class ProfileFragment : BaseFragment(), ResetBottomSheetListener, RedeemGiftCardDialogListener {

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        binding.redeemGiftCardLayout.setOnClickListener {
            val fm = activity?.supportFragmentManager ?: return@setOnClickListener
            val redeemDialog = RedeemGiftCardDialog()
            if (!redeemDialog.isAdded) {
                redeemDialog.show(fm, redeemDialog.tag)
            }
        }
    }

    override fun onRedeemClicked(code: String) {
        Log.d("Dialog","clicked")
        membershipViewModel.activateGift(code)
    }
}

方案三:使用setTargetFragment(兼容旧版本)

此方法已被标记为Deprecated,但仍可在旧项目中使用。

修改ProfileFragment:

显示Dialog时设置TargetFragment,且在配置变更后重新关联:

@AndroidEntryPoint
class ProfileFragment : BaseFragment(), ResetBottomSheetListener, RedeemGiftCardDialogListener {

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        // 配置变更后重新关联已存在的DialogFragment
        val existingDialog = parentFragmentManager.findFragmentByTag(RedeemGiftCardDialog::class.java.simpleName) as? RedeemGiftCardDialog
        existingDialog?.setTargetFragment(this, 1001)

        binding.redeemGiftCardLayout.setOnClickListener {
            val fm = activity?.supportFragmentManager ?: return@setOnClickListener
            val redeemDialog = RedeemGiftCardDialog()
            // 设置当前Fragment为TargetFragment,requestCode自定义
            redeemDialog.setTargetFragment(this, 1001)
            if (!redeemDialog.isAdded) {
                redeemDialog.show(fm, redeemDialog.tag)
            }
        }
    }

    override fun onRedeemClicked(code: String) {
        Log.d("Dialog","clicked")
        membershipViewModel.activateGift(code)
    }
}

修改RedeemGiftCardDialog:

在点击事件中通过targetFragment获取监听器:

binding.buttonContinue.setOnClickListener {
    val text = binding.textFieldCode.editText?.text.toString()
    if (text.isBlank() || text.length < 6) {
        Toast.makeText(context, "Shënoni kodin për të vazhduar!", Toast.LENGTH_SHORT).show()
        return@setOnClickListener
    }
    isLoading(true)
    // 强转TargetFragment为监听器
    (targetFragment as? RedeemGiftCardDialogListener)?.onRedeemClicked(text)
}

内容的提问来源于stack exchange,提问作者Flutura Demaj

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 17:35:54