#android #android-livedata
#Android #android-livedata
Вопрос:
savedRecordFileName
это переменная LivaData<String>
, я надеюсь получить значение savedRecordFileName
сразу в коде A.
Вы знаете, что переменная LiveData является ленивой, возможно, значение savedRecordFileName
равно null в binding.btnStop.setOnClickListener { }
, поэтому код в binding.btnStop.setOnClickListener { }
не будет запущен, когда значение savedRecordFileName
равно null .
Я надеюсь, что код в binding.btnStop.setOnClickListener { }
всегда может быть запущен, как я могу это сделать?
Кстати, я думаю, что код B не подходит, потому что значение savedRecordFileName
, возможно, изменено другой функцией.
Код B
binding.btnStop.setOnClickListener {
mHomeViewModel.savedRecordFileName.observe(viewLifecycleOwner){
val aMVoice = getDefaultMVoice(mContext,it)
mHomeViewModel.add(aMVoice)
}
}
Код A
class FragmentHome : Fragment() {
private val mHomeViewModel by lazy {
getViewModel {
HomeViewModel(mActivity.application, provideRepository(mContext))
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
...
binding.btnStop.setOnClickListener {
mHomeViewModel.savedRecordFileName.value?.let{
val aMVoice = getDefaultMVoice(mContext,it)
mHomeViewModel.add(aMVoice)
}
}
...
return binding.root
}
}
class HomeViewModel(val mApplication: Application, private val mDBVoiceRepository: DBVoiceRepository) : AndroidViewModel(mApplication) {
val savedRecordFileName: LiveData<String> = mDBVoiceRepository.getTotalOfVoice().map {
mApplication.getString(R.string.defaultName, (it 1).toString())
}
}
class DBVoiceRepository private constructor(private val mDBVoiceDao: DBVoiceDao){
fun getTotalOfVoice() = mDBVoiceDao.getTotalOfVoice()
}
@Dao
interface DBVoiceDao{
@Query("SELECT count(id) FROM voice_table")
fun getTotalOfVoice(): LiveData<Long>
}
Добавить содержимое
Чудакулли: Спасибо!
Я думаю, что ваш способ «переместить все это в класс viewmodel» хорош!
Я думаю, все будет в порядке, даже если filename
в вашем коде C есть livedata, верно?
Код C
viewModelScope.launch(Dispatchers.IO) {
filename = dao.getFilename() // without livedata. I think it will be OK even if the filename is livedata
voice = getDefaultVoice(...) // also do this in background
add(voice)
result.postValue(true)
}
Комментарии:
1. какой смысл использовать ViewModel? Удалите viewmodel, и вы сразу получите значение, я имею в виду, что вы должны просто изменить возвращаемый тип
getTotalOfVoice()
в DAO на бытьLong
вместоLiveData<Long>
2. Спасибо! Обычно Room использует фоновый поток для асинхронного запроса записи. Я должен получить результат запроса общих записей, используя LiveData или функцию приостановки.
Ответ №1:
Как вы сказали, вы должны выполнять запросы к помещению в фоновом режиме, чтобы избежать блокировки потока пользовательского интерфейса. Но вы не должны останавливаться на достигнутом. Выполняйте как можно больше работы в фоновом потоке. В вашем случае при нажатии кнопки запустите фоновый поток, который выполняет всю работу: получает имя из базы данных (с помощью прямого запроса, что возможно сейчас, когда вы работаете в фоновом режиме), получает голос по умолчанию и добавляет его к тому, к чему вы его добавляете.
Вы также должны переместить все это в класс viewmodel .
btnStop.setOnClickListener {
viewmodel.stop()
}
В viewmodel что-то вроде этого (java / псевдокод, подобный):
void stop() {
runinbackgroundthread {
filename = dao.getFilename() // without livedata
voice = getDefaultVoice(...) // also do this in background
add(voice)
// If you want the action to have a reault, observable by ui
// use a MutableLiveData and set it here, via postValue()
// as we are still in background thread.
result.postValue(true)
}
}
О том, как на самом деле реализовать мой псевдокод runinbackgroundthread
, см. Официальное руководство здесь: https://developer.android.com/guide/background/threading
Короче говоря, с сопрограммами Kotlin это должно выглядеть так:
viewModelScope.launch(Dispatchers.IO) {
filename = dao.getFilename() // without livedata
voice = getDefaultVoice(...) // also do this in background
add(voice)
result.postValue(true)
}
Комментарии:
1. Спасибо! Не могли бы вы посмотреть мой добавленный контент в вопросе?
2. Если вы заставите dao.GetFileName() возвращать LiveData, у вас может возникнуть та же проблема, что и раньше — вы не получаете значение сразу. На самом деле нет ничего сложного в добавлении другого метода в интерфейс DAO. У вас также может быть один, который возвращает LiveData, и тот, который возвращает значение напрямую — подробнее об этом см. В Документации Room .
Ответ №2:
Решение 1
Простым / сложным / грязным решением было бы пустое наблюдение в onViewCreated
like:
mHomeViewModel.savedRecordFileName.observe(viewLifecycleOwner){ }
что приводит к наблюдению LiveData, поэтому оно будет запрашиваться Room, потому что оно активно, и, надеюсь, когда вы нажимаете на кнопку, LiveData заполняется.
Решение 2
Но мой любимый подход был бы реактивным. Я не знаю, хотите ли вы взглянуть на свой код реактивно или нет, но вы можете использовать MediatorLiveData (или воспользоваться подобной библиотекой), чтобы решить эту ситуацию реактивно. Пусть в вашей виртуальной машине есть еще один LiveData с именем btnStopClicked
, и каждый щелчок изменяет значение этого LiveData. Итак, ваш код может выглядеть следующим образом:
class FragmentHome : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
...
binding.btnStop.setOnClickListener {
mHomeViewModel.btnStopClicked.value = null //we don't care about the value
}
mHomeViewModel.addVoiceEvent.observe(viewLifecycleOwner){
val aMVoice = getDefaultMVoice(mContext,it)
mHomeViewModel.add(aMVoice)
}
...
return binding.root
}
}
class HomeViewModel(val mApplication: Application, private val mDBVoiceRepository: DBVoiceRepository) : AndroidViewModel(mApplication) {
val savedRecordFileName: ... //whatever it was
val btnStopClicked = MutableLiveData<Any>()
val addVoiceEvent = savedRecordFileName
.combineLatestWith(btnStopClicked){srfn, _ -> srfn}
.toSingleEvent()
}
Также не забывайте toSingleEvent
, потому что, если вы не превратите это в событие, в некоторых ситуациях оно будет вызвано непреднамеренно (посмотрите на это).
Ответ №3:
TL; DR — Код A лучше, с дополнительными дополнениями.
Как вы уже знаете, LiveData
не гарантируется наличие ненулевого значения сразу после создания экземпляра. В идеале, что касается наилучшей практики, хорошо всегда рассматривать LiveData
значение как постоянно меняющееся (т.е. предполагать, что прямой доступ к значению дважды приведет к разным значениям). Это предполагаемое поведение, поскольку LiveData
оно предназначено для наблюдения, а не для того, чтобы вы имели дело с немедленным значением.
Следовательно, прослушайте LiveData
и соответствующим образом обновите свой пользовательский интерфейс. Например, отключите или скройте кнопку остановки, если внутри нее нет данных.
mHomeViewModel.savedRecordFileName.observe(viewLifecycleOwner) {
binding.btnStop.isEnabled = it != null
}
binding.btnStop.setOnClickListener {
mHomeViewModel.savedRecordFileName.value?.also {
val aMVoice = getDefaultMVoice(mContext,it)
mHomeViewModel.add(aMVoice)
}
}
Комментарии:
1. Спасибо! Обычно Room использует фоновый поток для асинхронного запроса записи, кажется, что я не могу получить результат запроса сразу, когда я использую Room , но иногда мне нужен результат запроса сразу, есть хороший способ сделать это?