Is it possible to get specific rows from Room with LiveData?
My goal is to retrieve certain items from data base, when each item is conditioned by to columns ("page" and "category").
The problem is that in LiveData observer I always receive the same page - that livedata object had been initiated with it in the beginning.
It has no effect if I change it afterwards in the ViewModel:
private fun requestNextPageFromDB(page: Int) {
filmsListLiveData = interactor.getPageOfFilmsFromDB(page)
}
I create the LiveData object in init block of my ViewModel:
var filmsListLiveData: LiveData<List<Film>>
init {
filmsListLiveData = interactor.getPageOfFilmsFromDB(2)
The method in Interactor class I initiate the livedata object:
fun getPageOfFilmsFromDB(page: Int): LiveData<List<Film>> =
repo.getPageOfFilmsInCategoryFromDB(page, getFilmsCategoryFromPreferences())
Next method in the repository:
fun getPageOfFilmsInCategoryFromDB(page: Int, category: String): LiveData<List<Film>> {
return filmDao.getCachedFilmsByPageAndCategory(page, category)
}
And the last one in the Dao Intertface:
#Query("SELECT * FROM cached_films WHERE page=:requestedPage AND category=:requestedCategory")
fun getCachedFilmsByPageAndCategory(requestedPage: Int, requestedCategory:String): LiveData<List<Film>>
All the methods above are invoked properly when the page number changes in livedata object.
Thanks a lot in advance.
From the description it seems that you are just replacing the filmsListLiveData reference, however the observer remains the original one, that is why it is not notified about any changes, since in the original filmsListLiveData there have been no changes.
You can use a MutableLiveData for the page value and then a Transformations.switchMap to obtain a LiveData that will react to the changes of the page value.
Inside your ViewModel, you can do something like this
// starting page is 2, not sure if it should be 1 or 2, I just copied you logic from init
private val currentPageLiveData = MutableLiveData(2)
var filmsListLiveData = Transformations.switchMap(currentPageLiveData) { page ->
interactor.getPageOfFilmsFromDB(page)
}
private fun requestNextPageFromDB(page: Int) {
currentPageLiveData.value = page
}
init {
// not needed anymore - remove this code
// filmsListLiveData = interactor.getPageOfFilmsFromDB(2)
}
With that, every time requestNextPageFromDB(page: Int) will be called, currentPageLiveData value will change, the switchMap will get called and a new call to interactor.getPageOfFilmsFromDB(page) will be made. That will update the LiveData value and your observer will be called again.
So the switchMap does exactly what you need, it switches the LiveData behind the scenes every time the source LiveData changes (in this case the currentPageLiveData) in such a way, that existing observers stop observing the old LiveData instance, the one switched from, and start observing the new LiveData instance, the one switched to.
At this point you can also change var filmsListLiveData to val filmsListLiveData and remove all code that is trying to replace its reference, since there is no need to replace this LiveData anymore (now it reacts to the page value change on its own).
Related
I know this question has been asked a couple of times, but I have a funny situation here that I can't figure out.
I have a database with "TASKS" and, apart from the writing/updating methods, I'm calling the get method twice: one for getting the last created task, and another to select an specific task by ID
#Query("SELECT * FROM tasks_history_table ORDER BY taskId DESC LIMIT 1")
suspend fun getCurrentTask(): Task2?
#Query("SELECT * from tasks_history_table WHERE taskId = :key ")
suspend fun get(key: Long): Task2?
Then in the viewModel I'm launching a coroutine for each time I call one of these methods.
This one for the last created Task:
private fun initializeCurrentTask(){
viewModelScope.launch {
_currentTask.value = getCurrentTaskFromDatabase()!!
}
}
suspend fun getCurrentTaskFromDatabase(): Task2? {
var currentTask = database.getCurrentTask()
return currentTask
}
And this one for the specific task
fun initializeSelectedTask(){
viewModelScope.launch {
_currentTask.value = getSelectedTaskFromDatabase(selectedTaskId.value!!)!!
}
}
suspend fun getSelectedTaskFromDatabase(taskId: Long): Task2? {
var currentTask = database.get(selectedTaskId.value!!)!!
return currentTask
}
So they are both pretty much the same, except for the parameter Id passed.
Then, I'm sending that data to the Fragment to update the UI, via LiveData
private val _currentTask = MutableLiveData<Task2>()
val currentTask : LiveData<Task2>
get() = _currentTask
And here the observer:
timerViewModel.currentTask.observe(viewLifecycleOwner) {
updateUIText()
updateCountdownUI()
updateAnimation()
}
Everytime I call the function to get the las saved task, the observers are called and everything works fine. But whenever I call the function to get a specific task by Id, the observers are not called.
I've set Logs all around and I've reached the conclusion that the LiveData is getting updated, but the observer is not triggered.
Here's the repo in case someone can have it a look. Thanks!!
https://github.com/arieldipietro/PomodoroTechnique
In FragmentTimer and FragmentHistory, you have created viewModel instances using their respective fragments as owners, which makes them to observe liveData which are triggered by their instances only. So, now when you trigger a task from FragmentHistory it isn't get observed in FragmentTimer.
You need to use SharedViewModel for passing data between fragments, you have to create object of TimerViewModel using activity as its owner, then you would be able to observe from FragmentTimer. Pass requireActivity() as the owner in ViewModelProvider's constructor.
timerViewModel = ViewModelProvider(requireActivity(), viewModelFactory)[TimerViewModel::class.java]
You can read more about it in this codelab tutorial.
I'm using MVVM as architecture on my app, but I watch an unexpected escenario in the observer code: Inside the observer always entered one time before get the real value.
myViewModel.getUserInfo().observe(this, androidx.lifecycle.Observer { user ->
if (user!= null) {
} else {
//THE FIRST TIME THROW HERE
}
In my viewModel I have this:
class MyViewModel : ViewModel() {
fun getUserInfo(): MutableLiveData<UserInfoResponse> {
val liveData: MutableLiveData<UserInfoResponse> = MutableLiveData()
liveData.postValue(UserInfoResponse("user"))
return liveData
}
Can anyone got the idea that is happening?
Thanks
postValue()
Posts a task to a main thread to set the given value. So if you have a following code executed in the main thread:
liveData.postValue("a");
liveData.setValue("b");
The value "b" would be set at first and later the main thread would override it with the value "a".
If you called this method multiple times before a main thread executed a posted task, only the last value would be dispatched.
At a guess, you're posting that value before you return the LiveData, but it actually gets set after the observer has been added and received the first value. When that happens, the observer is called again with the second, posted value.
You've mentioned adding and removing fragments, and I'm assuming they're using the same ViewModel, so when they start observing the LiveData it already has a value set from earlier, and the observer receives that immediately. The posted value comes later.
Im trying to get some data out of other ViewModels inside another ViewModel to make my code smaller, but im having a problem trying to implement what already worked on a fragment or in a activity, this is what i got:
class ObraConMediaViewModel(private val context: ViewModelStoreOwner,
private val id: Int): ViewModel(), LifecycleObserver {
var allObras: LiveData<ArrayList<ObraConMedia>>
private lateinit var viewModelobras: ViewModelObras
private lateinit var viewModelMediaObra: ViewModelMediaObra
val repositoryobras =ObrasRepository()
val repositoryMediaObra = MediaObraRepository()
val viewModelFactoryobras = ViewModelFactoryObras(repositoryobras)
val viewModelMediaObraFactory = ViewModelMedIaObraFactory(repositoryMediaObra)
init{
viewModelobras = ViewModelProvider(context, viewModelFactoryobras)
.get(ViewModelObras::class.java) // requireActivity() when called
viewModelMediaObra = ViewModelProvider(context, viewModelMediaObraFactory)
.get(ViewModelMediaObra::class.java)
viewModelobras.getObras(id)
viewModelobras.myResponse.observe(this , Observer { response ->
if (response.isSuccessful){
Log.d("Response", response.body()?.ans?.get(0)?.autor)
Log.d("Response", response.body()?.ans?.get(1)?.autor)
}else{
Log.d("Response", response.errorBody().toString())
}})
viewModelMediaObra.getMediaObra(Constantes.PRUEBA_ID)
viewModelMediaObra.myResponse.observe(this, Observer { response ->
if (response.isSuccessful){
Log.d("Response", response.body()?.ans?.get(0)?.filePath)
}
})
}}
I was having trouble with the Observer but extending the class to LifecycleObserver fixed it, i have no idea if this will even work but the only error that i have right now its the owner of the .observe(this,....), i dont seem to find a way to pass a lifecycleowner from the fragment to this viewmodel. All the variables i need to make this viewmodel work are inside those two responses. If this is a very bad way to do it please tell me. Thanks for reading.
Kindly note that above approach is not correct.
One should not create a instance of ViewModel inside another ViewModel.
There is a possibility that one ViewModel may get destroyed before another. This will lead to garbage reference and memory leaks.
I would recommend you to create the instance of both View Models in an Activity/Fragment and then call respective methods of ViewModel from Activity/Fragment.
Also, as you want to make your code smaller and concise, I highly recommend you Shared ViewModel.
This Shared ViewModel can be used by two fragments.
Please refer to this link
School project and I'm pretty new to Android development.
The problem
I have a button with a onClick listener in a save person fragment which will save person data to the database. Everything works fine except for some reason with the first click it wont return me the inserted row ID but it will do so with 2nd click onwards.
I really need this ID in before proceeding to the next fragment.
Not sure if this is important but whenever I return (reload) to this save person fragment, the behaviour is always the same that the first click allways fails to capture the inserted row ID.
input data:
first name = John
last name = Smith
Just for demo purpose, if I will try to use this button 3x to insert the person data (returned insert ID is in the log), I will get all 3 rows in database with name John Smith, but the very first inserted row ID is not captured (default initialised value is 0), please see the log below:
Log
2020-10-19 12:49:20.320 25927-25927/ee.taltech.mobile.contacts D/TEST_ADD_PERSON_ID: insertedPersonId: 0
2020-10-19 12:49:40.153 25927-25927/ee.taltech.mobile.contacts D/TEST_ADD_PERSON_ID: insertedPersonId: 5
2020-10-19 12:49:40.928 25927-25927/ee.taltech.mobile.contacts D/TEST_ADD_PERSON_ID: insertedPersonId: 6
EDITED ORIGINAL post
As suggested in the comments, I'm trying to go about the way of using LiveData and observer, but I'm still little bit stuck.
The setup
The below is the current setup.
Entity
#Entity(tableName = "person")
data class Person(
#PrimaryKey(autoGenerate = true)
val id: Int,
DAO
#Dao
interface PersonDao {
#Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun addPerson(person: Person): Long
Repository
class PersonRepository(private val personDao: PersonDao) {
val readAllPersonData: LiveData<List<Person>> = personDao.readAllPersonData()
suspend fun addPerson(person: Person): Long {
return personDao.addPerson(person)
}
ViewModel
I'm not sure if I'm doing things right at all here. I broke it down here in steps and created separate variables insertedPersonILiveData and insertedPersonId.
How could pass the returned row id to insertedPersonILiveData?
class PersonViewModel(application: Application) : AndroidViewModel(application) {
var insertedPersonILiveData: LiveData<Long> = MutableLiveData<Long>()
var insertedPersonId: Long = 0L
val readAllPersonData: LiveData<List<Person>>
private val repository: PersonRepository
init {
val personDao = ContactDatabase.getDatabase(application).personDao()
repository = PersonRepository(personDao)
readAllPersonData = repository.readAllPersonData
}
suspend fun addPerson(person: Person) = viewModelScope.launch {
insertedPersonId = repository.addPerson(person)
// ****************************************************************
// insertedPersonILiveData = insertedPersonId (what to do here) ???
// ****************************************************************
}
Save person fragment
This is the way I'm calling out the addPerson via modelView.
val person = Person(0, firstName, lastName)
lifecycleScope.launch {
personViewModel.addPerson(person)
}
Log.d("TEST_ADD_PERSON_ID","insertedPersonId: ${personViewModel.insertedPersonId}")
And this is the way I have done the observer (not sure if it's even correct).
val returnedIdListener: LiveData<Long> = personViewModel.insertedPersonILiveData
returnedIdListener.observe(viewLifecycleOwner, Observer<Long> { id: Long ->
goToAddContactFragment(id)
})
private fun goToAddContactFragment(id: Long) {
Log.d("TEST_ADD_PERSON_ID", "id: " + id)
}
Create database
#Database(
entities = [Person::class, Contact::class, ContactType::class],
views = [ContactDetails::class],
version = 1,
exportSchema = false
)
abstract class ContactDatabase : RoomDatabase() {
abstract fun personDao(): PersonDao
abstract fun contactTypeDao(): ContactTypeDao
abstract fun contactDao(): ContactDao
abstract fun contactDetailsDao(): ContactDetailsDao
companion object {
// For Singleton instantiation
#Volatile
private var instance: ContactDatabase? = null
fun getDatabase(context: Context): ContactDatabase {
return instance ?: synchronized(this) {
instance ?: buildDatabase(context).also { instance = it }
}
}
private fun buildDatabase(context: Context): ContactDatabase {
return Room.databaseBuilder(context, ContactDatabase::class.java, "contacts_database")
.addCallback(
object : RoomDatabase.Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
val request = OneTimeWorkRequestBuilder<SeedDatabaseWorker>().build()
WorkManager.getInstance(context).enqueue(request)
}
}
)
.build()
}
}
}
You're starting a coroutine to run addPerson, and then immediately calling Log with the current value of insertedPersonId in the viewmodel. The coroutine will run, insert the person, and update the VM with the ID of the inserted row, but that will happen long after your Log has run. Probably all of your results are actually the ID of the last record that was inserted.
I'm new to a lot of this too, but just based on what you have now, I think you just need to add
insertedPersonILiveData.value = insertedPersonId
in your addPerson function. That way you're updating that LiveData with a new value, which will be pushed to any valid observers. You've written some code that's observing that LiveData instance, so it should get the update when you set it.
edit your problem is that insertedPersonILiveData is the immutable LiveData type, so you can't set the value on it - it's read-only. You're creating a MutableLiveData object but you're exposing it as a LiveData type.
The recommended pattern for this is to create the mutable one as an internal object, expose a reference to it as an immutable type, and create a setter method that changes the value through the mutable reference (which it can access internally)
class myViewModel : ViewModel() {
// mutable version is private, all updates go through the setter function
// (the _ prefix is a convention for "private versions" of data fields)
private val _lastInsertedPersonId = MutableLiveData<Long>()
// we're making the instance accessible (for observing etc), but as
// the immutable LiveData supertype that doesn't allow setting values
val lastInsertedPersonId: LiveData<Long> = _lastInsertedPersonId
// setting the value on the MutableLiveData instance
// is done through this public function
fun setLastInsertedPersonId(id: Long) {
_lastInsertedPersonId.value = id
}
}
and then your observer would just call lastInsertedPersonId.observe, you don't need to copy the LiveData and observe that (like you're doing with returnedIdListener.
That's the basic pattern right there - internal MutableLiveData, exposed publicly as an immutable LiveData val, with a setter method to update the value. Everything outside the view model either observes the LiveData that's visible, or calls the setter method to update. Hope that makes sense! It's not that complicated once you get your head around what's basically going on
In the Android docs it shows an example creating a LiveData object as follows:
val currentName: MutableLiveData<String> by lazy {
MutableLiveData<String>()
}
But I have seen code elsewhere that shows it like this:
val currentName: MutableLiveData<String> = MutableLiveData()
Both of these are located in the viewmodel. In the second example, the LiveData model is instantiated when the class is created whereas in the first example, it is only instantiated when the object is first used.
Are both of these cases valid?
Yes, both of these cases are valid. However, there is a distinct difference between the two. When using by lazy it will still set the LiveData object, but it will not set it until the variable is first used. In the case of the second option, it will initialize the LiveData object when parent is created.