我已经困在这上面几个小时了。非常尊重能帮我解决这个问题的人。
每当我更改我的MutableLiveData时,我的视图都不会更新。
在我的锻炼应用程序中,我有一个锻炼列表:MutableLiveData
然而,由于我的数据库绑定xml实际上并没有引用MutableLiveData
选择器活动:
class SelectorActivity : AppCompatActivity() {
private lateinit var mExerciseSelectAdapter: ExerciseSelectAdapter
private lateinit var mViewModel: SelectorViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_selector)
mViewModel = ViewModelProviders.of(this).get(SelectorViewModel::class.java)
mExerciseSelectAdapter = ExerciseSelectAdapter(this, mViewModel)
setupRecyclerView()
loadExerciseList(intent.hasExtra("LOAD_FROM_DB"), resources)
}
private fun setupRecyclerView(){
rv_select_exers.layoutManager = LinearLayoutManager(this)
rv_select_exers.setHasFixedSize(true)
rv_select_exers.adapter = mExerciseSelectAdapter
}
private fun loadExerciseList(loadFromDB: Boolean, resources: Resources) {
doAsync {
val myDBExercises = loadExersFromDB()
mViewModel.populateExerciseListFromDB(myDBExercises)
mExerciseSelectAdapter.notifyDataSetChanged()
}
}
}
}
选择视图模型:
class SelectorViewModel : ViewModel() {
private val mExerciseList = MutableLiveData<ArrayList<ExerciseEntity>>()
val exerciseList : LiveData<ArrayList<ExerciseEntity>>
get() = mExerciseList
init {
mExerciseList.value = ArrayList()
}
fun populateExerciseListFromDB(myDBExercises: List<ExerciseEntity>) {
// .... Load our exer list from DB ... //
mExerciseList.value?.addAll(myDBExercises)
// Use the "setValue" method to notify observers of change
mExerciseList.value = mExerciseList.value
}
private fun updateRepsInExercise(exercise: ExerciseEntity, reps1: Int, reps2: Int, reps3: Int) {
exercise.set1Reps = reps1
exercise.set2Reps = reps2
exercise.set3Reps = reps3
// Force our livedata to detect a change
// TODO: not doing anything after increment set, our view is not updating
mExerciseList.value = mExerciseList.value
}
fun incrementSet(exercise: ExerciseEntity, smallIncrement: Boolean) {
// ... Compute the new reps ... //
updateRepsInExercise(exercise, numReps1, numReps2, numReps3)
}
}
练习选择适配器:
类练习选择适配器(私有val mLifaycleOwner: LifaycleOwner,私有val mViewModel:SelectorViewModel):RecycliView.Adapter(){私有lateinit var mWorkiseList:ArrayList
init {
// Use this to avoid blinking of list when data is changed
setHasStableIds(true)
// Get a reference to our viewmodel's exercise list
if (mViewModel.exerciseList.value != null ) mExerciseList = mViewModel.exerciseList.value!!
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ExerciseSelectViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val dataBinding: ExerciseSelectBinding = DataBindingUtil.inflate(layoutInflater, R.layout.exercise_select, parent, false);
dataBinding.setLifecycleOwner(mLifecycleOwner)
return ExerciseSelectViewHolder(dataBinding, mLifecycleOwner, mViewModel)
}
override fun onBindViewHolder(holder: ExerciseSelectViewHolder, index: Int) {
holder.bindExerciseSelectView(mExerciseList[index])
}
override fun getItemCount(): Int {
return mExerciseList.size
}
// Use this to avoid blinking of list when data is changed
override fun getItemId(position: Int): Long {
return mExerciseList[position].exerciseNum.toLong()
}
}
练习选择视图持有者:
class ExerciseSelectViewHolder(private val mDataBinding: ExerciseSelectBinding,
private val mLifecycleOwner: LifecycleOwner,
private val mViewModel: SelectorViewModel):
RecyclerView.ViewHolder(mDataBinding.root){
init {
// Used to enable proper observation of LiveData
mDataBinding.setLifecycleOwner(mLifecycleOwner)
}
fun bindExerciseSelectView(exerciseEntity: ExerciseEntity) {
mDataBinding.exerciseEntity = exerciseEntity
mDataBinding.viewModel = mViewModel
mDataBinding.spinnerAdapter = ArrayAdapter(mLifecycleOwner as Context,
R.layout.simple_spinner_dropdown_item,
exerciseEntity.allProgressions)
// Forces the bindings to run immediately
mDataBinding.executePendingBindings()
}
}
activity_selector. xml:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.epsilon.startbodyweight.selectorActivity.SelectorActivity">
<android.support.v7.widget.RecyclerView
android:id="@+id/rv_select_exers"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
exercise_select. xml:
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<import type="android.view.View"/>
<variable
name="exerciseEntity"
type="com.epsilon.startbodyweight.data.ExerciseEntity"/>
<variable
name="viewModel"
type="com.epsilon.startbodyweight.selectorActivity.SelectorViewModel"/>
<variable
name="spinnerAdapter"
type="android.widget.ArrayAdapter" />
</data>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<Spinner
android:id="@+id/sp_sel_exer"
//... Styling stuff
android:spinnerMode="dropdown"
android:adapter="@{spinnerAdapter}"
android:selection="@{exerciseEntity.progressionNumber}"
android:onItemSelected="@{(parent,view,position,id) -> viewModel.onItemSelectedSpinner(parent,position,exerciseEntity)}"/>
<Button
android:id="@+id/b_sel_increase_reps_small"
android:layout_width="wrap_content"
//... Styling stuff
android:onClick="@{() -> viewModel.incrementSet(exerciseEntity, true)}"
android:text="↑" />
<TextView
android:id="@+id/tv_sel_rep_1"
// ... Styling stuff
android:text="@{String.valueOf(exerciseEntity.set1Reps)}"
android:visibility="@{!exerciseEntity.isTimedExercise ? View.VISIBLE : View.GONE}"/>
<TextView
android:id="@+id/tv_sel_rep_2"
//... Styling stuff
android:text="@{String.valueOf(viewModel.exerciseList)}"
android:visibility="@{!exerciseEntity.isTimedExercise ? View.VISIBLE : View.GONE}"/>
<TextView
android:id="@+id/tv_sel_rep_3"
//... Styling stuff
android:text="@{String.valueOf(exerciseEntity.set3Reps)}"
android:visibility="@{!exerciseEntity.isTimedExercise ? View.VISIBLE : View.GONE}"/>
</LinearLayout>
</layout>
我是否需要使数据绑定观察MutableLiveData的更改
编辑:我找到了一个解决方案,很简单,但不理想。仍在寻找建议。
我没有直接将这个练习实体绑定到我的数据绑定XML布局,而是绑定了元素在列表中的位置。然后在我的xml中,我使用列表和这个索引访问元素。
在exercise_select. xml中,我已经替换了
android:text="@{String.valueOf(exerciseEntity.set2Reps)}"
由
android:text="@{String.valueOf(viewModel.exerciseList[index].set2Reps)}"
这似乎说服了数据绑定最终观察到对ArrayList的修改。
我不满意,有没有更“最佳实践”的方法来做到这一点?
我不知道你是否仍然有这个问题,但是我知道数据库绑定本身不能自己更新适配器中的练习列表,因为数据库绑定不能(也不应该)访问布局xml中的适配器实例。
您应该观察活动中练习列表的变化,如果列表发生变化,请将其提交给适配器,以便它可以更新回收器视图:
class SelectorActivity : AppCompatActivity() {
// ...
private fun setupRecyclerView(){
rv_select_exers.layoutManager = LinearLayoutManager(this)
rv_select_exers.setHasFixedSize(true)
rv_select_exers.adapter = mExerciseSelectAdapter
// observe changes to the list-liveData in the viewmodel
mViewModel.exerciseList.observe(this) { exercises ->
mExerciseSelectAdapter.submitList(exercises)
}
// ...
private fun loadExerciseList(loadFromDB: Boolean, resources: Resources) {
// I would advise to switch to kotlin-coroutines and move
// db-loading to the viewModel into an viewModelScope-block
// so the loading is cancelled when the app/activity is left by the user
doAsync {
val myDBExercises = loadExersFromDB()
// I think doAsync (from Anko?) does not do its work on the
// UI-thread, so switch to UI-thread before populating
mViewModel.populateExerciseListFromDB(myDBExercises)
// this notifyDataSetChanged is not needed anymore
// because of the observe-call above
// mExerciseSelectAdapter.notifyDataSetChanged()
}
}
}
}
编辑:我刚刚注意到您扩展了RecyclerView. Adapter
,而不是ListAdapter
(这将是最佳实践,但您还需要在ListAdapter
中使用DiffCallback
),因此您没有Adapter.submitList
,因此使用新的set-method更新适配器中的列表实例,然后像您已经做的那样调用Adapter.通知fyDataSetChanged()
。
还有什么可能是更新问题的一部分:你记得在初始化期间适配器中的练习列表的快照,据我所知,从适配器的角度来看,它从来没有更新过(我认为当你更新livedata-value时,它可能是也可能不是对列表的相同引用,因为livedata可能会重新初始化其内部值存储):
init {
// ...
// Get a reference to our viewmodel's exercise list
if (mViewModel.exerciseList.value != null ) mExerciseList = mViewModel.exerciseList.value!!
}
这击败了“从db加载列表并更新列表”代码。保持简单,只让观察-lambda更改列表并直接在适配器中访问mViewModel. workiseList
,而不是只访问它的初始快照。
关于填充方法,您可以在从DB加载后简单地将列表设置为livedata:
fun populateExerciseListFromDB(myDBExercises: List<ExerciseEntity>) {
// why load again? you load the list in loadExersFromDB and pass it in
// .... Load our exer list from DB ... //
// simply set the new list to the mutable livedata
// Use the "setValue" method to notify observers of change
mExerciseList.value = myDBExercises
}