Why use ViewModel

Generally, we write the business logic such as UI interaction and logic processing in the Activity/Fragment. As the business iterates, the page code becomes bloated and difficult to maintain, and does not conform to the principle of “single responsibility”. The page should only be responsible for the UI interaction content, relevant data acquisition and logical processing should be stored and processed separately.

The ViewModel is designed by Android specifically to hold the data needed for application pages. It takes the data needed for the page out of the page, and the page only has to handle the user interaction and the job of presenting the data

Another important feature of the ViewModel is to solve the problem of destruction and reconstruction caused by resource configuration. If the resource configuration changes, you need to consider data storage and recovery. If you do not store the data, you need to obtain data again. The ViewModel is independent of resource configuration changes and does not affect the lifecycle of the ViewModel even if resource configuration changes cause Activity pages to be rebuilt.

Simply put, the ViewModel is designed to store and manage data related to the interface in a life-cycle manner, allowing the data to survive configuration changes such as screen rotation, acting as a bridge between the View and the Model.

The ViewModel object exists for a time Lifecycle that is passed to the ViewModelProvider when the ViewModel is acquired. The ViewModel will remain in memory until Lifecycle is gone permanently: for an activity, when the activity completes; For fragments, this is when the fragment is separating

Two, basic use

Depend on the introduction of

dependencies {
    def lifecycle_version = "xxx"
    // ViewModel
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
    // LiveData
    implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
    // Lifecycles only (without ViewModel or LiveData)
    implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"

    // Saved state module for ViewModel
    implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version"

    // Annotation processor
    kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
    // alternately - if using Java8, use the following instead of lifecycle-compiler
    implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
}
Copy the code

2.1 Simple Use

The ViewModel class is responsible for preparing data for the interface and automatically retaining ViewModel objects during configuration changes.

For example, get and store user information in ViewModel

Define a User data class

data class User(var age:Int.var name:String)
Copy the code

Implement the viewModel classes

class UserModel:ViewModel() {
    val mUserLiveData:MutableLiveData<User> = MutableLiveData()
    init {
        // Simulate loading user data from the network
        mUserLiveData.postValue(User(1."name1"))}fun doSomething(a){
        val user = mUserLiveData.value
        if(user ! =null){
            user.age = 15
            user.name = "name15"
            mUserLiveData.value = user
        }
    }
}
Copy the code

Use the ViewModel in the Activity to get User information

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        text = findViewById(R.id.tv_text)
        
        val userModel: UserModel = ViewModelProvider(this)[UserModel::class.java]
        userModel.mUserLiveData.observe(this){
            text.text = it.toString()
        }

        text.setOnClickListener(){
            userModel.doSomething()
        }
    }
}
Copy the code

Click the button, the age in user changes to 15, and then rotate the screen so that the TextView displays the same age

A ViewModel must not reference a view, Lifecycle, or any class that might store a reference to the Activity context, as this could result in a memory leak, as any in the ViewModel will be destroyed automatically after the Activity is destroyed.

A ViewModel object exists longer than a particular instance of a view or LifecycleOwner. If the ViewModel needs an Application context, you can extend the AndroidViewModel class and set the constructor to receive the Application.

2.2 Communication between Fragments

It is common for two or more fragments in an Activity to need to communicate with each other. Activities and fragments can solve this communication problem by sharing a ViewModel, because fragments are attached to activities.

When you instantiate a ViewModel, passing the Activity into the ViewModelProvider gives you a Fragment of the ViewModel that the Activity created. The Fragment can easily access the data in the ViewModel. After you modify the userModel data in the Activity, the Fragment gets the updated data

class SharedViewModel:ViewModel() {
    private val selected:MutableLiveData<String> = MutableLiveData()
    fun select(string: String){
        selected.value = string
    }

    fun getSelected(a):LiveData<String>{
        return selected
    }
}
Copy the code
class MasterFragment : Fragment() {
    private var model: SharedViewModel? = null
    fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        // Get the ViewModel of the attached Activitymodel = getActivity()? .let { ViewModelProvider(it)[SharedViewModel::class.java] } stringSelector.setOnClickListener { item -> model!! .select(item) } } }Copy the code
class DetailFragment: Fragment() {private var model:SharedViewModel? = null
    fun onCreate(savedInstanceState: Bundle?).{
        super.onCreate(savedInstanceState)
        // Get the ViewModel of the attached Activitymodel = activity? .let { ViewModelProvider(it)[SharedViewModel::class.java] } model? .getSelected()? .observe(this, {/ / update the UI}}})Copy the code

Both the MasterFragment and DetailFragment mentioned above can get the ViewModel of the Activity. Once you get the ViewModel, you can get the data inside, which is equivalent to indirectly communicating with the ViewModel

Advantages of Fragment communication via ViewModel:

  1. ActivityNo action is required and no knowledge of this communication is required.
  2. In addition toSharedViewModelIn addition to the agreement,FragmentNo need to know each other
  3. eachFragmentEach has its own life cycle, independent of the otherFragmentLife cycle impact.

Three, principle analysis

About ViewModel principle analysis, based on version 2.4.0

Let’s take a look at some of the core roles of the ViewModel

  • ViewModelProvider

    The ViewModel usage utility class encapsulates a set of action methods used to create a ViewModel

  • ViewModelStore

    A storage class that stores multiple ViewModels

  • ViewModelStoreOwner

    The owner of ViewModel memory, activities and fragments are implementors of them

  • SavedStateHandle

    The extension of the ViewModel allows developers to save and rebuild data in the ViewModel rather than using onSaveInstanceState(Bundle) directly

  • .

The ViewModel works as shown below

ViewModel construction process:

ViewModelStore tree:

ViewModelStore destruction logic:

The principle of ViewModel can be summarized as follows:

  1. All instantiatedViewModelAre cachedViewModelStoreOf the encapsulated objects, the essence is aHashMap
  2. ViewModelStoreWith a specificThe Controller (Activity/fragments)Binding is born and dies with the same life cycle
  3. To obtainViewModelDelegate to the instance procedure ofViewModelProviderUtility class, which contains a createViewModelThe factory classFactoryAnd a rightViewModelStoreA reference to the
  4. To obtainViewModel, will first fromViewModelStoreTo obtain the cacheViewModel, if not cachedFacotryInstantiate a new oneViewModelAnd the cache
  5. viewModelStoreStored in theNonConfigurationInstancesOf the classmLastNonConfigurationInstancesIn, it is thereActivityClientRecordA component information in,ActivityClientRecordThere is aActivityThreadthemActivitiesDue toActivityThreadIs not affected byActivityReconstruct the impact, and so on,ViewModelIs not affected byActivityReconstruction effect
  6. ActivityUpon receipt ofON_DESTROYEvent if it is determined that a configuration item change caused the eventActivityDestroyed, then not calledgetViewModelStore().clear()ViewModelInternal data is preserved

3.1 ViewModel Storage and Acquisition

Start with the ViewModelProvider(this)[UserModel::class.java] of the previous code

// Create the ViewModelProvider and keep it in the store given ViewModelStoreOwner
public open class ViewModelProvider(
    private val store: ViewModelStore,
    private val factory: Factory
){
    public constructor(
        owner: ViewModelStoreOwner
    ) : this(owner.viewModelStore, defaultFactory(owner))
}
Copy the code
internal fun defaultFactory(owner: ViewModelStoreOwner): Factory =
if (owner is HasDefaultViewModelProviderFactory)
owner.defaultViewModelProviderFactory else instance

@JvmStatic
public val instance: NewInstanceFactory
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
get() {
    if (sInstance == null) {
        sInstance = NewInstanceFactory()
    }
    return sInstance!!
}
Copy the code

AppCompatActivity parent ComponentActivity has achieved ViewModelStoreOwner and fragments and HasDefaultViewModelProviderFactory interface, You can get the ViewModelStore and Factory instances directly from it. Of course, we can also pass in our own custom implementation of the Factory instance.

Let’s look at the internal implementation of ViewModelStore and Factory

3.1.1 Factory

The Factory is an internal interface to the ViewModelProvider, and the implementation class is used to build ViewModel instances

public interface Factory {
    /** * Creates a new instance of the given `Class`. */
    public fun <T : ViewModel> create(modelClass: Class<T>): T
}
Copy the code

For Factory, we focus on two implementation classes, NewInstanceFactory and AndroidViewModelFactory

NewInstanceFactoryThe implementation class

The NewInstanceFactory is the ViewModel that initializes the constructor’s parameterless type by reflection

@Suppress("SingletonConstructor")
public open class NewInstanceFactory : Factory {
    @Suppress("DocumentExceptions")
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        return try {
            // Reflection implementation, the passed ViewModelClass must contain a no-argument constructor
            modelClass.newInstance()
        } catch (e: InstantiationException) {
            throw RuntimeException("Cannot create an instance of $modelClass", e)
        } catch (e: IllegalAccessException) {
            throw RuntimeException("Cannot create an instance of $modelClass", e)
        }
    }

    public companion object {
        private var sInstance: NewInstanceFactory? = null

        /** * Retrieve a singleton instance of NewInstanceFactory. */
        @JvmStatic
        public val instance: NewInstanceFactory
        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
        get() {
            if (sInstance == null) {
                sInstance = NewInstanceFactory()
            }
            returnsInstance!! }}}Copy the code

AndroidViewModelFactoryThe implementation class

AndroidViewModelFactory is specifically used to instantiate objects with Context in ViewModel.

public open class AndroidViewModelFactory(
    private val application: Application
) : NewInstanceFactory() {
    @Suppress("DocumentExceptions")
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        return if (AndroidViewModel::class.java.isAssignableFrom(modelClass)) {
            try {
                modelClass.getConstructor(Application::class.java).newInstance(application)
            } catch (e: NoSuchMethodException) {
                throw RuntimeException("Cannot create an instance of $modelClass", e)
            } catch (e: IllegalAccessException) {
                throw RuntimeException("Cannot create an instance of $modelClass", e)
            } catch (e: InstantiationException) {
                throw RuntimeException("Cannot create an instance of $modelClass", e)
            } catch (e: InvocationTargetException) {
                throw RuntimeException("Cannot create an instance of $modelClass", e)
            }
        } else super.create(modelClass)
    }

    public companion object {
        internal fun defaultFactory(owner: ViewModelStoreOwner): Factory =
        if (owner is HasDefaultViewModelProviderFactory)
        owner.defaultViewModelProviderFactory else instance

        internal const val DEFAULT_KEY = "androidx.lifecycle.ViewModelProvider.DefaultKey"

        private var sInstance: AndroidViewModelFactory? = null

        /** * Retrieve a singleton instance of AndroidViewModelFactory. */
        @JvmStatic
        public fun getInstance(application: Application): AndroidViewModelFactory {
            if (sInstance == null) {
                sInstance = AndroidViewModelFactory(application)
            }
            returnsInstance!! }}}Copy the code

AndroidViewModelFactory uses the constructor to bring the ViewModel into the Application, so you can get the Context in the ViewModel, because the Application is global to the APP, so there is no memory leak.

HasDefaultViewModelProviderFactoryimplementation

ComponentActivity and fragments are realized HasDefaultViewModelProviderFactory interface, take a look at its contact with the Factory.

ComponentActivity, for example

public interface HasDefaultViewModelProviderFactory {
    @NonNull
    ViewModelProvider.Factory getDefaultViewModelProviderFactory(a);
}
Copy the code
@Override
public ViewModelProvider.Factory getDefaultViewModelProviderFactory(a) {
    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 (mDefaultFactory == null) {
        mDefaultFactory = new SavedStateViewModelFactory(
            getApplication(),
            this, getIntent() ! =null ? getIntent().getExtras() : null);
    }
    return mDefaultFactory;
}
Copy the code
public SavedStateViewModelFactory(@Nullable Application application,
                                  @NonNull SavedStateRegistryOwner owner,
                                  @Nullable Bundle defaultArgs) { mSavedStateRegistry = owner.getSavedStateRegistry(); mLifecycle = owner.getLifecycle(); mDefaultArgs = defaultArgs; mApplication = application; mFactory = application ! =null
        ? ViewModelProvider.AndroidViewModelFactory.getInstance(application)
        : ViewModelProvider.NewInstanceFactory.getInstance();
}
Copy the code

HasDefaultViewModelProviderFactory in ComponentActivity eventually is judged according to the Application through NewInstanceFactory or AndroidViewModelFactory gen Create ViewModel instances.

If we want to use a ViewModel instance with parameters, we need to customize the Factory. Customization of the Factory is covered at the end of this article.

3.1.2 ViewModelStore

The Factory is used to create viewModels, and the ViewModelStore is used to store viewModels

public class ViewModelStore {

    private final HashMap<String, ViewModel> mMap = new HashMap<>();

    //ViewModel is stored as Value
    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(a) {
        return new HashSet<>(mMap.keySet());
    }

    // Clear the ViewModel. Call this method if the ViewModelStore owner (Activity/Fragment) does not rebuild after being destroyed
    public final void clear(a) {
        for(ViewModel vm : mMap.values()) { vm.clear(); } mMap.clear(); }}Copy the code

ViewModelStore stores a ViewModel as a Value in a HashMap.

Both activities and fragments implement ViewModelStoreOwner to obtain the ViewModelStore. Here we use the Activity as an example

public interface ViewModelStoreOwner {
    @NonNull
    ViewModelStore getViewModelStore(a);
}
Copy the code
@Override
public ViewModelStore getViewModelStore(a) {
    // The activity is not yet associated with the Application, that is, it cannot fetch the viewModel before onCreate
    if (getApplication() == null) {
        throw new IllegalStateException("Your activity is not yet attached to the "
                                        + "Application instance. You can't request ViewModel before onCreate call.");
    }
    ensureViewModelStore();
    return mViewModelStore;
}
Copy the code
void ensureViewModelStore(a) {
    if (mViewModelStore == null) {
        // If the storage is empty, try to restore the data saved from the last configuration change
        NonConfigurationInstances nc =
            (NonConfigurationInstances) getLastNonConfigurationInstance();
        if(nc ! =null) {
            // Restore the ViewModelStore from NonConfigurationInstances
            mViewModelStore = nc.viewModelStore;
        }
        / / if lastNonConfigurationInstance does not exist, then the new one
        if (mViewModelStore == null) {
            mViewModelStore = newViewModelStore(); }}}Copy the code

The main contents of getViewModelStore() :

  1. ifViewModelStoreIf is empty, try first fromNonConfigurationInstanceFrom the access toViewModelStoreThe instance
  2. ifNonConfigurationInstanceIt doesn’t existnewaViewModelStore.

NonConfigurationInstancesclass

NonConfigurationInstances class is a static inner class ComponentActivity, used to store viewModelStore, it will not disappear or change along with the configuration change.

static final class NonConfigurationInstances {
    Object custom;
    ViewModelStore viewModelStore;
}
Copy the code

The secret is that ViewModel objects do not change as resource configuration changes cause activities/fragments to be destroyed and rebuilt.

The Activity will be called before Destroy retainNonConfigurationInstances () method, this method performs NonConfigurationInstances store operation.

//Activity.java
NonConfigurationInstances retainNonConfigurationInstances(a) { Object activity = onRetainNonConfigurationInstance(); HashMap<String, Object> children = onRetainNonConfigurationChildInstances(); FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig(); . NonConfigurationInstances nci =new NonConfigurationInstances();
    nci.activity = activity;
    nci.children = children;
    nci.fragments = fragments;
    nci.loaders = loaders;
    if(mVoiceInteractor ! =null) {
        mVoiceInteractor.retainInstance();
        nci.voiceInteractor = mVoiceInteractor;
    }
    return nci;
}

static final class NonConfigurationInstances {
    Object activity;
    HashMap<String, Object> children;
    FragmentManagerNonConfig fragments;
    ArrayMap<String, LoaderManager> loaders;
    VoiceInteractor voiceInteractor;
}

NonConfigurationInstances mLastNonConfigurationInstances;
Copy the code
//ComponentActivity.java
@Override
@Nullable
@SuppressWarnings("deprecation")
public final Object onRetainNonConfigurationInstance(a) {
    // Maintain backward compatibility.Object custom = onRetainCustomNonConfigurationInstance(); ViewModelStore viewModelStore = mViewModelStore; . NonConfigurationInstances nci =new NonConfigurationInstances();
    nci.custom = custom;
    nci.viewModelStore = viewModelStore;	// The ViewModelStore is stored
    return nci;
}
Copy the code

RetainNonConfigurationInstances () to the Activity of the deposit to NonConfigurationInstances viewModelStore object object

Why the allocation of resources change will not affect the NonConfigurationInstances instance

Look at the getLastNonConfigurationInstance () method

public Object getLastNonConfigurationInstance(a) {
    returnmLastNonConfigurationInstances ! =null
        ? mLastNonConfigurationInstances.activity : null;
}
Copy the code

MLastNonConfigurationInstances is how to? Look at the attach method

final void attach(Context context, ActivityThread aThread, NonConfigurationInstances lastNonConfigurationInstances, ...... ){... mLastNonConfigurationInstances = lastNonConfigurationInstances; . }Copy the code

The Activity of the attach method can give a mLastNonConfigurationInstances assignment, for the Activity associated context, in ActivityThread performLaunchActivity method call.

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {

    activity.attach(appContext, this, getInstrumentation(), r.token, r.ident, app, r.intent, r.activityInfo, title, r.parent, r.embeddedID, r.lastNonConfigurationInstances, config, r.referrer, r.voiceInteractor, window, r.configCallback, r.assistToken, r.shareableActivityToken); . }Copy the code

There is a component of ActivityClientRecord lastNonConfigurationInstances information, and exists in the mActivities ActivityThread ActivityClientRecord

//ActivityThread.java
/** * Maps from activity token to local record of running activities in this process. */
final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>();
Copy the code

Activity destroyed before they call retainNonConfigurationInstances () method, and the method is in performDestroyActivity () is invoked

/** Core implementation of activity destroy call. */
void performDestroyActivity(ActivityClientRecord r, boolean finishing,
                            int configChanges, boolean getNonConfigInstance, String reason) {
    r.lastNonConfigurationInstances = r.activity.retainNonConfigurationInstances();	//Activity Stores information. }Copy the code

ActivityClientRecord ActivityThread is not affected by the activity reconstruction, then its lastNonConfigurationInstances also is not affected, Corresponding NonConfigurationInstances viewModelStore is not affected, the storage of the ViewModel nature also are not affected

3.1.3 the get

Return to ViewModel’s original entry: ViewModelProvider(this)[UserModel::class.java].

The ViewModelProvider(this) takes the Factory instance and then calls the Get () method, which needs to pass in the Class object. The ViewModelProvider needs to take the Class to do the reflection.

Get () mainly generates a string Key automatically through modelClass and forwards the parameters to another get() method

@MainThread
public open operator fun <T : ViewModel> get(modelClass: Class<T>): T {
    // Return the full class name
    valcanonicalName = modelClass.canonicalName ? :throw IllegalArgumentException("Local and anonymous classes can not be ViewModels")
    return get("$DEFAULT_KEY:$canonicalName", modelClass)
}
Copy the code
@MainThread
public open operator fun <T : ViewModel> get(key: String, modelClass: Class<T>): T {
    // Get the ViewModel cache from the Map
    var viewModel = store[key]
    // Determine whether the viewModel object can be converted to this class
    if (modelClass.isInstance(viewModel)) {
        (factory as? OnRequeryFactory)? .onRequery(viewModel)return viewModel as T
    } else {
        @Suppress("ControlFlowWithEmptyBody")
        if(viewModel ! =null) {
            // TODO: log a warning.}}// No cache, build again with mFactory
    viewModel = if (factory is KeyedFactory) {
        factory.create(key, modelClass)
    } else {
        factory.create(modelClass)
    }
    // Cache the ViewModel
    store.put(key, viewModel)
    return viewModel
}
Copy the code

Fetch the ViewModel instance from the ViewModelStore by key. If it is not available or the type does not match, the mfactory.create () method is used to reflect the initialization of the ViewModel and store it in the mViewModelStore before returning the initialization result. Thus completing the initialization process of the ViewModel

3.1.4 summary

The ViewModel stores and retrieves some of the content briefly summarized as:

  1. All instantiatedViewModelAre cachedViewModelStoreEncapsulation object, its essence is aHashMap
  2. ViewModelStoreWith a specificThe Controller (Activity/fragments)Binding, as long as its life cycle
  3. To obtainViewModelDelegate to the instance procedure ofViewModelProviderUtility class, which contains a createViewModelThe factory classFactoryAnd a rightViewModelStoreA reference to the
  4. To obtainViewModel, will first fromViewModelStoreTo obtain the cacheViewModel, if not cachedFacotryInstantiate a new oneViewModelAnd the cache
  5. viewModelStoreStored in theNonConfigurationInstancesOf the classmLastNonConfigurationInstancesIn, it is thereActivityClientRecordA component information in,ActivityClientRecordThere is aActivityThreadthemActivitiesDue toActivityThreadIs not affected byActivityReconstruct the impact, and so on,ViewModelIs not affected byActivityReconstruction effect

3.2 the ViewModel recycling

To know when the ViewModel is reclaimed, just look at when the ViewModelStore empties the HashMap.

The ViewModel instance object is stored in the ViewModelStore, so the recycling time of the HashMap in the ViewModelStore can be confirmed by confirming the cleaning time.

The ViewModelStore clear() method is called in ComponentActivity

public ComponentActivity(a) {
    Lifecycle lifecycle = getLifecycle();
    getLifecycle().addObserver(new LifecycleEventObserver() {
        @Override
        public void onStateChanged(@NonNull LifecycleOwner source,
                                   @NonNull Lifecycle.Event event) {
            if (event == Lifecycle.Event.ON_DESTROY) {
                // Clear out the available context
                mContextAwareHelper.clearAvailableContext();
                // And clear the ViewModelStore
                if(! isChangingConfigurations()) { getViewModelStore().clear(); }}}}); . }Copy the code

The Activity does not call the ViewModelStore clear() method when it receives the ON_DESTROY event and determines that the page is being destroyed due to a resource configuration change. If it exits normally or is killed by the system, the clear() method is called to clear all cached ViewModel instances

4. Customize ViewModel

The ViewModelProvider provides two Factory interface implementation classes

  • NewInstanceFactory: initializes a constructor that contains no arguments by reflectionViewModel
  • AndroidViewModelFactory: contains only one and is initialized by reflectionApplicationConstructor of a typeViewModel

If needed by other types of constructors to initialize the ViewModel, the need to achieve ViewModelProvider. The factory interface to complete the initialization logic

class MyViewModel(val age: Int) : ViewModel() {
    val nameLiveData = MutableLiveData<String>()
}
Copy the code
class MainActivity : AppCompatActivity() {
    / / create myViewModelA
    private val myViewModelA by lazy {
        ViewModelProvider(this.object : ViewModelProvider.Factory {
            override fun <T : ViewModel> create(modelClass: Class<T>): T {
                return MyViewModel(10) as T
            }
        }).get(
            MyViewModel::class.java
        ).apply {
            nameLiveData.observe(this@MainActivity, {})}}/ / create myViewModelB
    private val myViewModelB by lazy {
        ViewModelProvider(this.object : ViewModelProvider.Factory {
            override fun <T : ViewModel> create(modelClass: Class<T>): T {
                return MyViewModel(20) as T
            }
        }).get(
            MyViewModel::class.java
        ).apply {
            nameLiveData.observe(this@MainActivity, {})}}override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        Log.e("myViewModelA", myViewModelA.toString() + " age: " + myViewModelA.age)
        Log.e("myViewModelB", myViewModelB.toString() + " age: " + myViewModelB.age)
    }
}
Copy the code
/** Prints the result **/
E/myViewModelA: github.leavesc.demo.MyViewModel@e24ac80 age: 10
E/myViewModelB: github.leavesc.demo.MyViewModel@e24ac80 age: 10
Copy the code

MyViewModelA and myViewModelB have different input parameters, but they correspond to the same memory address, that is, the first initialized ViewModel instance is cached and reused. This is because they default to the same Key, so the previous cache is reused between initializations of myViewModelB.

@MainThread
public open operator fun <T : ViewModel> get(key: String, modelClass: Class<T>): T {
    var viewModel = store[key]
    if (modelClass.isInstance(viewModel)) {
        (factory as? OnRequeryFactory)? .onRequery(viewModel)return viewModel as T
    } else {
        @Suppress("ControlFlowWithEmptyBody")
        if(viewModel ! =null) {
            // TODO: log a warning.
        }
    }
    viewModel = if (factory is KeyedFactory) {
        factory.create(key, modelClass)
    } else {
        factory.create(modelClass)
    }
    store.put(key, viewModel)
    return viewModel
}
Copy the code

If you want myViewModelA and myViewModelB to correspond to different instance objects, you need to actively specify different keys for them at initialization so that they can be stored together in the ViewModelStore HashMap

private val myViewModelA by lazy {
    ViewModelProvider(this.object : ViewModelProvider.Factory {
        override fun <T : ViewModel> create(modelClass: Class<T>): T {
            return MyViewModel(10) as T
        }
    }).get(
        "keyA", MyViewModel::class.java
    ).apply {
        nameLiveData.observe(this@MainActivity, {})}}private val myViewModelB by lazy {
    ViewModelProvider(this.object : ViewModelProvider.Factory {
        override fun <T : ViewModel> create(modelClass: Class<T>): T {
            return MyViewModel(20) as T
        }
    }).get(
        "keyB", MyViewModel::class.java
    ).apply {
        nameLiveData.observe(this@MainActivity, {})}}Copy the code
E/myViewModelA: github.leavesc.demo.MyViewModel@e24ac80 age: 10
E/myViewModelB: github.leavesc.demo.MyViewModel@9abd6fe age: 20
Copy the code

5. Common problems

5.1 Initializing traps

The initialization trap mentioned here, mentioned in the custom ViewModel section, is that the cache reuse mechanism of the ViewModel can cause usage problems.

class AViewModel() : ViewModel() {

    override fun onCleared(a) {
        super.onCleared()
        Log.e("AViewModel"."onCleared")}}Copy the code
class BViewMode : ViewModel() {

    override fun onCleared(a) {
        super.onCleared()
        Log.e("BViewMode"."onCleared")}}Copy the code
class MainActivity : AppCompatActivity() {

    private val aViewModel by lazy {
        ViewModelProvider(this).get(
            "myKey", AViewModel::class.java
        )
    }

    private val bViewModel by lazy {
        ViewModelProvider(this).get(
            "myKey", BViewMode::class.java
        )
    }

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        Log.e("aViewModel", aViewModel.toString())
        Log.e("bViewModel", bViewModel.toString())
        Log.e("MainActivity"."onCreate")}}Copy the code
/*** prints the result ***/
E/aViewModel: github.leavesc.demo.AViewModel@3c93503
E/AViewModel: onCleared
E/bViewModel: github.leavesc.demo.BViewMode@e24ac80
E/MainActivity: onCreate
Copy the code

The code above prints the result because aViewModel and bViewModel use the same key, but not of the same ViewModel type, causing the aViewModel in the HashMap to be overwritten and recycled when bViewModel is initialized.

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) {
            // Recycle old values if they existoldViewModel.onCleared(); }}}Copy the code

ViewModel:

Different types of ViewModel instances cannot be initialized with the same Key