Как я могу получить значение LivaData сразу в Android Studio?

#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 , но иногда мне нужен результат запроса сразу, есть хороший способ сделать это?