Android中两个不同Navigation Graph间的项目数据传递问题
问题描述
我的App里有两个独立的Navigation Graph:
- 第一个包含三个Fragment:已创建项目列表Fragment、新建项目Fragment,以及嵌套了第二个Navigation Graph的SupportFragment;
- 第二个Navigation Graph包含底部导航栏和项目详情Fragment,用户选中项目列表中的某一项时会激活这个导航图。
我用Room数据库存储项目信息,但不知道怎么把选中项目的数据传递到第二个Navigation Graph里:
- 本来想通过Navigation Graph的参数传值,但目标Fragment属于另一个导航图,不确定是否可行;
- 尝试过通过Room根据ID查询项目,但不知道代码层面怎么实现。
ProjectsDao
@Dao interface ProjectsDao { @Insert suspend fun addProjects(projects: Projects) @Delete suspend fun deleteProject(projects: Projects) @Query("DELETE FROM projects") suspend fun deleteAllProjects() @Query("SELECT * FROM projects WHERE projects.id = :id") fun findProjectById(id: Int): LiveData<Projects> @Update suspend fun updateProject(projects: Projects) @Query("SELECT * FROM projects") fun readAllProjects(): LiveData<List<Projects>> }
DataViewModel(已编辑)
class DataViewModel(application:Application): AndroidViewModel(application) { val readAllProjects: LiveData<List<Projects>> val currentProject: MutableLiveData<Projects> = MutableLiveData() private val repository: ProjectsRepository init{ val projectsDao = ProjectsDatabase.getDatabase(application).projectsDao() repository = ProjectsRepository(projectsDao) readAllProjects = repository.readAllProjects } .... fun findProjectById(id: Int):LiveData<Projects>{ return repository.findProjectById(id) } fun setCurrentItem(projects: Projects) { currentProject.value = projects //currentProject.postValue(projects) } }
MainAdapter相关代码
override fun onBindViewHolder(holder: MyViewHolder, position: Int) { val currentItem = projectsList[position] holder.binding.projectNameTxt.text = currentItem.pr_name setListeners(currentItem, holder) //holder.binding.rawLayout.setOnClickListener { // val action = MainFragmentDirections.actionMainFragmentToSupportFragment(currentItem) // holder.itemView.findNavController().navigate(action) //} //holder.binding.deleteProjectID.setOnClickListener{ // listener.deleteProject(currentItem) //} } private fun setListeners(currentItem: Projects, holder: MainAdapter.MyViewHolder) { holder.binding.rawLayout.setOnClickListener { val intent = Intent(holder.itemView.context, SupportFragment::class.java) intent.putExtra("id", currentItem.id) holder.itemView.context.startActivity(intent) //val action = MainFragmentDirections.actionMainFragmentToSupportFragment(intent) //holder.itemView.findNavController().navigate(action) } holder.binding.deleteProjectID.setOnClickListener{ listener.deleteProject(currentItem) } }
SupportFragment代码
class SupportFragment : Fragment() { private val arg by navArgs<SupportFragmentArgs>() private var _binding: FragmentSupportBinding? = null lateinit var viewModel : DataViewModel private val binding get() = _binding!! override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { _binding = FragmentSupportBinding.inflate(inflater, container, false) viewModel = ViewModelProvider(this).get(DataViewModel::class.java) return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val navHostFragment = childFragmentManager.findFragmentById(R.id.support) as NavHostFragment val navController = navHostFragment.findNavController() binding.bottomNav.setupWithNavController(navController) //Setting the current item value viewModel.setCurrentItem(arg.currentProject) }
ProjectDetailsFragment代码
class ProjectDetailsFragment : Fragment() { lateinit var viewModel: DataViewModel private var _binding: FragmentProjectDetailsBinding? = null private val binding get() = _binding!! override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { // Inflate the layout for this fragment _binding = FragmentProjectDetailsBinding.inflate(inflater, container, false) viewModel = ViewModelProvider(this).get(DataViewModel::class.java) return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val setDataObserver = Observer<Projects>{project-> binding.projectName.text = project.pr_name } //Observing currentProject viewModel.currentProject.observe(viewLifecycleOwner, setDataObserver) }
解决方案
方法一:跨导航图传递参数(Navigation原生支持)
Navigation组件本身支持跨导航图传递参数,不用额外复杂操作,步骤如下:
- 修改MainAdapter的跳转逻辑
放弃用Intent启动Fragment,改用Navigation的action跳转,直接传递项目ID:
private fun setListeners(currentItem: Projects, holder: MainAdapter.MyViewHolder) { holder.binding.rawLayout.setOnClickListener { // 生成跳转至SupportFragment的Action,传入项目ID val action = MainFragmentDirections.actionMainFragmentToSupportFragment(currentItem.id) holder.itemView.findNavController().navigate(action) } holder.binding.deleteProjectID.setOnClickListener{ listener.deleteProject(currentItem) } }
- 在导航图中配置参数
在第一个导航图中,给actionMainFragmentToSupportFragment添加参数:
<action android:id="@+id/action_mainFragment_to_supportFragment" app:destination="@id/supportFragment"> <argument android:name="projectId" app:argType="integer" /> </action>
此时SupportFragmentArgs会自动生成,不用手动修改。
- SupportFragment传递参数到子导航图
在SupportFragment中拿到传递过来的ID,再传递给子导航图的ProjectDetailsFragment:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val navHostFragment = childFragmentManager.findFragmentById(R.id.support) as NavHostFragment val navController = navHostFragment.findNavController() binding.bottomNav.setupWithNavController(navController) // 获取传递的项目ID val projectId = arg.projectId // 如果需要直接跳转至详情页,传递参数 val args = Bundle().apply { putInt("projectId", projectId) } // 若底部导航默认选中详情页,直接执行此跳转;否则可在选中详情页时传递 navController.navigate(R.id.projectDetailsFragment, args) }
- ProjectDetailsFragment接收参数并查询Room
在详情页接收参数,通过ViewModel调用Room查询:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val projectId = arguments?.getInt("projectId") ?: return viewModel.findProjectById(projectId).observe(viewLifecycleOwner) { project -> binding.projectName.text = project.pr_name } }
方法二:共享Activity级ViewModel
利用ViewModel的生命周期特性,使用Activity作为ViewModel的所有者,让SupportFragment和ProjectDetailsFragment共享同一个ViewModel实例,步骤如下:
- MainAdapter点击时设置ViewModel数据
点击项目时,直接给Activity级别的ViewModel设置选中的项目:
private fun setListeners(currentItem: Projects, holder: MainAdapter.MyViewHolder) { holder.binding.rawLayout.setOnClickListener { // 获取Activity级别的ViewModel val viewModel = ViewModelProvider(holder.itemView.context as AppCompatActivity)[DataViewModel::class.java] viewModel.setCurrentItem(currentItem) // 跳转至SupportFragment val action = MainFragmentDirections.actionMainFragmentToSupportFragment() holder.itemView.findNavController().navigate(action) } holder.binding.deleteProjectID.setOnClickListener{ listener.deleteProject(currentItem) } }
- 修改ViewModel获取方式
在SupportFragment和ProjectDetailsFragment中,都通过requireActivity()获取ViewModel,确保是同一个实例:
// SupportFragment中 viewModel = ViewModelProvider(requireActivity())[DataViewModel::class.java] // ProjectDetailsFragment中 viewModel = ViewModelProvider(requireActivity())[DataViewModel::class.java]
- ProjectDetailsFragment观察数据
此时直接观察ViewModel中的currentProject即可拿到选中的项目:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) viewModel.currentProject.observe(viewLifecycleOwner) { project -> binding.projectName.text = project.pr_name } }
内容的提问来源于stack exchange,提问作者Bitaghd
相关产品推荐
相关产品推荐

