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
Related
For eg, As ViewModel should be loosely coupled we cant pass interface reference to ViewModel to get a callback from Viewmodel and call the method implemented in the fragment.
Also please provide an example or reference to call with methods with two or more params using livedata.
Make a live data variable in ViewModel and attach an observer in Fragment to that variable and whenever there will be a change in data then the function will be automatically invoked.
For Example:
viewModel.loading.observe(viewLifecycleOwner, { loading ->
loading?.let {
// Here, I'm calling a new function named setLoaderVisibility
setLoaderVisibility(loader = binding.shimmerProductDetail, binding.containerBody, isLoaderVisible = loading)
}
})
Feel free to ask if something is unclear.
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.
I have two fragment that should share a viemodel
class FragmentA(): Fragment() {
val sharedViewModel = ViewModelProvider.(need_a_shared_context)....//not the activity context
}
class FragmentB(): Fragment() {
val sharedViewModel = ViewModelProvider.(need_a_shared_context).... //not the activity context
}
now, inside each fragment I need to access a shared viewmodel but not a sharedviewmodel hosted by the activity containing these fragments, because I'm working with Firebase and I have a listener sending data to my app.
So, If I attach the viewmodel to the activity lifecycle, I can share data between these two, but, it will be also listening for data when I'm not in FragmentA() or FragmentB()
Is there a way to scope the creation of this sharedviewmodel when I'm only at FragmentA() or FragmentB() ?
Edit
Since I'm using NavigationComponents, when I navigate from FragmentA() to FragmentB() , fragment A dies, so , If I create there my sharedviewmodel, it will die when I access FragmentB() and FragmentB() will generate a new instance of the viewmodel.
The question is kinda too wide.
Why don't u want the activity to host the viewmodel? If you dont want your listeners to be notified while not actually displaying the UI, you can remove it [listeners]. I do not know exactly about Realtime Database (sure it has same features), but Firestore's addSnapshotListener methods return ListenerRegistration object, which has a remove() method to remove the listener, so you are not notified about the document updates and thus not charged for it.
You can host the ViewModel by any component you want, but I would even consider using Object for it, so every instance of your ViewModel gets the same singleton, which stores the cached value and exposes it in the form of LiveData to your observers, but actually starts and stops observing the Firebase node (and updating the LiveData your fragments observe) only after specific methods get called - onStartObserving()/onStopObserving, which can be called from your fragments' onStart() & onStop()
One promise of the ViewModel is, that it survives cases like rotation of the screen. I still try to figure out how to organise this in practice.
On certain events of the model the View should update. There are two major options:
The ViewModel updates the View.
The View observes the ViewModel and updates itself.
In the first case the ViewModel needs a link to the View. I could inject the View into the ViewModel, yet my feeling is it would be better to inject the VieModel into the View.
What is the better style to join them?
Then after rotation the onCreate() method is called again triggering initialisations of the ViewModel a second time. I need to check for this else I am in danger to register listeners to the actual model twice and thrice and similar issues. I may even need to clean up relations to the old view first.
This checking feels kind of unclean. I would expect a dedicated API for this in the ViewModel, if this would be a standard practice. Without I have the feeling to be on the wrong track.
What are good patterns to deal with this in a clean standard way?
Soo.. You don't really have to connect the ViewModel and the Activity/Fragment "with respect to screen rotation", you get that for free - that's one of the perks.
The official documentation is really good.
You connect a ViewModel to your view in onCreate() by something like
public class MyActivity extends AppCompatActivity {
public void onCreate(Bundle savedInstanceState) {
MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
model.getUsers().observe(this, users -> {
updateUI()
});
}
}
And while it is true as you say that orientation change will trigger onCreate() again, it's not true that this will create a new ViewModel. The MyViewModel is only created the first time around in onCreate. Re-created activities receive the same MyViewModel instance created by the first activity. This is even true for different fragments/activities referencing the same ViewModel.
You should never ever inject a view into the ViewModel. It's the equivalent of drowning puppies. If you need a context in the ViewModel, extend AndroidViewModel instead (and pass it the Application).
What you do is that you create a ViewModel that holds all state. And handles fetching data from network or disk or what not. All that is not UI related goes in to the ViewModel (as a rule of thumb). All view updating stuff goes into the activity/fragment.
A ViewModel for the example above might look like
public class MyViewModel extends ViewModel {
private MutableLiveData<List<User>> users;
public LiveData<List<User>> getUsers() {
if (users == null) {
users = new MutableLiveData<List<Users>>();
loadUsers();
}
return users;
}
private void loadUsers() {
// Do an asynchronous operation to fetch users.
}
}
This often means that click events should probably be passed down into the ViewModel so that it can massage the data. And the view will just react to the updated (massaged) data.
I have a ViewModel named SharedViewModel:
public class SharedViewModel<T> extends ViewModel {
private final MutableLiveData<T> selected = new MutableLiveData<>();
public void select(T item) {
selected.setValue(item);
}
public LiveData<T> getSelected() {
return selected;
}
}
I've implemented it based on SharedViewModel example on the Google's Arch ViewModel reference page:
https://developer.android.com/topic/libraries/architecture/viewmodel.html#sharing_data_between_fragments
It is very common that two or more fragments in an activity need to communicate with each other. This is never trivial as both
fragments need to define some interface description and the owner
activity must bind the two together. Moreover, both fragments must
handle the case where the other fragment is not yet created or not
visible.
I have two fragments, called ListFragment and DetailFragment.
Until now I used these two fragments inside an activity called MasterActivity, and everything worked well.
I got the ViewModel in ListFragment, selected the value to use it on DetailFragment.
mStepSelectorViewModel = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
However, now, in certain cases, I need that ListFragment (a layout to a different device configuration) will be added to a different activity, called DetailActivity. Is there a way to do that similarly to the above example?
A little late but you can accomplish this using a shared ViewModelStore. Fragments and activities implement the ViewModelStoreOwner interface. In those cases fragments have a store per instance and activities save it in a static member (I guess so it can survive configuration changes).
Getting back to the shared ViewModelStore, let say for example that you want it to be your Application instance. You need your application to implement ViewModelStoreOwner.
class MyApp: Application(), ViewModelStoreOwner {
private val appViewModelStore: ViewModelStore by lazy {
ViewModelStore()
}
override fun getViewModelStore(): ViewModelStore {
return appViewModelStore
}
}
Then in the cases when you know that you need to share ViewModels between activity boundaries you do something like this.
val viewModel = ViewModelProvider(myApp, viewModelFactory).get(CustomViewModel::class.java)
So now it will use the Store defined in your app. That way you can share ViewModels.
Very important. Because in this example the ViewModels live in your application instance they won't be destroyed when the fragment/activity that uses them gets destroyed. So you will have to link them to the lifecycle of the last fragment/activity that will use them, or manually destroy them.
Well, I created a library for this purpose named Vita, You can share ViewModels between activities and even fragments with different host activity:
val myViewModel = vita.with(VitaOwner.Multiple(this)).getViewModel<MyViewModel>()
The created ViewModel in this way stay alive until its last LifeCycleOwner is destroyed.
Also you can create ViewModels with application scope:
val myViewModel = vita.with(VitaOwner.None).getViewModel<MyViewModel>()
And this type of ViewModel will be cleared when user closes app
Give it a try and kindly let me know your feedback:
https://github.com/FarshadTahmasbi/Vita
you can use factory to make viewmodel and this factor will return single object of view model.. As:
class ViewModelFactory() : ViewModelProvider.Factory {
override fun create(modelClass: Class): T {
if (modelClass.isAssignableFrom(UserProfileViewModel::class.java)) {
val key = "UserProfileViewModel"
if(hashMapViewModel.containsKey(key)){
return getViewModel(key) as T
} else {
addViewModel(key, UserProfileViewModel())
return getViewModel(key) as T
}
}
throw IllegalArgumentException("Unknown ViewModel class")
}
companion object {
val hashMapViewModel = HashMap<String, ViewModel>()
fun addViewModel(key: String, viewModel: ViewModel){
hashMapViewModel.put(key, viewModel)
}
fun getViewModel(key: String): ViewModel? {
return hashMapViewModel[key]
}
}
}
In Activity:
viewModelFactory = Injection.provideViewModelFactory(this)
// Initialize Product View Model
userViewModel = ViewModelProviders.of(this, viewModelFactory).get(
UserProfileViewModel::class.java)`
This will provide only single object of UserProfileViewModel which you can share between Activities.
I think we still get confused with the MVVM framework on Android.
For another activity, do not get confused because it must necessarily be the same, why?
This makes sense if it has the same logic (even if the logic could still be abstract in other useful classes), or if the view in the XML is almost identical.
Let's take a quick example:
I create a ViewModel called vmA, and an activity called A and I need the user's data, I will go to insert the repository in vmA of the User.
Now, I need another activity that needs to read user data,
I create another ViewModel called vmB and in it I will call the user repository.
As described, the repository is always the same.
Another way already suggested is to create N instances of the same ViewModel with the implementation of the Factory.
If you want a ViewModel that is shared by all your activities (as opposed to some),
then why not store what you want stored in that ViewModel
inside your Application class?
The trend presented at the last Google I/O seems to be to abandon the concept of Activities in favor of single-activity apps that have a lot of Fragments.
ViewModels are the way to remove the great number of interfaces the activity of an interface formerly had to implement.
Thus this aproach no longer makes for giant and unmaintainable activities.
Here's a link
Hope it helps you. O(∩_∩)O~
In addition:
1) The inspiration for the code came from smart pointer in c++.
2) It will be auto cleared when no activities or fragments references ShareViewModel.
The ShareViewModel # onShareCleared() function will be called at the same time!
You don't need to destroy them manually!
3) If you use dagger2 to inject the ViewModelFactory for share the viewmodel
between two activities (maybe three), Here's sample