Difference of setValue() & postValue() in MutableLiveData
Based on the documentation:
setValue():
Sets the value. If there are active observers, the value will be
dispatched to them. This method must be called from the main thread.
postValue():
Posts a task to a main thread to set the given value. If you called this method multiple times before a main thread executed a posted task, only the last value would be dispatched.
To summarize, the key difference would be:
setValue()
method must be called from the main thread. But if you need set a value from a background thread, postValue()
should be used.
When to use postvalue
According to the documentation:
Based on the documentation:
setValue():
Sets the value. If there are active observers, the value will be
dispatched to them. This method must be called from the main thread.
postValue():
Posts a task to a main thread to set the given value. If you called
this method multiple times before a main thread executed a posted
task, only the last value would be dispatched.
So I would always use setValue when possible, in your case as you have noticed setValue is better because you can be sure it will always be on the Main Thread.
However postValue can also be called from the Main Thread, but will perform a tiny bit worse.
Why LiveData setValue or PostValue triggers onChange just once in the view?
The Activity shouldn't have any MutablieLiveData member variables, that should be inside the ViewModel.
The reason for why it only works the first time, is because the first time you observe something it notifes as changed, however because your arrangement is incorrect it never updates again. That is, because ArticleRepository is recreated again inside your ViewModel with a new set of MutableLiveData, the previous one you subscribed to is no longer relevant - and you only subscribe once onCreate()
.
You should separate bind from async-tasks such as loadData()
they are not the same thing. Binding is what you do at the beginning to gather the MutableLiveData (what you are doing in loadData), but after you've done that once you shouldn't do it again.
I also noted that you are actually having LiveData inside the model, it's not recommended to do it this way as it breaks the pattern and can bring other issues. It's the ViewModel that is supposed to prepare the presentation, not the Repository. As you currently have configured things your repository might as well be called the ViewModel. Instead what you should do is use observables to notify the ViewModel of a new batch to post or handle possible errors that occurred.
Study this example: https://developer.android.com/topic/libraries/architecture/viewmodel
Note that loadUsers()
is done once when getUsers()
is called. This is what binds the Activity to the ViewModel. But loadUsers()
can be done again later and should post the changes to the LiveData inside the ViewModel.
Difference between 'setValue' and 'value' in Kotlin 4.1 MutableLiveData?
Thanks to this link - kotlinlang.org/docs/reference/java-interop.html#getters-and-setters - provided by @IR42 and other information by other contributors whos comments were unfortunately deleted I found my answer:
MutableLiveData is a Java class and Kotlin will infer a property when the Java class has methods that follow the Java conventions for getters and setters (no-argument methods with names starting with get and single-argument methods with names starting with set)
Code completion will not suggest the Java getter methods (i.e. getValue and setValue) but it will suggest the Kotlin inferred property (i.e. value)
You can still use the Java getter/setter methods but this is discouraged.
Using setValue instead of PostValue for LiveData
I just upgraded my project using a single Activity and multiple fragments using Navigation component, and the problem that I explained in the question resolved.
As I mentioned I was using multiple Activities with multiple fragments, as a result I reported that in Google issue tracker
setValue and postValue on MutableLiveData in UnitTest
First thing is that you need to work with the InstantTaskExecutorRule, that means:
@Rule public InstantTaskExecutorRule instantExecutorRule = new InstantTaskExecutorRule();
This will allow to work with MutableLiveData instantly. Remember to include in your app's build.gradle the import:
testCompile "android.arch.core:core-testing:$rootProject.ext.arch_version"
Then, you need to either define a JUnit Rule with a rule overriding RxSchedulers or within the @Before override the Schedulers as well using RxAndroidPlugins (if you are using RxJava)
RxSchedulersOverrideRule:
public class RxSchedulersOverrideRule implements TestRule {
@Override public Statement apply(final Statement base, Description description) {
return new Statement() {
@Override public void evaluate() throws Throwable {
RxAndroidPlugins.reset();
RxAndroidPlugins.setMainThreadSchedulerHandler(scheduler -> Schedulers.trampoline());
RxJavaPlugins.reset();
RxJavaPlugins.setIoSchedulerHandler(scheduler -> Schedulers.trampoline());
RxJavaPlugins.setComputationSchedulerHandler(scheduler -> Schedulers.trampoline());
RxJavaPlugins.setNewThreadSchedulerHandler(scheduler -> Schedulers.trampoline());
base.evaluate();
RxAndroidPlugins.reset();
RxJavaPlugins.reset();
}
};
}
}
And calling it in the test class:
@Rule public RxSchedulersOverrideRule rxSchedulersOverrideRule = new RxSchedulersOverrideRule();
The other option is calling within setup() in @Before:
RxAndroidPlugins.setInitMainThreadSchedulerHandler(schedulerCallable -> Schedulers.trampoline());
After using this you need to reset in @After's tearDownClass():
RxAndroidPlugins.reset();
MutableLiveData setValue: Unexpected behavior in Activity's onCreate
Your problem is that livedata
observer
is not active. because of this when you do liveData.value = 11
, this value doesn't get posted to the observer
.
and subsequently when you do liveData.value = 13
it overrides
the value 11.
To check if your live data has any active observers, you can do liveData.hasActiveObservers()
Docs clearly state that setValue
only dispatches if there are any active observers
void setValue (T value)
Sets the value. If there are active observers, the value will be
dispatched to them.
But why your observer is not active?
When you set a value, LiveData
internally uses ObserverWrapper
's shouldBeActive
method to check if a specific observer is active .
and when you use observe
method to register your observer
, the observer
is wrapped in a LifecycleBoundObserver
(subclass of ObserverWrapper
) which defines its shouldBeActive
as follows.
@Override
boolean shouldBeActive() {
return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
}
The part that matters is .isAtLeast(STARTED)
, here STARTED
is of type Lifecycle.State, and its documentation has following to say about it.
Started state for a LifecycleOwner. For an Activity, this state is
reached in two cases:after onStart call;
right before onPause call.
and because you register observer
in onCreate
, it doesn't become active right away and hence the problem.
For verification you can also use the observeForever
(please read its documentation, it differs greatly from observe
) method to register your observer
. because this method makes the observer
active
instantly, you will see the output that you expect.
liveData.observeForever {
Log.v("ActivityA", "liveData = $it")
}
Related Topics
Android Device Chooser -- Device Not Showing Up
Asynctask: Where Does the Return Value of Doinbackground() Go
How to Combine Bottomappbar + Fab with Bottomnavigationview
Best Way to Update Data with a Recyclerview Adapter
Justify Text in an Android App Using a Webview But Presenting a Textview-Like Interface
Adding Watermark Bitmap Over Video in Android: 4.3's Mediamuxer or Ffmpeg
How to Include Data Files with the App's APK
Broadcast Receiver Class and Registerreceiver Method
Determining If an Activity Exists on the Current Device
Difference Between Extending Lifecycleactivity,Activity,Actionbaractivity & Appcompactactivity
Official "Boost Library" Support for Android and iOS
How to Use Android Canvas to Draw a Rectangle with Only Topleft and Topright Corners Round
Dynamically Add Textviews to a Linearlayout
Eclipse Indigo - Cannot Install Android Adt Plugin
How to Handle Back Button with in the Dialog