Kotlin: Live data does not change Fragment UI 当数据改变时
Kotlin: Live data does not change Fragment UI when data changes
我很难在 MVVM 模式上使用实时数据。该应用程序应该:
- 从 API 获取数据(正确)
- 将该数据存储在来自 ViewModel
的 实时数据 object 中
- 然后fragment调用Observer方法填充recyclerView
问题出在第3点,它什么也没做,我找不到解决方案。
这是相关代码。 (如有遗漏,我会尽快回复)
主要Activity:
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)
}
}
提前谢谢大家,希望我能最终了解导致问题的原因!
我认为您不需要切换协程上下文。如果我正在审查这段代码,我希望有一些变化:
这应该都在相同的 IO
上下文中。然后你 postValue
到你的 liveData。
fun fetchPlayerData() {
viewModelScope.launch(Dispatchers.IO) {
val xx = api.fetch()
...
_playerState.postValue(xx) //see below
}
}
此外,最好不要公开可变状态,因此您的 ViewModel 不应公开 MutableLiveData
(这不应该真的很懒惰)。但是最好把状态封装在一个密封的class:
//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)
}
}
我很难在 MVVM 模式上使用实时数据。该应用程序应该:
- 从 API 获取数据(正确)
- 将该数据存储在来自 ViewModel 的 实时数据 object 中
- 然后fragment调用Observer方法填充recyclerView
问题出在第3点,它什么也没做,我找不到解决方案。
这是相关代码。 (如有遗漏,我会尽快回复)
主要Activity:
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)
}
}
提前谢谢大家,希望我能最终了解导致问题的原因!
我认为您不需要切换协程上下文。如果我正在审查这段代码,我希望有一些变化:
这应该都在相同的 IO
上下文中。然后你 postValue
到你的 liveData。
fun fetchPlayerData() {
viewModelScope.launch(Dispatchers.IO) {
val xx = api.fetch()
...
_playerState.postValue(xx) //see below
}
}
此外,最好不要公开可变状态,因此您的 ViewModel 不应公开 MutableLiveData
(这不应该真的很懒惰)。但是最好把状态封装在一个密封的class:
//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)
}
}