I hope to get the total of all records with Room database at once. But, normally Room use background thread to query record asynchronously.
If I use getTotalOfVoiceAsLiveData()
in Code A, it will return LiveData<Long>,
you know that LiveData variable is lazy, maybe the result is null.
If I use getTotalOfVoice()
in Code A, I will get error because I can't use return
in viewModelScope.launch{ }
.
How can I get the total of all records at once with Room database?
Code A
class HomeViewModel(val mApplication: Application, private val mDBVoiceRepository: DBVoiceRepository) : AndroidViewModel(mApplication) {
fun getTotalOfVoice():Long {
viewModelScope.launch {
return mDBVoiceRepository.getTotalOfVoice() //It will cause error
}
}
fun getTotalOfVoiceAsLiveData(): LiveData<Long>{
return mDBVoiceRepository.getTotalOfVoiceAsLiveData() //It's lazy, maybe the result is null.
}
}
class DBVoiceRepository private constructor(private val mDBVoiceDao: DBVoiceDao){
suspend fun getTotalOfVoice() = mDBVoiceDao.getTotalOfVoice()
fun getTotalOfVoiceAsLiveData() = mDBVoiceDao.getTotalOfVoiceAsLiveData()
}
@Dao
interface DBVoiceDao{
@Query("SELECT count(id) FROM voice_table")
suspend fun getTotalOfVoice(): Long
//When Room queries return LiveData, the queries are automatically run asynchronously on a background thread.
@Query("SELECT count(id) FROM voice_table")
fun getTotalOfVoiceAsLiveData(): LiveData<Long>
}
Add content
To Tobi: Thanks!
Why it is important to you to get the data directly?
I need to generate a filename based the total of the records, such as "untitled0", "untitled1", "untitled2"...
If I can get the query result at once, I can use the following code easyly.
Added again
I hope to record a voice by filename based the total of query records when I click Start button. You know the total of records will change when a reocrd is added or deleted!
Code B
fun getTotalOfVoice():Long {
//Get the query result at once
...
}
fun createdFileanme(){
return "untitled"+getTotalOfVoice().toString()
}
btnStart.setOnClickListener{
recordVoice(createdFileanme()) //I will record voice by filename
}
fun addRecord(){
...
}
fun deleteRecord(){
...
}
New added content
Thanks!
I think 'You should also move all of that into the viewmodel class, without LiveData '
is good way, you can see Image A and How can I get the value of a LivaData<String> at once in Android Studio? .
Do you agree with it?
Image A
android.arch.persistence.room.Dao. Marks the class as a Data Access Object. Data Access Objects are the main classes where you define your database interactions. They can include a variety of query methods. The class marked with @Dao should either be an interface or an abstract class.
The @Query annotation allows you to write SQL statements and expose them as DAO methods.
Go To App Inspection . Then select the process. Database Inspector will now show the database contents. in 2021, with current Android Studio, this should be the only accepted answer.
I hope to get the result at once, but LiveData is lazy
Sorry to tell, but this is how the Room interface is designed. You are right with the lazyness of the returned LiveData object. But this allows you to handle it on a different thread without having to manually handle different threads.
Based on your new information!
You basically have two options:
A) you could do the following:
In your View: (only one observer and one clickListener)
val totalVoiceCount: long
val viewModel = ViewModelProvider(requireActivity()).get(HomeViewModel::class.java)
viewModel.getTotalOfVoiceAsLiveData().observe(viewLifecycleOwner, Observer { totalOfVoice : Long ? ->
if (totalOfVoice != null)
totalVoiceCount = totalOfVoice
})
btnStart.setOnClickListener{
viewModel.recordVoice(totalVoiceCount)
}
In your ViewModel: (the logic and everything else)
fun recordVoice(totalVoiceCount : long){
val fileName = createdFileanme(totalVoiceCount)
// create your recording // depending on how you do this, it probably runs on a background thread anyways
}
fun createdFileName(totalVoiceCount : long){
return "untitled"+ String.valueOf(totalVoiceCount)
}
This works reliably because the LiveData has enough time to update the local copy of totalVoiceCount before the user has the chance to click the button.
B) Based on the answer in your parallel question you can of course outsource even more to a background thread. Then you also have the option to call the DAO query with a non-livedata return (as room returns non-livedata queries only on background threads). Is it worth to implement the threading suggestion of Ridcully? Not possible to answer without knowing what else is going on simultaneously... To me it seems like an overkill, but he is right that the more you do on background threads the better for your refresh rate..
Question: at once meaning synchronous or what ? if yes, what happens if the function to get the result has to take a longer time? like network call? well you can decide to do that on another thread.
What I think is for you to use a mutable Object and use the postValue function to dispatch the result to the observers. It should look something like below:
class HomeViewModel(val mApplication: Application, private val mDBVoiceRepository: DBVoiceRepository) : AndroidViewModel(mApplication) {
private val voices = MutableLiveData<Long>()
fun getTotalOfVoiceAsLiveData(): LiveData<Long> {
voices.postValue(mDBVoiceRepository.getTotalOfVoiceAsLiveData().value)
return voices;
}
}
Making use of it in your Fragment will look like below:
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
if (activity != null) {
val viewModel = ViewModelProvider(requireActivity())
viewModel.get(HomeViewModel::class.java).getTotalOfVoiceAsLiveData().observe(viewLifecycleOwner, Observer { voices: Long ? ->
voices // Sound of music ? be very free to use ...
})
}
}
Happy Coding.
You can return Deferred<Long>
from viewModelScope.async
. I recommend you to use:
val deferred = viewModelScope.async(Dispatchers.IO) {
return@async mDBVoiceRepository.getTotalOfVoice()
}
val value = deferred.await()
await() is suspend
Edit: If you want to get a getter which will use in your activity or fragment you need to write a suspend function like this:
suspend fun getTotalOfVoice(): Long {
return viewModelScope.async(Dispatchers.IO) {
return@async mDBVoiceRepository.getTotalOfVoice()
}.await()
}
But mvvm pattern allows you to create LiveData inside your ViewModel, which gives your fragment an observer.
In view model:
private val _totalOfVoiceLD: MutableLiveData<Long> = MutableLiveData()
val totalOfVoiceLD: LiveData<Long>
get() = _totalOfVoiceLD
fun updateTotalOfVoice() {
viewModelScope.launch(Dispatchers.IO) {
val totalOfVoice = mDBVoiceRepository.getTotalOfVoice()
_totalOfVoiceLD.postValue(totalOfVoice)
}
}
and in your fragment:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.totalOfVoiceLD.observe(viewLifecycleOwner, Observer { totalOfVoice ->
totalOfVoiceTextView.text = totalOfVoice.toString()
})
}
You can use coroutineContext.async to get data from DB and wait for getting it's response with data by using .await function for a async dispatch.
suspend fun getAllVoices() : Long{
val awatingResults = viewModelScope.async(Dispatchers.IO) {
mDBVoiceRepository.getTotalOfVoice()
}
val records = awatingResults.await()
return records
}
It is necessary to call a Suspend function from a coroutine and async.await() is always called in a suspended function so,
val voiceLiveData: MutableLiveData<Long> = MutableLiveData()
fun getAllVoicesFromDB() {
viewModelScope.launch(Dispatchers.IO) {
voiceLiveData.postValue(mDBVoiceRepository.getTotalOfVoice())
}
}
Now call it where ever you want to get your voice data from database and also remember do your further work inside your voiceLiveData observer where you get your response of voices :)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With