如何从Fragment打开DialogFragment并实现回调数据传递?
问题
我是一名大学生,此前在示例应用中通过回调实现了从MainActivity打开DialogFragment并返回数据,但现在尝试从Fragment打开该DialogFragment时,回调仍指向MainActivity而非发起的Fragment。请问能否通过回调实现需求?若不行,改用Navigation参数需要做哪些修改?我已附上在Activity中可行的代码示例,此前查资料了解到可通过Navigation参数实现,但对此不太理解。
现有代码
class MenuFragment : Fragment(), PlanAdapter.PlanItemClickListener, PlanItemDialogFragment.NewPlanItemDialogListener { private lateinit var binding : FragmentMenuBinding private lateinit var database: PlanListDatabase private lateinit var adapter: PlanAdapter override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { binding = FragmentMenuBinding.inflate(inflater, container, false) return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) database = PlanListDatabase.getDatabase(requireActivity().applicationContext) binding.fab.setOnClickListener{ PlanItemDialogFragment().show( childFragmentManager, PlanItemDialogFragment.TAG ) } } } class PlanItemDialogFragment : DialogFragment() { interface NewPlanItemDialogListener { fun onPlanItemCreated(newItem: PlanItem) } private lateinit var listener: NewPlanItemDialogListener private lateinit var binding: DialogNewItemBinding override fun onAttach(context: Context) { super.onAttach(context) listener = context as? NewPlanItemDialogListener ?: throw RuntimeException("Activity must implement the NewPlanItemDialogListener interface!") } override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { binding = DialogNewItemBinding.inflate(LayoutInflater.from(context)) return AlertDialog.Builder(requireContext()) .setTitle(hu.bme.aut.android.mobil_hf.R.string.new_plan_item) .setView(binding.root) .setPositiveButton(hu.bme.aut.android.mobil_hf.R.string.button_ok) { dialogInterface, i -> if (isValid()) { listener.onPlanItemCreated(getPlanItem()) } } .setNegativeButton(hu.bme.aut.android.mobil_hf.R.string.button_cancel, null) .create() } }
解决方案
一、通过回调实现需求的修改方法
可以用回调实现,问题出在PlanItemDialogFragment的onAttach方法里直接把context(此时是Activity)强转为监听器。只需让DialogFragment直接接收发起的Fragment作为监听器即可,修改步骤如下:
1. 修改PlanItemDialogFragment
class PlanItemDialogFragment : DialogFragment() { interface NewPlanItemDialogListener { fun onPlanItemCreated(newItem: PlanItem) } private var listener: NewPlanItemDialogListener? = null // 新增静态方法传入监听器 companion object { const val TAG = "PlanItemDialogFragment" fun newInstance(listener: NewPlanItemDialogListener): PlanItemDialogFragment { val fragment = PlanItemDialogFragment() fragment.listener = listener return fragment } } override fun onAttach(context: Context) { super.onAttach(context) // 若未传入监听器,再尝试从Activity获取(兼容原来的逻辑) if (listener == null) { listener = context as? NewPlanItemDialogListener ?: throw RuntimeException("Activity or Fragment must implement NewPlanItemDialogListener!") } } override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { binding = DialogNewItemBinding.inflate(LayoutInflater.from(context)) return AlertDialog.Builder(requireContext()) .setTitle(hu.bme.aut.android.mobil_hf.R.string.new_plan_item) .setView(binding.root) .setPositiveButton(hu.bme.aut.android.mobil_hf.R.string.button_ok) { _, _ -> if (isValid()) { listener?.onPlanItemCreated(getPlanItem()) } } .setNegativeButton(hu.bme.aut.android.mobil_hf.R.string.button_cancel, null) .create() } }
2. 修改MenuFragment中打开Dialog的代码
binding.fab.setOnClickListener{ val dialog = PlanItemDialogFragment.newInstance(this) dialog.show(childFragmentManager, PlanItemDialogFragment.TAG) }
这样监听器就会绑定到发起的MenuFragment,而非Activity。
二、改用Navigation参数的修改方法
如果不想用回调,Navigation组件的SavedStateHandle可以更优雅地实现数据返回,步骤如下:
1. 确保PlanItem实现Parcelable接口
Navigation参数要求数据可序列化,给PlanItem添加Parcelable实现:
@Parcelize data class PlanItem( // 你的实体字段,比如id、title等 ) : Parcelable
2. 在导航图(nav_graph.xml)中添加DialogFragment
将PlanItemDialogFragment添加为dialog类型的目的地:
<dialog android:id="@+id/planItemDialogFragment" android:name="你的包名.PlanItemDialogFragment" android:label="PlanItemDialogFragment"> </dialog>
3. 修改MenuFragment:导航并监听返回结果
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) database = PlanListDatabase.getDatabase(requireActivity().applicationContext) binding.fab.setOnClickListener{ // 导航到DialogFragment findNavController().navigate(R.id.planItemDialogFragment) } // 监听Dialog返回的数据 findNavController().currentBackStackEntry?.savedStateHandle?.getLiveData<PlanItem>("newPlanItem") ?.observe(viewLifecycleOwner) { newItem -> // 处理返回的新数据,比如插入数据库、更新列表 // 处理后移除数据,避免重复触发 findNavController().currentBackStackEntry?.savedStateHandle?.remove<PlanItem>("newPlanItem") } }
4. 修改PlanItemDialogFragment:返回数据
移除原有的监听器接口,修改确定按钮逻辑:
class PlanItemDialogFragment : DialogFragment() { private lateinit var binding: DialogNewItemBinding override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { binding = DialogNewItemBinding.inflate(LayoutInflater.from(context)) return AlertDialog.Builder(requireContext()) .setTitle(hu.bme.aut.android.mobil_hf.R.string.new_plan_item) .setView(binding.root) .setPositiveButton(hu.bme.aut.android.mobil_hf.R.string.button_ok) { _, _ -> if (isValid()) { val newItem = getPlanItem() // 通过SavedStateHandle返回数据给上一个页面 findNavController().previousBackStackEntry?.savedStateHandle?.set("newPlanItem", newItem) dismiss() } } .setNegativeButton(hu.bme.aut.android.mobil_hf.R.string.button_cancel) { _, _ -> dismiss() } .create() } }
这样就完成了用Navigation参数实现数据返回的改造,无需依赖回调接口。
内容的提问来源于stack exchange,提问作者Naaaah
相关产品推荐
相关产品推荐

