How to show LoadingState when using Room Livedata MVVM - android

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.

Related

How to update a row in room? (please read first)

So I observe my room db in recyclerview via viewmodel. To update a row I call update method on repository object in activity. Am I doing it right? Because this is what I have seen in tutorials. My question is if we are using repository object for update, create and viewmodel to read data, whats the use case for setdata and postdata methods of livedata? Also how to update an entire table(overwrite)?
Observing
noteViewModel.getAllNotes().observe(this, Observer<List<Note>> { notes ->
notes?.let {
notesList = notes as ArrayList<Note>
notesAdapter = NotesAdapter(notes, this#MainActivity)
recyclerView.adapter = notesAdapter
notesAdapter!!.notifyDataSetChanged()
}
})
Updating
NoteDatabase.getInstance(this#MainActivity).noteDao().updateNote(
notechecked.also { it.done = value }
Your code is fine. This is exactly how your'e supposed to observe and update rows in Room.
The use case for setData and postData methods of LiveData are.. well.. to update the LiveData object.
For example, Room uses postData() to update the LiveData object it initially returned (when you called getAllNotes()), whenever you update your DB in a way that affects your LiveData object.
Because Room calls postData() for you, you don't need to.
But, if you were to hold some value directly in your viewmodel (rather than in Room), inside a LiveData object, and you'd want to change it when the user clicks something in your view, you'd have to call setData/postData yourself.

android Room livedata once

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?

How to use ViewModel in Android with LiveData from repository (DB/network)?

I have Fragment/Activity that needs to display some data.
The data are coming from web API (or can come from DB).
I have Repository class that exposes this data as LiveData making request to web API or DB.
Now, I need to implement ViewModel with LiveData and consider how I should do this correctly. I have try to resemble this example:
https://github.com/googlesamples/android-architecture-components/blob/master/GithubBrowserSample/app/src/main/java/com/android/example/github/ui/repo/RepoViewModel.kt
But this example is very specific. As I understand it I have:
repoId -> which I need to set from VIEW using setId() at the start
of Activity/Fragment
then I can refresh data in view model's LiveData using retry() which only resets repoId with the same value
repo, contributions - real observable LiveData are making request by repository to Web API each time repoId is set using
Transofrmations.switchMap()
So: In Activity I need to:
viewModel.repo.observe(), viewModel.contributions.observe()
viewModel.setId() - to get initial data loading
viewModel.retry() - each time I want to refresh data in VIEW
Now in my Activity I need to display First Name/Last Name in Navigation Drawer.
1. I need to load this data on Activity create
2. I need to refresh this data each time Drawer is opened or maybe on Activity Restart (from background)
3. I have UserProfileRepository.getUserProfile() -> returning LiveData
Here is my problem! this request to web api doesn't need any ID as it just takes user profile based on current authentication token in header (this is passed to HTTP request headers using interceptor)
So I don't have any repoId or userId (in my case) to make it MutableLiveData and then make other properties on view model using Transformations.switchMap.
I have tried to directly return LiveData with:
fun getUserProfile() : LiveData<UserProfile> {
return repository.getUserProfile()
}
But here was problem with refreshing View as future calls to getUserProfile() just returns new Observable LiveData, instead notifying previous LiveData observer.
I think I could use some MediatorLiveData and local LiveData property in ViewModel but this seems a little overcomplicated.
So I conceived such approach (using fake refresher private property of type LiveData<Int>). It function as a trigger for Transformations.switchMap() blocks to execute each time I want to refresh userProfile (or enforce repository to make web api request, return new LiveData<T> and as I understand place this new data in ViewModel property userProfile live data. This way my Activity's Observer will be notified with actual web api data each time I call refresh() method.
class UserProfileViewModel
#Inject constructor(private val userProfileRepo: UserProfileRepository) : ViewModel() {
private val refresher : MutableLiveData<Int> = MutableLiveData()
val userProfile : LiveData<Resource<UserProfile>> = Transformations.switchMap(refresher) { refreshCount ->
userProfileRepo.getUserProfile()
}
fun refresh() {
refresher.value = (refresher.value ?: 0) + 1
}
}
Is this approach correct way to solve this problem? Or should I implement it in other way.

android MVVM without Databinding

Question : Can I implement android app with MVVM without using Databinding.
Problem I am trying to solve is pretty simple:
read a list of items from backend API and show them in a Recylerview.
How I am implementing:
In the View - I have Activity and RecyclerViewAdapter
Model : ApiResponse and data models
network - retrofit API service, RxJava2
for ViewModel part - I have a ViewModel class(that doesn't derive from anything) that basically calls Retrofit Service and gets data using RxJava calls.
ViewModel has calls such as :
void getItems();
void addItemData();
void removeItem();
which call service with RXJava2 as
ObServable<ApiResponse> getItems();
ObServable<ApiResponse> addItemData();
ObServable<ApiResponse> removeItem();
View instantiates ViewModel object.
ViewModel gets the instance of Adapter object during creation.
In the View, clicking a button calls a ClickHandler in the Activity which calls a ViewModel#getItems() method. Since ViewModel has link to Adapter, the viewModel updates the items in the adapter so that RecyclerView is automatically updated.
I am not sure if this is right approach for MVVM.
Databinding seems a bit like spaghetti to me.
Again, can we implement MVVM in android without DataBinding ?
Is the approach OK?
Yes! You can. But i think your approach can be better.
Remember that the view model must not have a reference to your view.
ViewModel expose observables, and in your view, you should observe those observables and react over changes.
You can have something like this:
Note: This example is with Kotlin and LiveData because, why not? But you can take this and use it with Java & Rx
ItemsViewModel : ViewModel() {
private val items = MutableLiveData<List<Items>>()
fun getAllItems() : LiveData<List<Items>> {
return items
}
//..
}
ItemsActivity : Activity() {
private var itemsAdapter: ItemsAdapter? = null
private var viewModel: ItemsViewModel? = null
override fun onCreate(savedInstance: Bundle) {
// ...
// Create your Adapter
itemsAdapter = ItemsAdapter()
recyclerView.adapter = itemsAdapter
// Create and observe your view model
viewModel = ViewModelProviders.of(this).get(ItemsViewModel::class.java)
viewModel.getAllItems().observe(this, Observer {
it?.let {
adapter?.datasource = it
}
}
In this case, the view observes view model, and notify the adapter. Then in your adapter, you do the bind as usual, without databinding.
Definitely possible, it's totally up to you how you interpret the "binding" part of MVVM. In our team, we use MVVM with RxJava instead of Android Data Binding.
Your ViewModel has an interface with outputs and inputs like this:
interface TasksViewModel {
// inputs
Observer<Task> taskAddedTrigger();
Observer<Task> taskClickedTrigger();
Observer<Task> taskCompletedTrigger();
// outputs
Observable<Boolean> isLoading();
Observable<List<Task>> tasks();
}
Your ViewModel then just uses RxJava to map inputs to outputs in a very functional style.
You Fragment supplies Inputs to the ViewModel whenever User input is received. It subscribes to Outputs and updates the user interface accordingly when the ViewModel's Output changes.
Here is a blog post which covers the topic in detail (Disclaimer: I wrote it)
The distinguishing characteristic of MVVM is that the ViewModel is not directly coupled to a View (indeed, you could bind your ViewModel to different layouts). This also has implications on the ease of unit testing. By having a reference to the Adapter, it is technically more like MVC. You don't have to use databinding, but for true MVVM, I think you would need another Observer Pattern mechanism for the View to be notified of changes so that it could pull the data it needs.
Your saying Since ViewModel has a link to Adapter and that is the problem because ViewModel should not have reference to view and In your adapter, you have views so by doing this your not following MVVM at all!!
You can still use MVVM without data binding but you need some way to notify the view about data changes, It can be LiveData (preferred way), Java Observable, Rx or even a custom implementation. The view will get notified about the changes and updates itself, in your case, view will update the adapter.
see my answer here for an example Are actions allowed in MVVM? Android
I think you should use data binding to notify the data changed from network or database, your viewmodel should expose methods for requiring or updating data, when the data arrived you can do some operation on your data, and post them to your container(activity or fragment), in there you can update your RecyclerView and its adapter

How do I pass SQLite Cursor from MVP Model to View without breaking Presenter?

I have an app implementation using the MVP design, and I want to use a SQLlite database to source data data to the UI View. I don't want/need to use a ContentProvider as the data is only required by my app alone.
I know that the View should be 'dumb' and any 'business' logic should go in the Presenter and the Model. Yet the Presenter should contain no Android code in order to allow for standalone unit testing.
So how do I perform the transportation of SQLite data from the Model back to the Presenter and then the View if I can't use a cursor in the Presenter?
I can use a callback but is the 'correct' way to create my own data structure, load the data into that and then pass a list in the callback?
I'd like to stay true to the MVP design.
I've Googled this to death and whilst there's plenty of stuff on handling the Context in the Presenter there's not a lot about using other Android data structures.
You shouldn't have SQLite access in the view even in presenter.
All accesses to model are made in model layer, so if you need to interact with that cursor from the view / presenter you will have to create methods to call it:
From View call a presenter method -> From Presenter call model method -> From model call its cursor interaction method.
And viceversa.
So... what you need should be something like this:
View notifies to some presenter method that want some data (presenter.loadInfo(...)) -> Presenter calls the model (UseCase, a model class, etc.) [model.loadData(...)] -> Model has an attribute or gateway to database, so it asks for a query and, once it has de info, it returns to the presenter.
It's expected to be asynchronous, so create a callback and call it from presenter:
model.loadInfo(..., new onResponseCallback() {
method onResponse(MyResponseClass response) {
//Manage your response as you want
view.showData(...)
}
method onFailure(MyFailureClass failure) {
view.showFailure...
}
});

Categories

Resources