Reputation: 2295
I'm learning Android Jetpack, the following Code is from a sample project at https://github.com/android/sunflower .
In my mind, a ViewModel classs must inherit from ViewModel()
in Code B, just like Code E.
1: Why doesn't the class PlantAndGardenPlantingsViewModel
inherit from ViewModel() in Code B?
2: And more, it seems that the app need launch executePendingBindings()
opeartion (Code D, see source code) to update list_item_garden_planting.xml
UI when it use Code B, right?
BTW, the other ViewModel class in the project is inherit from ViewModel(), such as Code E, you can see source code.
Code A
list_item_garden_planting.xml
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="clickListener"
type="android.view.View.OnClickListener"/>
<variable
name="viewModel"
type="com.google.samples.apps.sunflower.viewmodels.PlantAndGardenPlantingsViewModel"/>
</data>
...
Code B
PlantAndGardenPlantingsViewModel.kt
class PlantAndGardenPlantingsViewModel(plantings: PlantAndGardenPlantings) {
private val plant = checkNotNull(plantings.plant)
private val gardenPlanting = plantings.gardenPlantings[0]
...
Code D
...
fun bind(plantings: PlantAndGardenPlantings) {
with(binding) {
viewModel = PlantAndGardenPlantingsViewModel(plantings)
executePendingBindings()
}
}
...
Code E
class PlantDetailViewModel(
plantRepository: PlantRepository,
private val gardenPlantingRepository: GardenPlantingRepository,
private val plantId: String
) : ViewModel() {
val isPlanted = gardenPlantingRepository.isPlanted(plantId)
val plant = plantRepository.getPlant(plantId)
fun addPlantToGarden() {
viewModelScope.launch {
gardenPlantingRepository.createGardenPlanting(plantId)
}
}
}
Upvotes: 1
Views: 967
Reputation: 67189
Actually there is not anything magical with ViewModel class. It's creation, and retention which makes it useful.
ViewModel source code simplified
public abstract class ViewModel {
// Can't use ConcurrentHashMap, because it can lose values on old apis (see b/37042460)
@Nullable
private final Map<String, Object> mBagOfTags = new HashMap<>();
private volatile boolean mCleared = false;
/**
* This method will be called when this ViewModel is no longer used and will be destroyed.
* <p>
* It is useful when ViewModel observes some data and you need to clear this subscription to
* prevent a leak of this ViewModel.
*/
@SuppressWarnings("WeakerAccess")
protected void onCleared() {
}
@MainThread
final void clear() {
mCleared = true;
// Since clear() is final, this method is still called on mock objects
// and in those cases, mBagOfTags is null. It'll always be empty though
// because setTagIfAbsent and getTag are not final so we can skip
// clearing it
if (mBagOfTags != null) {
synchronized (mBagOfTags) {
for (Object value : mBagOfTags.values()) {
// see comment for the similar call in setTagIfAbsent
closeWithRuntimeException(value);
}
}
}
onCleared();
}
}
Which is a container where you call onCleared() when you are done with it. 2 things that make a ViewModel class is useful are
1- You get the same ViewModel if you use the same Activity(key of hashMap) 2- You get the same ViewModel after device rotation
If you check out the super classes of AppCompatActivity
, you can see that ComponentActivity
class implements theViewModelStoreOwner
interface.
This interface has a method getViewModelStore()
which creates or returns a ViewModelStore item.
public ViewModelStore getViewModelStore() {
if (getApplication() == null) {
throw new IllegalStateException("Your activity is not yet attached to the "
+ "Application instance. You can't request ViewModel before onCreate call.");
}
if (mViewModelStore == null) {
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
// Restore the ViewModelStore from NonConfigurationInstances
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
return mViewModelStore;
}
and ViewModelStore is container for a HashMap that holds keys as class names.
public class ViewModelStore {
private final HashMap<String, ViewModel> mMap = new HashMap<>();
final void put(String key, ViewModel viewModel) {
ViewModel oldViewModel = mMap.put(key, viewModel);
if (oldViewModel != null) {
oldViewModel.onCleared();
}
}
final ViewModel get(String key) {
return mMap.get(key);
}
Set<String> keys() {
return new HashSet<>(mMap.keySet());
}
/**
* Clears internal storage and notifies ViewModels that they are no longer used.
*/
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.clear();
}
mMap.clear();
}
}
If you use the same objects to retrieve a ViewModel, if key has equals and hashcode, you get the same ViewModel.
That's how you get same ViewModel
in fragments if you use Activity
as key
mvViewModel = ViewModelProvider(activity!!, viewModelFactory).get(MyViewModel::class.java)
2- You get the same ViewModel after device rotation since ComponentActivity class has a method
/**
* Retain all appropriate non-config state. You can NOT
* override this yourself! Use a {@link androidx.lifecycle.ViewModel} if you want to
* retain your own non config state.
*/
@Override
@Nullable
public final Object onRetainNonConfigurationInstance() {
Object custom = onRetainCustomNonConfigurationInstance();
ViewModelStore viewModelStore = mViewModelStore;
if (viewModelStore == null) {
// No one called getViewModelStore(), so see if there was an existing
// ViewModelStore from our last NonConfigurationInstance
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
viewModelStore = nc.viewModelStore;
}
}
if (viewModelStore == null && custom == null) {
return null;
}
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.custom = custom;
nci.viewModelStore = viewModelStore;
return nci;
}
If you wish to benefit from these two features you can use Jetpack ViewModel
or you can build same structure using a HashMap and factory pattern for creation of a custom ViewModel class implementation.
Upvotes: 3
Reputation: 318
The ViewModel class is designed to store and manage UI-related data in a lifecycle conscious way. The ViewModel class allows data to survive configuration changes such as screen rotations. So,in some cases,you shouldn't inherit from ViewModel() when Activity/Fragment is killed and re-built the view-model will be reused, the same instance of view-model. For example, local(country or language) of the device has been changed, the Activity might be destroyed and recreated, the view-model will be still the one like before-destroy. Besides,you can see more detail in this link.https://github.com/android/sunflower/issues/132
Upvotes: 0