【问题标题】:Kotlin: Live data does not change Fragment UI when data changesKotlin:实时数据在数据更改时不会更改 Fragment UI
【发布时间】:2022-01-11 12:57:05
【问题描述】:

我很难在 MVVM 模式上使用实时数据。该应用程序应该:

  1. 从 API 获取数据(它正确)
  2. 将该数据存储在 ViewModelLive data 对象中
  3. 然后fragment调用Observer方法填充recyclerView。

问题出现在第 3 点,它什么也没做,我找不到解决方案。

这里是相关代码。 (如有遗漏,我会尽快回复)

主要活动:


class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding
    private val viewModel: SharedViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        // Custom button to fetch data from api and log the Live Data value.
        binding.refreshFab.setOnClickListener {
            viewModel.fetchPlayerData()
            Log.d("gabs", "${viewModel.livePlayerlist.value}")
        }
    }
}

视图模型:

class SharedViewModel(app: Application): AndroidViewModel(app) {

//    val playerDao = LaRojaDB.getDatabase(app).playerDao()

    lateinit var playerList: Players
    val livePlayerlist: MutableLiveData<MutableList<Players.PlayersItem>> by lazy {
        MutableLiveData<MutableList<Players.PlayersItem>>()
    }

    fun fetchPlayerData() {
        CoroutineScope(Dispatchers.IO).launch {
            val response = MyService.getLaRojaService().getAllPlayers()
            withContext(Dispatchers.Main) {
                if (response.isSuccessful) {
                    val body = response.body()

                    if(!body.isNullOrEmpty()){
                        playerList = body
                        val playerArrayList = mutableListOf<Players.PlayersItem>()
                        playerList.forEach {
                            playerArrayList.add(it)
                        }
                        livePlayerlist.value = playerList
                    }
                }
            }
        }
    }
}

显示回收器视图的片段:(片段已经显示,我设置了一个 textView 作为标题,以确保我也是新使用片段。)

class PlayerListFragment : Fragment() {

    private var _binding: FragmentPlayerListBinding? = null
    private val binding get() = _binding!!

    private val model: SharedViewModel by viewModels()

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        _binding = FragmentPlayerListBinding.inflate(inflater, container, false)
        binding.rvPlayerList.layoutManager = LinearLayoutManager(activity)

        ----> // This is the observer that does not update the UI** <----
        model.livePlayerlist.observe( viewLifecycleOwner, {
            binding.rvPlayerList.adapter = PlayerAdapter(it)
        })

        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_player_list, container, false)
    }
}

提前谢谢大家,希望我终于可以知道是什么导致了这个问题!

【问题讨论】:

  • 实际观察者是否被调用?如果你放一个断点,它会命中吗?如果有,它是否包含您期望的数据?如果是这样,那么您可能遇到了适配器问题。如果没有,那么我们将需要更多关于这个 sharedVM 是如何设置的信息。
  • 你为什么要切换到主上下文呢?你应该留在 IO 并做 livedata.postValue(...) 而不是 livedata.value = ...
  • 我终于找到了解决方案,我按照您所说的做了,首先将 LiveData 更改为 Sealed 类,然后,由于问题仍然存在,我按照您在此 cmets 中提到的条件...事实证明,正如您所指出的,我没有将数据正确地发送到适配器,我在某个不属于的地方有代码。 (了解片段是如何工作的:我将代码放在方法 onCreateView 而不是 onViewCreated 中!)。非常感谢,对我帮助很大!
  • 祝你好运!很高兴听到它起作用了 ;)

标签: android-studio kotlin android-fragments mvvm android-livedata


【解决方案1】:

我认为您不需要切换协程上下文。如果我正在查看此代码,我预计会有一些更改:

这应该都在同一个IO 上下文中。然后你 postValue 到你的 liveData。

fun fetchPlayerData() {
   viewModelScope.launch(Dispatchers.IO) {
      val xx = api.fetch()
      ...
      _playerState.postValue(xx) //see below
   }
}

此外,最好不要公开可变状态,因此您的 ViewModel 不应该公开MutableLiveData(它不应该真的很懒惰)。但是最好将状态封装在一个密封的类中:

    //delete this
    val livePlayerlist: MutableLiveData<MutableList<Players.PlayersItem>> by lazy {
        MutableLiveData<MutableList<Players.PlayersItem>>()
    }

应该是:(名称只是伪代码,我不知道这段代码是关于什么的)

sealed class PlayerDataState {
  data class ListAvailable(data: List<Players.PlayersItem>>): PlayerDataState
  object Loading(): PlayerDataState
}

还有你的新 LiveData:

private val _playerState = MutableLiveData<PlayerDataState>()
val playerState: LiveData<PlayerDataState>() get() = _playerState

最后,从 UI 观察时,您只需...

model.playerState.observe(viewLifecycleOwner, {
   when (it) {
     is Loading -> ... 
     is ListAvailable -> binding.rvPlayerList.adapter = PlayerAdapter(it.data)
   }
}

【讨论】:

  • 感谢您的分析!我在调整代码时遇到了麻烦,因为它对我来说是新事物,但我会做我的研究,如果它有效或者我仍然有问题,我会回复你。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-02-01
  • 1970-01-01
  • 2023-03-19
  • 2020-05-18
  • 2022-01-01
  • 2012-03-18
  • 1970-01-01
相关资源
最近更新 更多