Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I get the query result at once when I use Room?

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 enter image description here

like image 583
HelloCW Avatar asked Oct 22 '20 08:10

HelloCW


People also ask

What is Dao in room?

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.

What annotation is used to build your SQL query at runtime dynamically?

The @Query annotation allows you to write SQL statements and expose them as DAO methods.

How do you read a room database?

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.


4 Answers

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:

  1. load data from Room via LivaData
  2. add observer that stores the current total amount
  3. when the button is clicked you just read the local copy

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..

like image 23
Tobi Avatar answered Oct 21 '22 01:10

Tobi


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.

like image 155
Wale Avatar answered Oct 21 '22 03:10

Wale


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()
        })
    }
like image 1
Iliya Mashin Avatar answered Oct 21 '22 01:10

Iliya Mashin


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

like image 1
Muhammad Shujja Avatar answered Oct 21 '22 01:10

Muhammad Shujja