Android Studio에서 LivaData <String>의 값을 한 번에 얻으려면 어떻게해야합니까?
savedRecordFileName의 변수이므로 코드 A에서 한 번에 LivaData<String>의 값을 얻고 싶습니다 savedRecordFileName.
LiveData 변수가 게으르다는 것을 알고 있으며의 값 savedRecordFileName이에서 null binding.btnStop.setOnClickListener { }일 수 있으므로의 값 이 null 일 때 코드가 binding.btnStop.setOnClickListener { }실행되지 않습니다 savedRecordFileName.
의 코드 binding.btnStop.setOnClickListener { }가 항상 실행 되기를 바랍니다 . 어떻게해야합니까?
BTW, Code 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>
}
콘텐츠 추가
Ridcully에게 : 감사합니다!
나는 당신의 방법이 "그 모든 것을 viewmodel 클래스로 옮기는 것"이라고 생각한다!
filename코드 C에 라이브 데이터가 있어도 괜찮을 것이라고 생각합니다 .
코드 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)
}
답변
말했듯이 ui 스레드를 차단하지 않도록 백그라운드에서 Room 쿼리를 수행해야합니다. 하지만 여기서 멈 추면 안됩니다. 백그라운드 스레드에서 가능한 한 많은 작업을 수행하십시오. 귀하의 경우 버튼을 클릭하면 모든 작업을 수행하는 백그라운드 스레드 실행을 시작합니다. 데이터베이스에서 이름 가져 오기 (백그라운드에서 실행 중이므로 지금 가능), 기본 음성 가져 오기 및 추가 무엇이든 추가하고 있습니다.
또한 모든 것을 viewmodel 클래스로 옮겨야합니다.
btnStop.setOnClickListener {
viewmodel.stop()
}
viewmodel에서 다음과 같은 것 (Java / pseudo code like) :
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
간단하고 까다 롭고 더러운 솔루션은 다음 onViewCreated과 같이 빈 관찰입니다 .
mHomeViewModel.savedRecordFileName.observe(viewLifecycleOwner){ }
LiveData가 관찰되도록하므로 활성 상태이기 때문에 Room에서 쿼리하고 버튼을 클릭하면 LiveData가 채워집니다.
해결 방법 2
하지만 제가 가장 좋아하는 접근 방식은 반응 형입니다. 당신이 당신의 코드 여부를 대한 반응을 살펴보고 싶어 나도 몰라,하지만 당신은 MediatorLiveData를 사용 (또는 같은 라이브러리를 활용할 수 있습니다 이 )도 반응이 상황을 해결하기 위해. VM에 이름이 지정된 다른 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이벤트로 전환하지 않으면 일부 상황에서 의도하지 않게 호출 될 수 있으므로 잊지 마십시오 ( this 참조 ).
TL; DR-코드 A가 더 좋으며 추가 사항이 추가되었습니다.
이미 알고 있듯이 LiveData인스턴스화 직후에 null이 아닌 값이 있다는 보장은 없습니다. 이상적으로는 모범 사례로서 항상 LiveData가치를 항상 변화하는 것으로 취급하는 것이 좋습니다 (즉, 직접 가치 접근이 두 번 다른 가치를 생성한다고 가정). 이것은 LiveData즉각적인 가치를 다루는 대신 관찰 가능하도록 설계된 의도 된 동작 입니다.
따라서 LiveData이에 따라 UI를 듣고 업데이트하십시오. 예를 들어 내부에 데이터가 없을 때 중지 버튼을 비활성화하거나 숨 깁니다 .
mHomeViewModel.savedRecordFileName.observe(viewLifecycleOwner) {
binding.btnStop.isEnabled = it != null
}
binding.btnStop.setOnClickListener {
mHomeViewModel.savedRecordFileName.value?.also {
val aMVoice = getDefaultMVoice(mContext,it)
mHomeViewModel.add(aMVoice)
}
}