I'm making a function that opens a dialog when I touch the recyclerView item. I put viewModel in adapter and onClick function in viewHolder to open dialog with viewModel.
It looks like this.
Adapter(viewModel) // Initialize adapter in Activity or ViewModel
↓
class Adapter(viewModel: ViewModel) : ViewModel() {
inner class ViewHolder() {
fun onClick(binding: RecyclerViewItemBinding) : RecyclerView.ViewHolder(binding.root) {
Dialog(viewModel) // Open Dialog with viewModel
}
}
}
Can I use ViewModel in adapter or Dialog? If can't, what should I do? I need to change the data of the ViewModel by receiving the changed value from the Dialog.
ViewModels should be limited to activities/fragments, avoid passing it around to whereever you like it. Use liveData to pass events around, for example to pass click events interceptable in the adapter to the activity/fragment to which the viewModel is attached to. Also use liveData to notify your adapter about data changes that occur inside the viewModel. I have created a simple project that reflects your desired outcome, please have a look:
https://github.com/phamtdat/ViewModelForAdapterDemo
key points:
data are handled in the viewModel only
notify the data changes using liveData
update adapter on data changes
intercept click events in viewHolder, and forward it to adapter
forward click events from adapter to activity that the viewModel is attached to
show dialog with corresponding data on click event
change the data in dialog logic
the result should be: updated recyclerView displaying new data
This way you have clear separation of concerns:
viewModel - handles only data
activity - handles only UI events (in our case the click events of items and showing the dialog)
adapter - handles only correct rendering of item UIs
For simplicity, I didn't use dataBinding, but of course I would if I had time, that way you don't have to update the UI inside the viewHolder, but just pass the model to the binding.
Related
I have multiple Activities with the same CardView. To inflate this CardView I reuse the ViewHolder in my adapter. Now I'm trying to handle a click on the ViewHolder with setOnClickListener() and I need to save the Model represented by CardView into the database using a Repository. So for MVVM, the repository can communicate only with ViewModels and Databases or other resources, but every Activity has a different ViewModel so I couldn't pass the ViewModel to the Adapter to update the Model into the Database.
So I'm thinking that the adapter that I reuse, needs only one ViewModel for the database actions indifferently from the Activity that uses it.
So I need a static method in the main ViewModel that saves that Model.
This approach is correct for the MVVM pattern?
I don't know if I understand your question correctly but I guess that you want to somehow send a data from recyclerview adapter to your database.
"so I couldn't pass the ViewModel to the Adapter to update the Model into the Database"
You should never pass any viewModel to the adapter class because you will tightly couple them together and they wouldn't be reusable anymore.
In your case you should send data from adapter class and receive it in activity. Then activity should call viewModel to update specific data and viewModel will then call repository which will access database and save data.
It sounds complicated but it is well organised and you can reuse all components in many activities/fragments because they are not depending on each other. Adapter sends events that are received by your activity but the adapter does not know (and shouldn't know) which activity will receive the data. If you would pass viewModel as a parameter to the adapter class then it couldn't be used in activities that have different viewModel.
P.S Try to not use activity for all your views, in Android we have a Fragment component which is very useful in that case.
Useful links
fragments https://developer.android.com/guide/fragments
MVVM https://proandroiddev.com/understanding-mvvm-pattern-for-android-in-2021-98b155b37b54
I have two Fragment-s with RecyclerView and LiveData both. When I made some changes in second fragment, I need to update RecyclerView in first fragment.
So after update second fragment I need to trigger onChange method in first fragment.
Code from first fragment:
item_viewmodel.getAllCategoryModel().observe(getViewLifecycleOwner(), new Observer<List<Items>>() {
#Override
public void onChanged(List<Items> myLists) {
//Observer is already registered, but I need to call it manully from second fragment.
}
});
How can I do this?
You can achieve this by using Shared viewmodel, create a instance of viewmodel in both the fragments and use accordingly, for more details visit this page link
You should try use Transformations.switchMap() method, put that the first livedata depends of the second livedata by the method describe above
Here is an example class that holds a typed-in name of a user String (such as from an EditText) in a MutableLiveData and returns a LiveData containing a List of User objects for users that have that name. It populates that LiveData by requerying a repository-pattern object each time the typed name changes.
This ViewModel would permit the observing UI to update "live" as the user ID text changes.
class UserViewModel extends AndroidViewModel {
MutableLiveData<String> nameQueryLiveData = ...
LiveData<List<String>> getUsersWithNameLiveData() {
return Transformations.switchMap(
nameQueryLiveData,
name -> myDataSource.getUsersWithNameLiveData(name));
}
void setNameQuery(String name) {
this.nameQueryLiveData.setValue(name);
}
}
Here more about this...
If you want more specific answer please post all code about your viewModel
I assume best way would be to use by activityViewModels() kotlin property delegate in fragments and share the host activity's Viewmodel for cross fragment communication.
More about usage here
My question is kind of not strictly precised. I have a Fragment with list of items that are backed up by REST API service:
GET /api/items
I have a ItemsViewModel class, ItemsProvider with LiveData objects and APIService that get the items from REST service. The implementation of ListFragment observes the ItemsViewModel and its state.
Now I have a question. How do you implement inserting data into ViewModel in your implementations? How do you handle state update?
You can take LiveData<List<? of items>> as MutableLiveData inside ItemsViewModel.
Now, when you've new items, you need to set your LiveData value.
You need to create an observer to that inside of your activity/fragment where you needed.
I'm started to implement MVVM and I do not know if use the ViewModel inside my Adapter is a Good Practice or an AntiPattern.
To give you an example, think about a list of colors and an Image that you will change the background. In that case, I'm observing the selected Color and binding the background color to display in the UI.
But I'm observing the colorSelected in two places:
In the Fragment in order to change the background color
In the Adapter to display which color is selected
So, Am I using MVVM properly when I passing and observing the ViewModel to the Adapter?
I am not sure now if it is good pattern, but there is one problem with this:
this way your adapter is tied to your ViewModel meaning you can't reuse it with other ViewModel on another screen. On another hand it is much easier to work without those additional callbacks from recyclerView which makes implementation easier. I guess it is "better" to use callbacks, but "easier" to pass ViewModel (I think CG should deal with it without a problem)
Well, I looked for the solution for this question, but finally have to handle it myself. I would like to post my thought here for discussion.
Simply put, passing a viewModel instance to adapter is not elegant, in my opinion. Instead, I recommend you override the "onAttachedToRecyclerView" method of your RecyclerViewAdapter to get your viewModel:
public class YoursAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
......
private YoursViewModel viewModel;
......
#Override
public void onAttachedToRecyclerView(#NonNull RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
if(viewModel==null){
viewModel=new ViewModelProvider((ViewModelStoreOwner) recyclerView.getContext()).get(YoursViewModel.class);
}
}
......
}
Why I think this solution is good?
1、ViewModel itself has nothing to do with contexts. But creating an instance of ViewModel needs a context.
2、Which context is your choice? The recyclerView's context, since you want to do something inside the adapter, and the adapter is associated with a recyclerView. Why I override the "onAttachedToRecyclerView"? Because this callback provides a recyclerView refference, and only executes once.
3、As long as the recyclerView's context is alive, the ViewModel is alive. Is it what you want? If the answer is "Yes", then it is reasonable to create your ViewModel instance based on the recyclerView's context.
4、You have had a ViewModel in Fragment. Is it reasonable to create a new one in adapter? Yes. One of the advantages of ViewModel is sharing data, as long as they are in the same activity.
5、You actually don't need to observe a livedata inside an adapter (which needs context again), if you have properly designed the ViewModel. Basically you just observe a livedata on fragment. Your ViewModel inside an adapter is just responsible to change the data, not caring about how the data changed, i.e., do yourViewModelInstance.post_value_to_liveData_and return_it().observe() on fragment, and do yourViewModelInstance.post_value_to_liveData_and return_it() inside adapter.
I'm creating a sample app (Last-Mvvm) to learn (and possibly show) the usage of mvvm pattern, using Android data binding.
I have an activity with my ViewModel object inside. I have also a RecyclerView adapter, which contains an arraylist of items that are converted to another Viewmodel. I want to save the state of the list inside the adapter (for rotation changes).
So: where should I save it? inside the activity? Or in the viewModel of the activity? Or somewhere else?
Also, there is another thing that isn't really clear.
Is it fine to perform rest calls (through Retrofit) or database calls directly from the viewModel (since i'd use interfaces), or would it be better to make an interface that the view (activity) implements and performs all the calls?
I will try to explain
First. So: where should I save it? inside the activity? Or in the viewModel of the activity? Or somewhere else?
and
Second. Is it fine to perform rest calls (through Retrofit) or database calls directly from the viewModel (since i'd use interfaces), or would it be better to make an interface that the view (activity) implements and performs all the calls?
on example from article.
On the schema below you can see implementation of OnClickListener and OnLongClickListener for RecyclerView item.
doted lines is a links
solid lines is a method calls
How it works
ViewHolderWrapper works as ClickListener for root view of ViewHolder. Depending on implementation ViewHolderWrapper can be proxy for HolderClickObservable or depending on SelectionHelper, ViewHolderWrapper can manually highlight selected item.
SelectionHelper is responsible for saving selection state and notify SelectionObserver about changes.
Listener (Adapter in this case) is responsible for highlighting of selected items and for updates.
Summarizing
First. You can restore adapter state via activity with methods onSaveInstanceState() and onRestoreInstanceState().
Second. You need to create lightweight ViewHolder which only responsible for binding data to view. Actions can be performed at adapter or activity.
See also example application