多模块架构中如何在导航图内复用Fragment?
可行解决方案:打破Fragment与固定导航流程的绑定
针对你要复用:feature_login中Fragment的需求,这里有几个落地性强的方案,直接解决导航逻辑绑定的问题:
方案1:给可复用Fragment定义专属导航回调接口
核心思路是让Fragment不处理具体导航逻辑,而是把用户操作事件抛给宿主(调用它的Activity/Fragment),由宿主决定后续跳转方向。
以你提到的ChooseRoleFragment为例:
- 在
:feature_login模块中给Fragment定义回调接口:
interface OnRoleSelectedListener { fun onRoleSelected(roleId: String) }
- 在
ChooseRoleFragment中持有这个接口实例,点击按钮时触发回调:
class ChooseRoleFragment : Fragment() { private var listener: OnRoleSelectedListener? = null override fun onAttach(context: Context) { super.onAttach(context) // 尝试从宿主Activity或父Fragment获取回调实现 listener = when { context is OnRoleSelectedListener -> context parentFragment is OnRoleSelectedListener -> parentFragment as OnRoleSelectedListener else -> throw IllegalArgumentException("宿主必须实现OnRoleSelectedListener") } } // 按钮点击事件 private fun onRoleClick(roleId: String) { listener?.onRoleSelected(roleId) } override fun onDetach() { super.onDetach() listener = null } }
- 不同宿主实现不同逻辑:
- 登录流程的
LoginActivity实现接口,跳转到原角色Fragment:class LoginActivity : AppCompatActivity(), OnRoleSelectedListener { override fun onRoleSelected(roleId: String) { findNavController().navigate(ChooseRoleFragmentDirections.toRoleDetail(roleId)) } } - 设置界面的
SettingsFragment实现接口,返回设置页面:class SettingsFragment : Fragment(), OnRoleSelectedListener { override fun onRoleSelected(roleId: String) { // 保存选中的角色,然后返回设置界面 viewModel.saveSelectedRole(roleId) findNavController().popBackStack() } }
- 登录流程的
这个方案比你之前考虑的“提取导航逻辑到Activity接口”更轻量,每个Fragment只关注自己的事件,不会给Activity增加过多全局接口。
方案2:通过ViewModel注入导航目标
如果你的项目已经用Dagger做依赖注入,可以给可复用Fragment的ViewModel注入动态导航目标,让Fragment只负责触发导航,不用关心目标是谁。
还是以ChooseRoleFragment为例:
- 定义一个通用的导航命令类:
sealed class NavCommand { data class Directions(val directions: NavDirections) : NavCommand() object PopBackStack : NavCommand() }
- 给
ChooseRoleViewModel添加导航命令的依赖:
class ChooseRoleViewModel @Inject constructor( private val navCommand: NavCommand ) : ViewModel() { fun onRoleSelected() { // 触发导航命令 _navigateEvent.postValue(navCommand) } }
- 在不同模块中通过Dagger提供不同的
NavCommand实例:- 登录模块提供原流程的导航方向:
@Provides @LoginScope fun provideChooseRoleNavCommand(): NavCommand { return NavCommand.Directions(ChooseRoleFragmentDirections.toRoleDetail()) } - 设置模块提供返回的命令:
@Provides @SettingsScope fun provideChooseRoleNavCommand(): NavCommand { return NavCommand.PopBackStack }
- 登录模块提供原流程的导航方向:
- Fragment中监听ViewModel的导航事件并执行:
class ChooseRoleFragment : Fragment() { private val viewModel: ChooseRoleViewModel by viewModels() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) viewModel.navigateEvent.observe(viewLifecycleOwner) { command -> when(command) { is NavCommand.Directions -> findNavController().navigate(command.directions) NavCommand.PopBackStack -> findNavController().popBackStack() } } } }
这个方案适合需要高度定制导航的场景,完全解耦Fragment和导航目标,复用性更强。
方案3:利用导航组件的参数传递动态决定目标
如果不想引入额外接口或依赖注入,可以通过Fragment的arguments传递导航目标信息:
- 在
ChooseRoleFragment中定义参数键:
const val ARG_NAV_TARGET = "nav_target" enum class NavTarget { LOGIN_FLOW, SETTINGS }
- 宿主启动Fragment时传入目标类型:
// 设置界面启动Fragment val fragment = ChooseRoleFragment().apply { arguments = bundleOf(ARG_NAV_TARGET to NavTarget.SETTINGS.name) }
- Fragment根据参数决定导航逻辑:
private fun onRoleClick(roleId: String) { val target = arguments?.getString(ARG_NAV_TARGET)?.let { NavTarget.valueOf(it) } when(target) { NavTarget.LOGIN_FLOW -> findNavController().navigate(ChooseRoleFragmentDirections.toRoleDetail(roleId)) NavTarget.SETTINGS -> { viewModel.saveSelectedRole(roleId) findNavController().popBackStack() } } }
这个方案最轻量化,适合简单场景,但扩展性不如前两个方案。
内容的提问来源于stack exchange,提问作者4gus71n
相关产品推荐
相关产品推荐

