I'm using room in viewmodel to get list of Strings and show on RecyclerView. I use Livedata for returning list of room to observe it on Fragment and set data to RecyclerView
The problem is when I modify any data in my RecyclerView all the above steps runs again because I used LiveData and it listening for changes and recylerview rest.
But why I used livedata? just to inform when Room process is done and get result
my question is should I use livedata for queries that no need to get updates?
if yes: how to prevent set data again?
if no: how to inform data is fetched while object without livedata cant be ovserved?
Related
In MVVM architecture, should the view model only return Live data to fragment?
Is it ok to return other primitive data type other than Live data also?
You could return the data type that you need but in most cases you need to return Live data or StateFlow ( which is similar to Live data )
Because you want that observe in your fragment to get notified about any change.
There is no difference is term of architecture between LiveData<String> and String but it's just about what you need, if you need data that you can observe and change the UI of your fragment depending on that data use Live data and if you just need to access another primitive data type for some reason, it's totally fine.
It depends on your case.
As we know ViewModel is the recommended UI-State holder, it maintains the state while changing the view configuration.
We usually use observable types LiveData, RxJava2, and Flow, so if any changes come from the data or domain layers or even from another view like "SharedViewModel" the UI will be continuously updated.
In most cases, we use observable types in the view model and non-observables in the view itself.
We can see this UI event decision tree in the documentation.
which helps us to configure where should we put the data.
.
for more details.
UI events
State holders and UI State
I use MVVM architecture with Room DB and LiveData in my project.
When the application works offline, the data is received from roomdb, and when the user is connected to the Internet, when entering the application, the data is received as Livedata from roomdb and a request is sent to the server to update the Room DB and update the observed list with Livedata.
I did the update as follows in the repository constructor:
public MainRepository(Application application) {
DatabaseManager databaseManager = DatabaseManager.getInstance(application);
dataDao = databaseManager.dataDao();
context = application.getApplicationContext();
getDataFromWebService();
}
Inside the getDataFromWebService() function, I retrofit the data from the server and place it asynctask on the server with OnConflictStrategy.REPLACE.
Now when the app opens, I receive and display the data with the observer.
My problem is that in this case, the data is fetched twice, but when I remove the getDataFromWebService() function from the code above, it fetches only once correctly.
Twice fetching the list has exactly the same data but the list is updated again and the animation corrupts the display of data.
Please help me how to update the database without fetching the list twice.
Is updating the database in the repository constructor correct? If not, then where should the update be done?
I use Android Architecture Components to build my app. There is Paging Library to load items with Room generated DataSource. Also there is BoundaryCallback to get new data from server and store it in the database. It works fine, all is reactive, changes in the database come into PagedList.
But now I need to these items get some additional data, some calculations before they come into PagesList and RecyclerView. These calculations is not so fast to executing them on main thread in RecyclerView ViewHolder (actually I need to get additional data from the database or even from the server). So I supposed that I need to write my custom DataSource and make calculations there and then pass these processed items to PagedList.
I created my ItemKeyedDataSource (I'm not sure this is correct, because I load data from database, but this data source type is designed for network, but I don't think this is critical), and make queries in Dao that return List of items. After I got a "page", I make calculations to items and then pass it to callback. It works, PagedList gets processed items.
But unfortunately there is no reactivity with this approach. No changes in database come to my PagedList. I tried to return LiveData<List> from Dao and add observeForever() listener in DataSource but it fails since you can't run it on background thread.
I watched Room generated DataSource.Factory and LimitOffsetDataSource but it doesn't look good to me since you need to pass table names to observe changes and other unclear things.
I suppose that I need to use invalidate(), but I don't because I have no idea where it should be.
There is 3 main questions:
Is it right to process items in DataSource before they come to RecyclerView or there is a better place?
Should I use PositionalDataSource instead of ItemKeyedDataSource?
How can I add Room reactivity to custom DataSource?
It seems that I've found a mistake in my DataSource.Factory. Instead of creating DataSource object in create() method I just returned object which was passed to that factory (I saw it in one popular article on Medium). And because of that I couldn't invalidate my DataSource. But now I create DataSource in that method and invalidation works.
The only problem is to understand where and when to invalidate. For now I've found some workaround: make a query in Dao that returns LiveData of last item, and then observe it in my Activity to understand that data was modified and call invalidate(). But I'm not sure this is a good solution. Maybe you know a better one.
You may add invalidationTracker in your DataSource:
dbRoom.getInvalidationTracker().addObserver(
object : InvalidationTracker.Observer("your_table") {
override fun onInvalidated(#NonNull tables: Set<String>) {
invalidate()
}
})
so I'm coming from an MVP background...
What I'm basically trying to do is start a loadingView as soon as we start fetching the data from Room (SQLite), stop the loadingView when successful and all of that logic should be handled in my ViewModel (trying to keep my fragment clean) class for the Fragment.
What I've done right now is that I've got two LiveData's:
My actual data that comes from the DB
A livedata for the state of the fragment:
Here's what I mean:
enum HomeState{
LOADING,
LIVE
}
private LiveData<List<SomeData>> someData;
private MutableLiveData<HomeState> homeState;
I'm observing both in my fragment and I want to have my homeStateLiveData determine whether the fragment should be displaying a loading view.. As you can probably see, this won't work as when the new data comes it immediately goes to the fragment and I can't control the homeState logic from the ViewModel
As you can probably see, this won't work as when the new data comes it
immediately goes to the fragment and I can't control the homeState
logic from the ViewModel
You can control the homeState based on the database LiveData by putting yourself between the fragment's observer and the database's LiveData. The way you would do this would be either through a Transformation or through a MediatorLiveData.
// with a Transformation
// this would be the method which returns the database LiveData
public LiveData<List<SomeData>> getDatabaseData() {
// the view should show a loading indicator
homeState.setValue(HomeState.LOADING);
// we don't actually map anything, we just use the map function to get
// a callback of when the database's LiveData has finished loading
return Transformations.map(dao.getSomeData(), list -> {
// the database has just finished fetching the data from the database
// and after this method returns it will be available to the observer
// in the fragment.
// we also need to dismiss the loading indicator
homeState.setValue(HomeState.LIVE);
return list;
});
}
With a MediatorLiveData you would do something similar, just make the MediatorLiveData listen for the database LiveData and update the homeState in the observer it sets when you add the database LiveData as its source.
If you want to abstract this, you could wrap the data you get from the database and the state(loading or available) into a single class and change your ViewModel to only return a LiveData with that class. The architecture components guide has an example(kind of related) on how you may do this, there they monitor the status of the network but you could easily adapt this to your database scenario.
I am using loading state in mvvm, rx, kotlin, retorfit for recyclerview.
Here is my actual loading state.
Here is my binding adapter for observe loading state.
Here is my extended recyclerview class for loading state and empty view.
Here is my xml file for bind loading state.
Maybe you can get inspiration from my example.
I using MVVM on my Android application, on ViewModel I have many observers (from data binding) like ObservableBoolean, ObservableField, I read that I can use LiveData/MutableLiveData instead this observers... What's the difference? I can replace all my data binding observers by LiveData/MutableLiveData?
eg:
replace:
val loading: ObservableBoolean = ObservableBoolean()
By:
val loading: MutableLiveData<Boolean> = MutableLiveData()
Many times has passed and I learned a lot...
Replace all Data Binding Observable by LiveData, because LiveData respect the Activity lifecycle and can be used in JetPack lib's, like Room, Coroutine...
Depends on where you are reading the data from .
In our current project ,we read directly from RoomDB . RoomDB has the capability to send back a liveData object .
Through your ViewModel , you do a query to RoomDB which returns a LiveData (RoomDB will be your Single Source of Truth)
Your View say an Activity or Fragment - Subscribes to this viewmodel as an observer
And you update the view as per the response returned .
You could also directly bind the xml through Android Databinding(Using LiveData with Data Binding)
Mutable Data is used normally if you have any modifications after retrieving
This is a good Place to Start
If your aim is to just change basic properties of views in xml based on a change with the data in primitive data type in view model, it is simple and easy to use Data binding. For rest of the cases, live data is the only way.