获取数据绑定以观察实时数据的更改以供回收者查看

Lan*_*aru 5 data-binding android android-recyclerview android-livedata android-jetpack

我已经坚持了几个小时。尊敬任何可以帮助我解决这个问题的人。

每当我更改MutableLiveData时,我的视图就不会更新。

在我的锻炼应用程序中,我有一个锻炼列表:MutableLiveData>,该视图使用回收者视图显示。回收者视图持有者正在使用数据绑定来显示此ArrayList中的各个练习。每当我更新整个列表(使用setValue)时,我都希望视图能够更新。

但是,由于我的数据绑定xml实际上并未引用MutableLiveData>(),因此仅引用了列表中的ExerciseEntity元素,因此我认为它没有观察到任何更改。因此,它没有更新。我不知道如何使其正确更新。

SelectorActivity:

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()
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

}

SelectorViewModel:

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)
    }
}
Run Code Online (Sandbox Code Playgroud)

ExerciseSelectAdapter:

class ExerciseSelectAdapter(private val mLifecycleOwner:LifecycleOwner,private val mViewModel:SelectorViewModel):RecyclerView.Adapter(){private lateinit var mExerciseList: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()
}
Run Code Online (Sandbox Code Playgroud)

}

ExerciseSelectViewHolder:

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()
    }
}
Run Code Online (Sandbox Code Playgroud)

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>
Run Code Online (Sandbox Code Playgroud)

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>
Run Code Online (Sandbox Code Playgroud)

当在布局XML中根本不使用此列表时,是否需要使数据绑定观察MutableLiveData>的更改?我想将ArrayList的每个成员都设为另一个可变的实时数据,例如:MutableLiveData >>,但这似乎相当复杂...

编辑:我找到了一个解决方案,这很简单,但并不理想。仍在寻找建议。

我没有将executionEntity直接绑定到我的数据绑定XML布局,而是绑定了元素在列表中的位置。然后在我的xml中,使用列表和此索引访问元素。

在exercise_select.xml中,我替换了

android:text="@{String.valueOf(exerciseEntity.set2Reps)}"
Run Code Online (Sandbox Code Playgroud)

通过

android:text="@{String.valueOf(viewModel.exerciseList[index].set2Reps)}"
Run Code Online (Sandbox Code Playgroud)

这似乎可以说服数据绑定最终观察到对ArrayList的修改。

我不满意,是否还有其他“最佳做法”来做到这一点?

arn*_*ans 0

我不知道您是否仍然遇到这个问题,但我知道数据绑定本身无法自行更新适配器中的练习列表,因为数据绑定不能(也不应该)访问适配器实例布局-xml。

您应该观察活动中练习列表的变化,如果列表发生变化,则将其提交给适配器,以便它可以更新recyclerview:

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()
        }
    }
}
}
Run Code Online (Sandbox Code Playgroud)

编辑:我只是注意到你扩展了RecyclerView.Adapter,而不是ListAdapter(这将是最佳实践,但是你还需要一个DiffCallbackListAdapter,所以你没有adapter.submitList,所以用新的设置方法更新适配器中的列表实例并然后adapter.notifyDataSetChanged()像你已经做的那样打电话。

另外,可能是更新问题的一部分:您在初始化过程中记得一次适配器中练习列表的快照,并且据我所知,从适配器的角度来看,它从未更新过(我认为它可能或可能)当您更新 livedata-value 时,不再是对列表的相同引用,因为 livedata 可能会重新初始化其内部值存储):

init {
    // ...

    // Get a reference to our viewmodel's exercise list
    if (mViewModel.exerciseList.value != null ) mExerciseList = mViewModel.exerciseList.value!!
}
Run Code Online (Sandbox Code Playgroud)

这击败了“从数据库加载列表并更新列表”代码。保持简单,只让observe-lambda更改列表并mViewModel.exerciseList直接在适配器中访问,而不是仅访问它的初始快照。

关于填充方法,您可以简单地将列表设置为从数据库加载后的实时数据:

    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
    }
Run Code Online (Sandbox Code Playgroud)