LiveData observes twice instead of only one - kotlin

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.

Related

Access fragment view in parent activity

I have an activity which displays multiple fragments depending on which one is selected.
I also have a button in this activity and I want to obtain a value from a specific fragment when this button is clicked.
How can I obtain this value?
I tried to get the view I wanted from the fragment from the activity as the code shows below but I can understand that it doesn't work since the fragment is still to be created.
onOffButton.setOnClickListener {
if (onOffButton.text.contains("ON")) {
onOffButton.text = "TURN OFF"
var hoursPicker = findViewById<NumberPicker>(R.id.hoursPicker)
}
}
The short version is you shouldn't do this, there are all kinds of complications (especially when you're trying to access the Fragment's Views).
It's even more complicated if the Fragment might not even be added to the UI at all! If it's not there, what value are you supposed to use? If you want to somehow create the Fragment just so it exists, and so you can read the value from its text box, then that's a sign the value really needs to be stored somewhere else, so you don't need the Fragment if you want to access it.
The easiest, recommended, and modern way to share data like this is with a ViewModel:
class MyViewModel : ViewModel() {
// setting a default value here!
var currentHour: Int = 0
}
class MyActivity : AppCompatActivity() {
val model: MyViewModel by viewModels()
fun onCreate(...) {
...
onOffButton.setOnClickListener {
// access the data in the ViewModel
val currentHour = model.currentHour
}
}
}
class MyFragment : Fragment() {
// using activityViewModels so we get the parent Activity's copy of the VM,
// so we're all sharing the same object and seeing the same data
val model: MyViewModel by activityViewModels()
fun onViewCreated(...) {
...
hoursPicker.setOnValueChangeListener { _, _, newValue ->
// update the VM
model.currentHour = newValue
}
}
}
So basically, you have this ViewModel object owned by the Activity and visible to its Fragments. The VM outlives all of those components, so you don't lose data while an Activity is being destroyed on rotation, or when a Fragment isn't added to the UI, etc.
The VM is the source of data, everything else just reads from it, or updates it when something changes (like when the Fragment updates the variable when its number picker's value changes). This way, the Activity doesn't need to go "ask" the Fragment for info - it's stored in a central location, in the VM
This is the most basic way to use a ViewModel - you can start using LiveData and Flow objects to make different UI components observe data and react to changes too. For example, your button in your Activity could change some enabled state in the VM, and the Fragment (if it's added) will see that change and can do things like make the number picker visible or invisible.
It's way easier to coordinate this stuff with a ViewModel, so if you don't already know how to use them, I'd recommend learning it!

Android Room - selective query with LiveData

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

Preventing code in subscribe from executing when subject is initialized

In this code:
class RequestNewPasswordFragment {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
btnRequestNewPassword.setOnClickListener {
view.hideKeyboard()
viewModel.validateEmail(txtInputLayoutEmail.textValue)
}
disposables += viewModel.emailValidationSubject
.observeOnMainThread()
.subscribe { validationResponse ->
viewModel.requestNewPassword()
}
}
When the fragment is initialized, the emailValidationSubject gets initialized. This causes the code in subscribe to execute, which makes a call to the requestNewPassword in the viewModel. I want to avoid this. I want this to only be called when btnRequestNewPassword is clicked. The code in subscribe should only get called when the viewModel needs to validate the input. How can I prevent viewModel.requestNewPassword() from being called when the fragment is initialized?
I'm assuming your emailValidationSubject is a BehaviourSubject based on your previous question here.
BehaviourSubject will always emit an value on subscription, hence you need to provide an initial value.
it begins by emitting the item most recently emitted by the source Observable (or a seed/default value if none has yet been emitted)
You need to use a PublishSubject:
PublishSubject emits to an observer only those items that are emitted by the source Observable(s) subsequent to the time of the subscription.

Difference between get() and by lazy

Having a room Dao as below,
#Dao
public abstract class AccountDao {
#Query("SELECT * FROM Account LIMIT 0,1")
public abstract Account readAccount();
}
is there any differences between get() and by lazy in the sample below?
open val account: LiveData<Account>
get() = accountDao.readAccount()
open val account: LiveData<Account> by lazy { accountDao.readAccount() }
The difference is in how many times the function body (accountDao.readAccount()) will be executed.
The lazy delegate will execute the lambda one single time the first time it is accessed and remember the result. If it is called again, that cached result is returned.
On the other hand, defining the getter (get()) will execute the function body every time, returning a new result every time.
For example, let's suppose we have a class called Foo with both a getter and a lazy value:
class Foo {
val getterVal: String
get() = System.nanoTime().toString()
val lazyVal: String by lazy { System.nanoTime().toString() }
}
And then use it:
fun main() {
with(Foo()) {
repeat(2) {
println("Getter: $getterVal")
println("Lazy: $lazyVal")
}
}
}
For me, this prints:
Getter: 1288398235509938
Lazy: 1288398235835179
Getter: 1288398235900254
Lazy: 1288398235835179
And we can see that the getter returns a newly calculated value each time, and the lazy version returns the same cached value.
In addition to Todd's answer:
Yes, there is a difference for LiveData objects as well. Every call of accountDao.readAccount() will result in a different LiveData object. And it does matter, despite the fact that all of the returned LiveData will get updated on every change in the Account entity. Let me explain on these examples:
by lazy
As Todd mentioned, the block inside the lazy delegate will be executed once, at the first time that the account property is accessed, the result will be cached and returned on every next access. So in this case a single one LiveData<Account> object is created. The bytecode generated by Kotlin to achieve this is equivalent to this in Java:
public class Activity {
private Lazy account$delegate
public LiveData<Account> getAccount() {
return account$delegate.getValue();
}
}
get()
By creating a custom account property's getter and calling accountDao.readAccount() inside, you will end up with different LiveData<Account> objects on every access of the account property. Once more, bytecode generated for this case in Kotlin in Java is more or less this:
public class Activity {
public LiveData<Account> getAccount() {
return accountDao.readAccount();
}
}
So you can see, using a lazy property results in generating a backing field for this property, while using a custom getter creates a wrapper method for the accountDao.readAccount() call.
It's up to your needs which approach you should use. I'd say that if you have to obtain the LiveData only once, you should go with get(), because a backing field is needless in that case. However if you're going to access the LiveData in multiple places in your code, maybe a better approach would be to use by lazy and create it just once.

Correct way of implementing LiveData

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.