Как я могу сбросить LiveData при изменении LiveData двусторонней привязки данных в Android Studio?

#android #kotlin #android-databinding #android-livedata

#Android #котлин #android-привязка к данным #android-livedata #kotlin #android-привязка данных

Вопрос:

EditText использует двустороннюю привязку данных, а кнопка использует одностороннюю привязку данных в layout_detail.xml

Я надеюсь, что кнопка будет включена при aDetailViewModel.aMVoice.name изменении.

Когда изменяется содержимое EditText, значение aMVoice in DetailViewModel также будет изменено, я думаю, что могу сбросить значение isChanged , но я не знаю, как это сделать? Не могли бы вы мне рассказать?

layout_detail.xml

 <layout 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">

    <data>
        <import type="android.view.View" />
        <variable name="aDetailViewModel"
            type="info.dodata.voicerecorder.viewcontrol.DetailViewModel" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <EditText
            android:id="@ id/eTName"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:inputType="textPersonName"
            android:text="@={aDetailViewModel.aMVoice.name}" />

        <Button
            android:id="@ id/btnSave"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:enabled="@{aDetailViewModel.isChanged}"
            android:text="Save" />

    </LinearLayout>

</layout>
  

Код

 class DetailViewModel(private val mDBVoiceRepository: DBVoiceRepository, private val voiceId:Int) : ViewModel() {

   val aMVoice=mDBVoiceRepository.getVoiceById(voiceId)     //I hope to reset isChanged when aMVoice is changed

   val isChanged: LiveData<Boolean> = MutableLiveData<Boolean>(false)    
}
  

class DBVoiceRepository private constructor(private val mDBVoiceDao: DBVoiceDao){
    fun getVoiceById(id:Int)=mDBVoiceDao.getVoiceById(id)
}

@Dao
interface DBVoiceDao{      
   @Query("SELECT * FROM voice_table where id=:id")
   fun getVoiceById(id:Int):LiveData<MVoice>
}

@Entity(tableName = "voice_table", indices = [Index("createdDate")])
data class MVoice(
    @PrimaryKey (autoGenerate = true) @ColumnInfo(name = "id") var id: Int = 0,
    var name:          String = "",
    var path:          String = "",  
)
  

Ответ №1:

Поскольку вы хотите выяснить изменения значений для нескольких атрибутов, мы должны разделить каждый из них определенным образом LiveData с помощью map преобразований. Эти новые LiveData значения играют роль двусторонней привязки.

Наконец, для интеграции изменений лучший способ — использовать MediatorLiveData , который запускается при каждом изменении, а затем проверять значения. Итак, достаточно проверить равенство начального значения из DB с полученными значениями из представлений.

 class DetailViewModel(...) {

    private val aMVoice = mDBVoiceRepository.getVoiceById(voiceId)

    val voiceName = aMVoice.map { it.name } as MutableLiveData<String>
    val voicePath = aMVoice.map { it.path } as MutableLiveData<String>
    // ... (similar for more attributes)

    val isChanged = MediatorLiveData<Boolean>().apply {
        addSource(voiceName) { postValue(currentMVoice != aMVoice.value) }
        addSource(voicePath) { postValue(currentMVoice != aMVoice.value) }
        // ... (similar for more attributes)
    }

    // it collects current values delivered from views as a `MVoice` object.
    private val currentMVoice: MVoice?
        get() = aMVoice.value?.copy(
            name = voiceName.value ?: "",
            path = voicePath.value ?: "",
            // ... (similar for more attributes)
        )
}
  

Затем используйте voiceName , voicePath , etc в макете:

 <EditText
    android:id="@ id/eTName"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:inputType="textPersonName"
    android:text="@={aDetailViewModel.voiceName}" />

<EditText
    android:id="@ id/eTPath"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:inputType="text"
    android:text="@={aDetailViewModel.voicePath}" />

// ...
  

Чтобы использовать map преобразование, не забудьте добавить lifecycle-livedata-ktx зависимость в build.gradle , если вы этого не сделали раньше.

 implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0'
  

Комментарии:

1. Спасибо! но ваш код не работает. val aMVoice = mDBVoiceRepository.getVoiceById(voiceId).map { isChanged.postValue(true); it } не будет запущен при изменении содержимого aMVoice

2. Спасибо! но я думаю, что, возможно, есть другие способы получше. Вы знаете, я должен проверить все поля, чтобы определить, изменилось ли оно, это сложная работа, если MVoice включает имя, путь, дату, описание, isFavorited и т.д.

3. Добро пожаловать! Я снова обновил, чтобы соответствовать требованию проверки дополнительных атрибутов 🙂

Ответ №2:

Попробуйте вот так

 class DetailViewModel(private val mDBVoiceRepository: DBVoiceRepository, private val voiceId: Int) :
    ViewModel() {

    val aMVoice =
        mDBVoiceRepository.getVoiceById(voiceId)     //I hope to reset isChanged when aMVoice is changed

    val isChanged: MutableLiveData<Boolean> = MutableLiveData<Boolean>(false)

    fun listenChanges(owner: LifecycleOwner) {
        aMVoice.observe(owner) {
            isChanged.value?.apply {
                isChanged.value = !this
            }
        }
    }
}
  

и вызвать прослушиватель оттуда, где у вас есть владелец жизненного цикла — например, из фрагмента

 class Fragment1 : Fragment() {

    lateinit var viewModel: DetailViewModel

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        viewModel.listenChanges(viewLifecycleOwner)
    }

}
  

Комментарии:

1. Спасибо, но я не думаю, что ваш код может выполняться, я не могу понять, что isChanged.value = !this имеется в виду

2. Попробуйте — возможно, потребуется некоторая настройка. Если вы не понимаете эту часть кода, это означает, что вы на самом деле не знаете Kotlin и его функции области видимости. Также вам может потребоваться использовать IsChanged.postValue(!this) в зависимости от вашего текущего потока выполнения.