You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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
    }
}

问题根源

  1. ViewModel实例不共享:两个Fragment各自通过ViewModelProvider(this)创建独立的StalkerViewModel实例,Fragment A请求的数据不会同步到Fragment B的ViewModel,导致Fragment B的LiveData无更新。
  2. 请求时序问题:Fragment A中getTvCategory与getToken同步调用,可能在Token未保存完成时就发起分类请求(虽然调试显示数据已拿到,但属于潜在风险)。

解决方案

方案1:共享ViewModel(推荐)

让两个Fragment共用同一个ViewModel实例,生命周期绑定到宿主Activity或Navigation Graph,实现数据跨Fragment同步。

修改步骤:

  1. 统一ViewModel初始化:两个Fragment中都用宿主Activity的ViewModelStore:
val viewModel: StalkerViewModel by lazy {
    ViewModelProvider(requireActivity())[StalkerViewModel::class.java]
}
  1. 修正请求时序:在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的输入参数。

修改步骤:

  1. 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)
}
  1. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 10:01:16