#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) в зависимости от вашего текущего потока выполнения.