Fragment A发起Retrofit请求,如何在Fragment B的RecyclerView展示数据?
Android开发问题:跨Fragment LiveData观察失效与数据展示方案
问题场景
在Fragment A中点击按钮触发两次Retrofit请求:先获取Token,再用Token拉取电视分类数据,调试确认请求成功且已拿到目标数据。需在Fragment B的RecyclerView中展示分类数据,但Fragment B内tvCategoryAdapter.setTvGenresList(response)未执行,LiveData观察者收不到数据更新。已通过Navigation Graph关联两个Fragment,纠结是否要将第二次请求移至Fragment B(需传递Fragment A中两个EditText的输入值)。
当前代码
Fragment A 代码
class AddStalkerFragment : Fragment(R.layout.fragment_add_stalker) { private lateinit var binding: FragmentAddStalkerBinding private lateinit var sessionManager: SessionManager private lateinit var tvCategoryAdapter: TvCategoryAdapter val viewModel: StalkerViewModel by lazy { ViewModelProvider(this)[StalkerViewModel::class.java] } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { binding = FragmentAddStalkerBinding.inflate(inflater, container, false) return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) prepareRecyclerView() sessionManager = SessionManager(requireActivity()) val stalkerUrl = binding.etServerUrl.text val macAddress = binding.etMacAddress.text val buttonSave = binding.btnSaveStalker buttonSave.setOnClickListener { viewModel.getToken( url = stalkerUrl.toString(), cookie = "mac=${macAddress};stb_lang=en;timezone=Europe/Amsterdam;", ) viewModel.getTvCategory( url = stalkerUrl.toString(), cookie = "mac=${macAddress};stb_lang=en;timezone=Europe/Amsterdam;", token = "Bearer ${sessionManager.fetchAuthToken()}" ) } viewModel.getTokenLiveData.observe(viewLifecycleOwner) { response -> if (response == null) { Toast.makeText(requireActivity(), "Token Error", Toast.LENGTH_SHORT).show() return@observe } val loginResponse = response.js.token Toast.makeText(requireActivity(), "Token Ok", Toast.LENGTH_SHORT).show() sessionManager.saveAuthToken(loginResponse) } viewModel.getTvCategoryLiveData.observe(viewLifecycleOwner) { response -> if (response == null) { Toast.makeText(requireActivity(), "Categories Error", Toast.LENGTH_SHORT).show() return@observe } Toast.makeText(requireActivity(), "Categories Ok", Toast.LENGTH_SHORT).show() tvCategoryAdapter.setTvGenresList(response.js) } } private fun prepareRecyclerView() { tvCategoryAdapter = TvCategoryAdapter() binding.rvTest.apply { layoutManager = LinearLayoutManager(context) adapter = tvCategoryAdapter } } }
Fragment B 代码
class TvCategoryFragment : Fragment(R.layout.fragment_tv_category) { private lateinit var binding: FragmentTvCategoryBinding private lateinit var tvCategoryAdapter: TvCategoryAdapter val viewModel: StalkerViewModel by lazy { ViewModelProvider(this)[StalkerViewModel::class.java] } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { binding = FragmentTvCategoryBinding.inflate(inflater, container, false) return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) prepareRecyclerView() viewModel.getTvCategoryLiveData.observe(viewLifecycleOwner) { response -> if (response == null) { Toast.makeText(requireActivity(), "Categories Error", Toast.LENGTH_SHORT).show() return@observe } Toast.makeText(requireActivity(), "Categories Ok", Toast.LENGTH_LONG).show() tvCategoryAdapter.setTvGenresList(response.js) } } private fun prepareRecyclerView() { tvCategoryAdapter = TvCategoryAdapter() binding.rvLayoutTvCategory.apply { layoutManager = LinearLayoutManager(context) adapter = tvCategoryAdapter } } }
Adapter 代码
class TvCategoryAdapter : RecyclerView.Adapter<TvCategoryAdapter.ViewHolder>() { private var genreList = ArrayList<J>() fun setTvGenresList(genreList: List<J>) { this.genreList = genreList as ArrayList<J> notifyDataSetChanged() } class ViewHolder(val binding: RvItemTvcategoryBinding) : RecyclerView.ViewHolder(binding.root) {} override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { return ViewHolder( RvItemTvcategoryBinding.inflate(LayoutInflater.from(parent.context)) ) } override fun onBindViewHolder(holder: ViewHolder, position: Int) { holder.binding.rvItemTvcategory.text = genreList[position].title } override fun getItemCount(): Int { return genreList.size } }
问题根源
- ViewModel实例不共享:两个Fragment各自通过
ViewModelProvider(this)创建独立的StalkerViewModel实例,Fragment A请求的数据不会同步到Fragment B的ViewModel,导致Fragment B的LiveData无更新。 - 请求时序问题:Fragment A中
getTvCategory与getToken同步调用,可能在Token未保存完成时就发起分类请求(虽然调试显示数据已拿到,但属于潜在风险)。
解决方案
方案1:共享ViewModel(推荐)
让两个Fragment共用同一个ViewModel实例,生命周期绑定到宿主Activity或Navigation Graph,实现数据跨Fragment同步。
修改步骤:
- 统一ViewModel初始化:两个Fragment中都用宿主Activity的ViewModelStore:
val viewModel: StalkerViewModel by lazy { ViewModelProvider(requireActivity())[StalkerViewModel::class.java] }
- 修正请求时序:在Token成功获取后再发起分类请求:
buttonSave.setOnClickListener { viewModel.getToken( url = stalkerUrl.toString(), cookie = "mac=${macAddress};stb_lang=en;timezone=Europe/Amsterdam;", ) } viewModel.getTokenLiveData.observe(viewLifecycleOwner) { response -> if (response == null) { Toast.makeText(requireActivity(), "Token Error", Toast.LENGTH_SHORT).show() return@observe } val loginResponse = response.js.token sessionManager.saveAuthToken(loginResponse) // 拿到Token后再请求分类 viewModel.getTvCategory( url = stalkerUrl.toString(), cookie = "mac=${macAddress};stb_lang=en;timezone=Europe/Amsterdam;", token = "Bearer ${loginResponse}" ) }
方案2:将分类请求移至Fragment B
若不想共享ViewModel,可把分类请求放到Fragment B,同时传递Fragment A的输入参数。
修改步骤:
- Fragment A导航传参:
viewModel.getTokenLiveData.observe(viewLifecycleOwner) { response -> if (response == null) { Toast.makeText(requireActivity(), "Token Error", Toast.LENGTH_SHORT).show() return@observe } val loginResponse = response.js.token sessionManager.saveAuthToken(loginResponse) // 导航到Fragment B并传递参数(需在Navigation Graph中定义参数) val action = AddStalkerFragmentDirections.actionAddStalkerToTvCategory( stalkerUrl = stalkerUrl.toString(), macAddress = macAddress.toString() ) findNavController().navigate(action) }
- Fragment B接收参数并请求:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) prepareRecyclerView() // 接收Navigation参数 val args = TvCategoryFragmentArgs.fromBundle(requireArguments()) val stalkerUrl = args.stalkerUrl val macAddress = args.macAddress val token = "Bearer ${SessionManager(requireActivity()).fetchAuthToken()}" // 发起分类请求 viewModel.getTvCategory( url = stalkerUrl, cookie = "mac=${macAddress};stb_lang=en;timezone=Europe/Amsterdam;", token = token ) // 观察数据更新Adapter viewModel.getTvCategoryLiveData.observe(viewLifecycleOwner) { response -> if (response == null) { Toast.makeText(requireActivity(), "Categories Error", Toast.LENGTH_SHORT).show() return@observe } tvCategoryAdapter.setTvGenresList(response.js) } }
额外优化建议
- Adapter中
setTvGenresList避免强制类型转换:
fun setTvGenresList(genreList: List<J>) { this.genreList.clear() this.genreList.addAll(genreList) notifyDataSetChanged() }
- ViewModel中对外暴露
LiveData,内部用MutableLiveData维护,防止外部直接修改数据。
内容的提问来源于stack exchange,提问作者Alex Mutschl
相关产品推荐
相关产品推荐

