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()
Related
I'm using databinding, the Jetpack Navigation Component (in which one activity hosts several fragments) and a shared ViewModel with activityViewModels() in the fragments. The Room database is initialized (and some resource expensive queries) when a ViewModel function is called from the StartFragment (hosted by the MainActivity). The host activity is created twice (and destroyed once), causing the fragment to do the same and duplicate Room queries.
ViewModel varialble in the Fragment:
private val viewModel: WorkoutListViewModel by activityViewModels()
ViewModel function:
if (applicationStarted) {
roomDb = WorkoutsRoomDatabase.getInstance(application)
repository = WorkoutsRepository(roomDb)
collectAllWorkouts() // Expensive database query
insertWorkoutGroup(WorkoutGroup(FIRST_TAB_TITLE), null)
}
I know the activity, fragment creation and Room queries happen twice because I used logs to test it.
I suspect the cause is related to databinding, the navigation component, or the way the shared ViewModel with activityViewModels().
Even though there's better ways to initialize the database and start queries, I appreciate an input on this too but my question is specifically about the host activity being created twice.
edit: The activity it created -> destroyed -> created
The bundle is null at first and the second time it is:
Bundle[{android:viewHierarchyState=Bundle[{android:views={16908290=android.view.AbsSavedState$1#cd936b5, 2131230775=android.view.AbsSavedState$1#cd936b5, 2131230790=android.view.AbsSavedState$1#cd936b5, 2131231216=android.view.AbsSavedState$1#cd936b5}}], androidx.lifecycle.BundlableSavedStateRegistry.key=Bundle[{android:support:lifecycle=Bundle[{}], androidx.lifecycle.internal.SavedStateHandlesProvider=Bundle[{}]}], android:lastAutofillId=1073741824, android:fragments=android.app.FragmentManagerState#c25a14a}]
I have a restaurant app with two main fragments each with their own viewmodels -
LiveList fragment & viewmodel that retrieves a list of restaurants from the internet and displays them in a recyclerView.
SavedList fragment & viewmodel that displays the saved restaurants from a database and deals with all the database interactions
What I want to do is when the user clicks the star next to each restaurant in the LiveList for that restaurant to be added to my SavedList database.
Can I:
1- simply call a reference of SavedListViewModel.addRestaurant from LiveListViewModel ?
2- need to change to a shared ViewModel approach?
3- make the addRestaurant an interface that the LiveList can access?
4- let SavedListViewModel observe a piece of Livedata from LiveListViewModel and tie that Livedata to the selected restaurant?
5- none of these approaches viable?
Make a shared view model in activity scope.
viewModels gives you the ViewModel instance scoped to the current fragment. This will be different for different fragments.
activityViewModels gives you the ViewModel instance scoped to the current activity. Therefore the instance will remain the same across multiple fragments in the same activity.
https://developer.android.com/codelabs/basic-android-kotlin-training-shared-viewmodel#4
My app has an activity on which many fragments are placed for different pages.
Some fragments have a recyclerView that data given via a viewModel.
Now, I would first create an instance of the viewModel in each fragment and put the data in the recyclerView using observer and LiveData, but this caused LiveData observer is being triggered two times.
After that, the idea came to me to create a static instance of the viewModel in the activity class to solve this problem and use it in different fragments. It worked, and LiveData observer triggered once.
Now my question is, is it right to use a static instance of viewModel in MainActivity (and use it from fragments)?
If not, then what should I do to solve the problem(Trigger the observer twice)?
How does the Android Viewmodel works internally?
How Viewmodel save the data when the activity gets destroyed and recreated when the device get rotated
View Model's Internal Wokring:
View Model:
View Model is a lifecycle awared class, designed to store and manage UI related data. It a main component in MVVM architecture.
When a view model is created, it is stored inside activity or fragment manager.
Benefits:
Lifecycle awared
Hold and share UI data
Survives in rotation and retains data
Here we can raise a question that how we can get same instance of view model when new instance of activity is created while rotating screen from portrait to landscape ?
Answer:
To create a viewmodel object, ViewModelProvider class is required.
ViewModelProvider is the utility class which is used to create the instance of viewmodel in the following way.
Create a ViewModelProvider instance
Get the desired viewmodel from the viewmodel provider object
Internally creation of ViewModelProvider required two parameters.
ViewModelStoreOwner: it is an interface.It has just one method which returns the
ViewModelStore.
Factory: It is a nested interface in the ViewModelProvider class and is used to manufacture viewmodel objects.
val viewModelProvider = ViewModelProvider(this)
val viewModelProvider2 = ViewModelProvider(this,viewModelFactory)
If the factory is not passed then the default factory is created. Custom factory can be created for the parameterized viewmodel.
So now we have instance of viewmodel provider,
Now let’s get our viewmodel object
val viewModelProvider = ViewModelProvider(this)
val viewModel = viewModelProvider.get(LoginViewModel::class.java)
As we can see, we simply just called the get(arg) method with the desired viewmodel class reference and our viewmodel object was created.
So all the magic happens in this get method
This method gets the canonical name of the viewmodel class,creates a key by appending DEFAULT_KEY then it calls the another get function which takes the key and viewmodel class reference
This method checks the viewmodel instance in the viewmodel store first.If the viewmodel instance is there in the viewmodelstore then it simply returns that instance .If viewmodel instance is not there then it uses the factory to create the new instance and saves that instance in viewmodel store and then it return the viewmodel instance.
This viewmodel store is linked to ViewModelStoreOwner so our activity/fragment has their own viewmodel store.
It is ViewModelStore which stores the viewmodel and is retained when the rotation occurs and which returns the same viewmodel instance in the new activity instance.
Interview Question : Viewmodel store is linked to activity / fragment and when in the process of rotation current instance is destroyed and new instance is created then how this ViewModelStore object is still the same?
Let’s know about this magic
ViewModelStoreOwner is an interface. ComponentActivity implements this interface.
In above implementation , we can see that in the new activity object
when viewmodel store is null then it first checks with the
NonConfigurationInstance which returns the previous activity’s
viewmodel store.
If activity is being created for the first time then always new
viewmodel store objects will be created.
So It is NonConfigurationInstance object which is passed from old destroyed activity to newly created activity when rotations happens.It contains all the non-configuration related information including viewmodel store which contains the viewmodel of old activity object.
Answer is inspired by This Link
How does the Android Viewmodel works internally?
Android's ViewModel is designed to store and manage UI-related data in such a way that it can survive configuration changes such as screen rotations.
ViewModel gets called by an activity that previously called it, it re-uses the instance of that ViewModel object. However, if the Activity gets destroyed or finished, counterpart ViewModel calls the onClear() method for clearing up resources. Meaning if you have added something like this to your ViewModel:
override fun onClear() {
super.onClear()
clearAllLiveDataValues()
disposeAllVariables()
}
Function calls added here will be invoked.
How Viewmodel save the data when the activity gets destroyed and recreated when the device get rotated
ViewModel has its own lifecycle that allows itself to recover its state, and the transient data it holds, during screen rotations.
NOTE: Activity and ViewModel's lifecycle are both ephemeral. Allowing the ViewModel to handle critical or sensitive data during configuration changes IS NOT RECOMMENDED.
Your application should use either shared prefs, secured storage (if necessary), local database or cloud storage when you are expected to handle critical or sensistive data in a specific screen or part of your app.
I recommend that you read the following:
https://developer.android.com/topic/libraries/architecture/viewmodel
https://android.jlelse.eu/android-architecture-components-a563027632ce
https://medium.com/androiddevelopers/viewmodels-persistence-onsaveinstancestate-restoring-ui-state-and-loaders-fc7cc4a6c090
I've been using MVP for a long time now and I'm starting to transfer to a hybrid state between MVP and MVVM
In detail my apps will go like this:
Each Activity has a 0 to x Fragments that represent its views
Each Fragment will request the Activity's ViewModel so that they can retrieve data using LiveData
The Activity will have a seperate ViewModel which will act as the presenter. On creation that ViewModel will be injected with the Activity's ViewModel with the LiveData so that it can update the UI as needed
The presenter will get the messages sent to the data ViewModel and send the results back to it
My questions:
Could holding a reference to the data ViewModel in the presenter ViewModel cause a memory leak or adverse effects such as memory leaks?
Where should business logic be? in the presenter or in the model part?
For example, let's say I have a list of items and the user long presses one to edit them, what part of this architecture should be responsible for checking if the user has permission to do this and either let them edit the item or show an error message?
Is there a way for the Fragments to only get part of the Activity's ViewModel?
For example , assuming the activity has 3 Fragments under it, and one ViewModel to cater to them
Can I use something like:
class MainViewModel : ViewModel() , IFragmentA, IFragmentB, IFragmentC
and then when I try to get the ViewModel in the fragments I can write something like:
lateinit var viewModel: IFragmentA
override fun onAttach(context: Context?) {
super.onAttach(context)
vm = ViewModelProviders.of(context as AppCompatActivity).get(IFragmentA::class.java)
}
note:I know the above code does not work , what I am asking is if there is a way for something similar to this could work
Is the correct way to send back messages to the activity SingleEvents?
For example, if the user tries to delete an entry , and I wish for them to enter a password, would the flow be:
The Fragment sends the message to delete to its ViewModel
The ViewModel passes it on to the Presenter
The Presenter decides that it needs password verification before moving on
The presenter sets the value of a SingleEvent in ViewModel
The ViewModel notifies the event's subscribers (in this case the MainActivity) that they should show a dialog asking for a password
Thank you for any help you can provide
I have recently ported one of my app from MVP to MVVM architecture. it doesn't matter whether you do it partially or completely, you are moving towards something great and clean and you are going to like it.
Before checking the answer please have a look at this MVVM architecture diagram and some of it's dos and don'ts
Let's look at the roles of each classes here.
Activity/Fragment:
-Listen to MutableLiveData Obeservers and set Data to the views, no other logics here.
ViewModel
user input Validations (username, password empty or null checks)
set your mutableLive
ask repository to start a task network or localdatastorage(sqlite), with callbacks.
Repository
cache required data.
should not hold any reference to ViewModel this will create a circular dependency.
Decides what to do - whether to make network call or load data from local storage. manipulation of the data received goes here(business logic).
use the callback received from ViewModel to update data to viewModel, strictly no direct communication.
RemoteDataSource
makes a network call and gives the data received back to the repository.
LocalDataSource
handles all SQLite related stuff and gives the requested data through callbacks.
there is a todo app sample project from google which uses MVVM. please refer it, it will be very helpful.
No presenter - check user inputs on viewmodel and communicate forth using repository and back using MutableLiveData.
Do your business logic in Repository, consider it more like a model in mvp pattern.
You can have single viewModel for your activity and its fragments. All your fragments communicate through one viewModel. So Each Fragment will only react to the LiveDataObserver it listens to.
There is actually an example of this use case in the Google sample project for MVVM.
AddEditTaskActivity.java
public static AddEditTaskViewModel obtainViewModel(FragmentActivity activity) {
// Use a Factory to inject dependencies into the ViewModel
ViewModelFactoryfactory= ViewModelFactory.getInstance(activity.getApplication());
return ViewModelProviders.of(activity, factory).get(AddEditTaskViewModel.class);
}
AddEditTaskFragment.java
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final View root = inflater.inflate(R.layout.addtask_frag, container, false);
if (mViewDataBinding == null) {
mViewDataBinding = AddtaskFragBinding.bind(root);
}
mViewModel = AddEditTaskActivity.obtainViewModel(getActivity());
mViewDataBinding.setViewmodel(mViewModel);
mViewDataBinding.setLifecycleOwner(getActivity());
setHasOptionsMenu(true);
setRetainInstance(false);
return mViewDataBinding.getRoot();
}
Password Verification Flow:
fragment ask the ViewModel to deleteEntry.
Ask repository to decide whether verification is necessary, with the data which we already have or communicating with the local data source.
ViewModel receives a callback from Repository saying verification needed, ViewModel updates the respective MutableLiveData showVerification.postValue(true);
As the activity is listening to the showVerificationObserver, it shows the verification UI.
Hope it helps.