If I call lets say some function A in init block of View Model which will update live data and after that I m observing this live data in onViewCreated of activity. Then it is correct or not.? It will get that value which is stored in live data? Or I have to call function A after observing live data in activity.
And if it is working why it will work?
LiveData is just a data holder. It will store the latest value of the state. If you call function A in the init block, the live data will probably get its value before you start observing it in your Fragment/Activity i.e. when you observe it, you will get the new value of live data updated by function A.
If you call function A after setting up the observer, you will probably see the initial value of the LiveData first and then the new value will be observed.
In either case, it doesn't make much of a difference, because the value set by function A will anyway be processed. But is you want to set the value of LiveData as soon as the Activity/Fragment loads, init block of the ViewModel is a good choice.
Also note that if you call function A from the Fragment/Activity, it will be called every time the view gets destroyed and recreated. So if you are doing some operation in function A which should not be repeated on every view recreation, ViewModel's init block is the only option here.
Related
I have a fragment, which contains LiveData, and SwiperRefreshLauout. In onCreateView I have set the observer for some String.
User, while playing with fragment, can change the value of this string, but it should be only temporary. Whenever user will trigger onRefreshListener, the String should get "default" data, that came from observer.
I'd like to know, if there's any way to call data from observer again, in onRefreshListener, or I have to remove observer from onCreateView, and make another one after onRefreshListener is called?
Dummy scenario:
We have String, called test. In OnCreateView, we are setting the observer to the ViewModel, and get some data from database to the test string. Let's say it'll be name - "Mark". When user will pick from spinner another name: "Carl" the test string will have value of "Carl" now. User would like to refresh the UI of layout, and now, the test string should have value - "Mark" again.
If I understand your situation correctly, here I have a suggestion:
In the onCreateView setup an observer of your live data, viewModel.data.observe(...)
At the same time you could call the function responsible for fetching the data, viewModel.fetchData("initialValue"). On the other hand, since this is the initial call, you could use the init section of the View Model class and call there the initial fetch. init { fetchData("initialValue") }
Then, everytime the pull to refresh callback is executed, inside the onRefresh you could call the function to fetch the data, but this time with the temp value viewModel.fetchData("defaultValue")
So, everytime you call the viewModel.fetchData("anyValue") the data LiveData will be refreshed and since you are already subscribed in the onCreateView then you will receive the updates there.
if this is not clear, please share some code in this way, I can give better advise.
We use ViewModels for storing and manipulating data that is to be used by Views in Activity/Fragment - as data in ViewModel survives configuration change which helps us to retain UI state. It is one of the reasons we do network call operations in ViewModel, but is there any other reason we do network call operations in ViewModel?
I read somewhere that if we do network calls in ViewModels, the call does not happen again on configuration change? But I'm pretty sure API call is happening again in my Fragment on changing device orientation.
To overcome this problem you can call your function in init method of viewmodel makeApiCall() to prevent second call due to onViewCreated method. And you can store api result into livedata.
Alternatively, You can also use LiveData scope like this:
val makeApiCall: () -> LiveData<List<Data>> = {
liveData {
emit(repository.fetchData()) // API call
}
}
Call makeApiCall lambda function from your onViewCreated, now API call will emit only one time and your live data will get observed in onViewCreated.
This is the one of the main advantage of viewmodel to prevent API call on orientation change.
Another advantage is, Suppose if you close the screen and now API call is no longer needed so, if you are using RxJava you need to cancel/dispose API call to release resource so you can perform it in onCleared method of viewModel irrespective of orientation change.
Or if you are using coroutine you can use LiveData scope and viewModel
scope no need to care about canceling coroutine. it's managed by self.
We keep api hit in viewModel because of following reasons as per my practices
1)It reduces the coupling between Android components and non Android components
2)You can reuse the same ViewModel for some other screen as well
3) After fetching data you store that data in you liveData holder which can be used data to your UI on it's configuration change without making api hit
I want to make sure I understand correctly when is the onChanged method of a LiveData's Observer called.
Let's say we have an object A which has some primitives types
(some ints, some strings, etc.) and an object B as fields.
I know that onChanged is called when I call setValue. I'm pretty sure that it is also called when A's primitive fields change, or when I reassign the object field to a new instance, and that it is NOT called when I change any of B's fields.
Please correct me if I'm wrong.
Do these rules also apply to a LiveData object fetched from Room?
Are there any other cases where onChanged is called?
The fact is that the onChanged method of the LiveData's Observer is only called when the containing value of the LiveData is being set using setValue() or postValue() method. There is no mechanism to observe fields inside an object that is held by a LiveData. As a result, by changing the value of fields inside object-A, the observer shouldn't be notified.
On the other hand, as you know, the methods that are provided by Room to query on a db are able to return LiveData<SomeType> instead of SomeType. Under the hood, Room creates and registers a ContentObserver on your query's table(s) to get aware of changes in it. So, every time a change occurs on the data, Room gets notified using the mentioned ContentObserver, then fetches the query result again and post it on the LiveData.
I want to initialize the LiveData value when the app is launched, not every time the orientation changes.
Can I use the constructor of the subclass of ViewModel for it?
Instead of LiveData which pushes the last value to observers for every config change (like a RxJava BehaviourSubject), you should use something which pushes the event once.
You can use:
SingleLiveEvent: Send the event to only 1 observer, check here and here or alternatives here
LiveEvent: Send the event to all the observers, check here
Both of those approaches will not cache events, which means that an observer should be already observing the *LiveEvent to receive it
You can find articles online about alternative approaches but the philosophy behind them is probably the same
I was left with some questions regarding ViewModels after reading this:
https://developer.android.com/topic/libraries/architecture/saving-states
It says here that you should use a combination of both a ViewModel for configuration changes (like screen rotation), and using onSaveInstanceState() for all other cases where an activity is destroyed and then recreated in order to save the UI state.
My question is how do we know the way to restore the state when onCreate(Bundle) is called - should I use the ViewModel or should I use the bundle received as a parameter? When the configuration changes, onSaveInstanceState() is also called, and obviously onCreate() is always called.
If I only restore the state from a ViewModel, it won't always remain with the correct data (since the activity could have been destroyed due to other reasons than configuration changes). If I only use the bundle I save in onSaveInstanceState() then why would I use a ViewModel to begin with?
I think it's good to think of this sources as a chain.
You have 2 sources of data - ViewModel, that is faster but lives less and saved instance state that is slower but lives longer.
The rule is simple - try using your ViewModel and if it is not populated use the bundle from onSaveInstanceState().
When you do val model = ViewModelProviders.of(this).get(MyViewModel::class.java) in onCreate() you can check if you get a new instance of viewModel. Then, if it is a new instance (i.e. it's data fields are empty) you can get some basic data from your bundle, like content id, and fetch data from the backend or database based on that id, populate your new ViewModel with it and then populate your activity from the ViewModel (if you are using LiveData it will be very natural).
Next time onCreate is called you repeat the process, either populating your activity from ViewModel or populating your ViewModel using data in the Bundle and then populating your activity from your ViewModel.
Update:
Actually there is very similar approach described in the official docs. The only difference is that you pass the bundle to ViewModel and it decides if it needs fetching data, I was not specific about this mechanism.