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
相关产品推荐
相关产品推荐

