Android Studio에서 LivaData <String>의 값을 한 번에 얻으려면 어떻게해야합니까?

Oct 21 2020

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)
}

답변

1 Ridcully Oct 28 2020 at 04:02

말했듯이 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)
}
2 Amin Oct 28 2020 at 03:30

해결책 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 참조 ).

WeiChan Oct 22 2020 at 03:58

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)                
    }
}