Edit: This question is a bit out of date now that Google has given us the ability to scope ViewModel
to navigation graphs. The better approach (rather
If you don't want the ViewModel
to be scoped to the Activity
lifecycle, you can scope it to the parent fragment's lifecycle. So if you want to share an instance of the ViewModel
with multiple fragments in a screen, you can layout the fragments such that they all share a common parent fragment. That way when you instantiate the ViewModel
you can just do this:
CommonViewModel viewModel = ViewModelProviders.of(getParentFragment()).class(CommonViewModel.class);
Hopefully this helps!
As I know you can't remove ViewModel object manually by program, but you can clear data that stored in that,for this case you should call Oncleared()
method manually
for doing this:
Oncleared()
method in that class that is extended from ViewModel
classIn my case, most of the things I observe are related to the View
s, so I don't need to clear it in case the View
gets destroyed (but not the Fragment
).
In the case I need things like a LiveData
that takes me to another Fragment
(or that does the thing only once), I create a "consuming observer".
It can be done by extending MutableLiveData<T>
:
fun <T> MutableLiveData<T>.observeConsuming(viewLifecycleOwner: LifecycleOwner, function: (T) -> Unit) {
observe(viewLifecycleOwner, Observer<T> {
function(it ?: return@Observer)
value = null
})
}
and as soon as it's observed, it will clear from the LiveData
.
Now you can call it like:
viewModel.navigation.observeConsuming(viewLifecycleOwner) {
startActivity(Intent(this, LoginActivity::class.java))
}
I found a simple and fairly elegant way to deal with this issue. The trick is to use a DummyViewModel and model key.
The code works because AndroidX checks the class type of the model on get(). If it doesn't match it creates a new ViewModel using the current ViewModelProvider.Factory.
public class MyActivity extends AppCompatActivity {
private static final String KEY_MY_MODEL = "model";
void clearMyViewModel() {
new ViewModelProvider(this, new ViewModelProvider.NewInstanceFactory()).
.get(KEY_MY_MODEL, DummyViewModel.class);
}
MyViewModel getMyViewModel() {
return new ViewModelProvider(this, new ViewModelProvider.AndroidViewModelFactory(getApplication()).
.get(KEY_MY_MODEL, MyViewModel.class);
}
static class DummyViewModel extends ViewModel {
//Intentionally blank
}
}
Quick solution without having to use Navigation Component
library:
getActivity().getViewModelStore().clear();
This will solve this problem without incorporating the Navigation Component
library. It’s also a simple one line of code. It will clear out those ViewModels
that are shared between Fragments
via the Activity
It seems like it has been already solved in the latest architecture components version.
ViewModelProvider has a following constructor:
/**
* Creates {@code ViewModelProvider}, which will create {@code ViewModels} via the given
* {@code Factory} and retain them in a store of the given {@code ViewModelStoreOwner}.
*
* @param owner a {@code ViewModelStoreOwner} whose {@link ViewModelStore} will be used to
* retain {@code ViewModels}
* @param factory a {@code Factory} which will be used to instantiate
* new {@code ViewModels}
*/
public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
this(owner.getViewModelStore(), factory);
}
Which, in case of Fragment, would use scoped ViewModelStore.
androidx.fragment.app.Fragment#getViewModelStore
/**
* Returns the {@link ViewModelStore} associated with this Fragment
* <p>
* Overriding this method is no longer supported and this method will be made
* <code>final</code> in a future version of Fragment.
*
* @return a {@code ViewModelStore}
* @throws IllegalStateException if called before the Fragment is attached i.e., before
* onAttach().
*/
@NonNull
@Override
public ViewModelStore getViewModelStore() {
if (mFragmentManager == null) {
throw new IllegalStateException("Can't access ViewModels from detached fragment");
}
return mFragmentManager.getViewModelStore(this);
}
androidx.fragment.app.FragmentManagerViewModel#getViewModelStore
@NonNull
ViewModelStore getViewModelStore(@NonNull Fragment f) {
ViewModelStore viewModelStore = mViewModelStores.get(f.mWho);
if (viewModelStore == null) {
viewModelStore = new ViewModelStore();
mViewModelStores.put(f.mWho, viewModelStore);
}
return viewModelStore;
}