• Basic usage of Kotlin coroutines
  • Kotlin coroutine introduction to Android version in detail (ii) -> Kotlin coroutine key knowledge points preliminary explanation
  • Kotlin coroutine exception handling
  • Use Kotlin coroutine to develop Android applications
  • Network request encapsulation for Kotlin coroutines

Extend the series

  • Encapsulating DataBinding saves you thousands of lines of code

The author is just an ordinary developer, the design is not necessarily reasonable, we can absorb the essence of the article, to dross.

ViewModel basic use

We learned how to encapsulate DataBinding for code optimization in our previous article, encapsulating DataBinding Saves You Thousands of lines of Code. In this section, we’ll build on the previous article and explain how to use the ViewModel wrapper.

ViewModel basic use

We all know that the basic way to create a ViewModel is to create it in one of three ways:

class MainActivity05:AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
        /** / val viewModel = ViewModelProvider (this, MainViewModelFactory (MainRepository ())), get (MainViewModel: : class. Java) or val viewModel = ViewModelProvider(viewModelStore,MainViewModelFactory(MainRepository())).get(MainViewModel::class.java) */
        viewModel.mUser.observe(this) {
            Log.d("mUser"."$it")}}}class MainViewModel:ViewModel() {
    private val _user:MutableLiveData<User> = MutableLiveData(User(1."Test"))
    val mUser: LiveData<User> = _user
}

// Create the simplest ViewModel factory
class MainViewModelFactory(
) : ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        return MainViewModel() as T
    }
}
Copy the code

Since we are just demonstrating the MainViewModel we simply created a mUser: LiveData

object for the MainActivity to observe.

We built on this by introducing the lifecycle- Extensions library.

    implementation 'androidx. Lifecycle: lifecycle - extensions: 2.2.0'
Copy the code

We create a ViewModel using the methods it provides: ViewModelProviders

class MainActivity:AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
        viewModel.mUser.observe(this) {
            Log.d("mUser"."$it")}}}Copy the code

Creating viewModels via ViewModel providers is based on methods provided by the Lifecycle -Extensions library, We can see that ViewModelProviders are actually created through the ViewModelProvider.

/**
 * Utilities methods for {@link ViewModelStore} class.
 * @deprecated Use the constructors for {@link ViewModelProvider} directly.
 */
@Deprecated
public class ViewModelProviders {
    @Deprecated
    @NonNull
    @MainThread
    public static ViewModelProvider of(@NonNull FragmentActivity activity) {
        return new ViewModelProvider(activity);
    }
    
    @Deprecated
    @NonNull
    @MainThread
    public static ViewModelProvider of(@NonNull FragmentActivity activity,
            @Nullable Factory factory) {
        if (factory == null) {
            factory = activity.getDefaultViewModelProviderFactory();
        }
        return new ViewModelProvider(activity.getViewModelStore(), factory);
    }
   
    @Deprecated
    @NonNull
    @MainThread
    public static ViewModelProvider of(@NonNull Fragment fragment) {
        return new ViewModelProvider(fragment);
    }
    
    @Deprecated
    @NonNull
    @MainThread
    public static ViewModelProvider of(@NonNull Fragment fragment, @Nullable Factory factory) {
        if (factory == null) {
            factory = fragment.getDefaultViewModelProviderFactory();
        }
        returnnew ViewModelProvider(fragment.getViewModelStore(), factory); }}Copy the code

This method was used most often in early versions, and is still used in many articles on the Web. However, we can see that ViewModelProviders are deprecated in older versions of the lifecycle-extensions library.

Create the ViewModel using the KTX extension library

Since ViewModelProviders are obsolete, it’s also a bit cumbersome to write them. So for further simplification, we use the following method from the KTX library:

 implementation "Androidx. Activity: activity - KTX: 1.2.2." "
 implementation "Androidx. Fragments: fragments - KTX: 1.3.3." "
Copy the code

With these two extension libraries, we can use viewModels to create the viewModels we need very quickly,

class MainActivity:AppCompatActivity() {
 private val viewModel:MainViewModel by viewModels()
 
    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        viewModel.mUser.observe(this) {
            Log.d("mUser"."$it")}}}Copy the code

In fragments we can create them using activityViewModels or viewModels.

class HomeFragment:Fragment() {
    val activityViewModel:MainViewModel by activityViewModels()
    / / or
    val viewModel:MainViewModel by viewModels()
}
Copy the code

You can see that viewModels is actually an inline function for ComponentActivity, The specified ViewModel is obtained by calling ViewModelLazy in the viewModels function, and the viewModels can be passed in the factory factoryProducer used to create the ViewModel:

public inline fun <reified VM : ViewModel> ComponentActivity.viewModels(
    noinline factoryProducer: (() -> Factory)? = null
): Lazy<VM> {
    valfactoryPromise = factoryProducer ? : { defaultViewModelProviderFactory }return ViewModelLazy(VM::class.{ viewModelStore }.factoryPromise)
}
Copy the code

By looking at the ViewModelLazy implementation, we can see that ViewModelLazy is ultimately created by calling the ViewModelProvider method. This is the kotlin syntax sugar.

public class ViewModelLazy<VM : ViewModel> (
    private val viewModelClass: KClass<VM>,
    private val storeProducer: () -> ViewModelStore,
    private val factoryProducer: () -> ViewModelProvider.Factory
) : Lazy<VM> {
    private var cached: VM? = null

    override val value: VM
        get() {
            val viewModel = cached
            return if (viewModel == null) {
                val factory = factoryProducer()
                val store = storeProducer()
                ViewModelProvider(store, factory).get(viewModelClass.java).also {
                    cached = it
                }
            } else {
                viewModel
            }
        }

    override fun isInitialized(a): Boolean= cached ! =null
}
Copy the code

Note that the ViewModel created by activityViewModels in the Fragment will be the Acivity ViewModel of the Fragment. The following steps are basically the same.

public inline fun <reified VM : ViewModel> Fragment.viewModels(
    noinline ownerProducer: () -> ViewModelStoreOwner = { this },
    noinline factoryProducer: (() -> Factory)? = null
): Lazy<VM> = createViewModelLazy(VM::class.{ ownerProducer().viewModelStore }, factoryProducer)

public inline fun <reified VM : ViewModel> Fragment.activityViewModels(
    noinline factoryProducer: (() -> Factory)? = null
): Lazy<VM> = createViewModelLazy(
    VM::class.{ requireActivity().viewModelStore }, factoryProducer ? : { requireActivity().defaultViewModelProviderFactory } )public fun <VM : ViewModel> Fragment.createViewModelLazy(
    viewModelClass: KClass<VM>,
    storeProducer: () -> ViewModelStore,
    factoryProducer: (() -> Factory)? = null
): Lazy<VM> {
    valfactoryPromise = factoryProducer ? : { defaultViewModelProviderFactory }return ViewModelLazy(viewModelClass, storeProducer, factoryPromise)
}
Copy the code

You can see that requireActivity is actually called in the activityViewModels method to create it. If we need to implement data sharing between activities and fragments, we can create it in this way.

Create the ViewModel through reflection

Although the above introduction of KTX library to deal with, may not satisfy the appetite of some people. After all, real development environments often have a variety of problems that lead to inappropriate development usage. It doesn’t matter, we can go one step further with reflection.

From the evolution above, we know that no matter how the external initialization changes, the ultimate way to create is to call the ViewModelProvider. At this point we can create our ViewModel through reflection in the same way that we encapsulate DataBinding.

We’ll add an extension method for ComponentActivity that gets the concrete type of the generic ViewModel we got in BaseActivity:

inline fun <VM: ViewModel> ComponentActivity.createViewModel(position:Int): VM {
    val vbClass = (javaClass.genericSuperclass as ParameterizedType).actualTypeArguments.filterIsInstance<Class<*>>()
    val viewModel = vbClass[position] as Class<VM>
    return ViewModelProvider(this).get(viewModel)
}
Copy the code

Then we can create the ViewModel we need in BaseActivity with createViewModel. If you don’t understand ViewDataBinding in BaseActivity, you can look at encapsulating DataBinding.

abstract class BaseActivity<VB : ViewDataBinding,VM: ViewModel> : AppCompatActivity(), BaseBinding<VB> {
    protected val mBinding: VB by lazy(mode = LazyThreadSafetyMode.NONE) {
       getViewBinding(layoutInflater)
    }
    protected lateinit var viewModel:VM

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setContentView(mBinding.root)
        viewModel = createViewModel(1)
        mBinding.initBinding()
        initObserve()
    }
    abstract fun initObserve(a)
}
Copy the code

Modify our MainActivity:

class MainActivity : BaseActivity<ActivityMainBinding, MainViewModel>() {

    override fun initObserve(a) {
        viewModel.mUser.observe(this){
            Log.d("MainViewModel"."user: $it") //TestViewModel: user: user (id=1, name= test)}}override fun ActivityMainBinding.initBinding(a) {
        // Data binding...}}Copy the code

At this point we successfully created MainViewModel, which saves us a lot of time writing ViewModel template code. Virtually increased the time to catch fish and blow water.

But there is a problem with this. The above method only supports viewModels with no arguments. What if the ViewModel you want to create needs to be passed in parameters. For example, we changed the MainViewModel to require a MainRepository:

class MainViewModel(private val repository: MainRepository):ViewModel() {
    private val _user: MutableLiveData<User> = MutableLiveData(User(1."Test"))
    val mUser: LiveData<User> = _user
}
Copy the code

If we continue to use the above method, our program will crash with an error indicating that the instantiation object cannot be created:

java.lang.RuntimeException: Cannot create an instance of class com.carman.kotlin.coroutine.request.viewmodel.MainViewModel
Copy the code

We need to create a MainViewModelFactory to create our MainViewModel:

class MainViewModelFactory(
    private val repository: MainRepository
) : ViewModelProvider.Factory {
    @Suppress("UNCHECKED_CAST")
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        return MainViewModel(repository) as T
    }
}
Copy the code

How do we combine MainViewModelFactory with the above reflection method? Although I can pass in a Factory using the ViewModelProvider, as we did when we first created MainActivity directly:

ViewModelProvider(this,MainViewModelFactory(MainRepository())).get(MainViewModel::class.java) 
Copy the code

But now we are creating by reflection, when we have many viewModels, we don’t know which one to use. We need to create a utility class for ViewModelUtils, and then extract the createViewModel extension function. We need to add a factory parameter to determine whether we need to create a factory:

object ViewModelUtils {
    fun <VM : ViewModel> createViewModel(
        activity: ComponentActivity,
        factory: ViewModelProvider.Factory? = null,
        position: Int
    ): VM {
        val vbClass =
            (activity.javaClass.genericSuperclass as ParameterizedType).actualTypeArguments.filterIsInstance<Class<*>>()
        val viewModel = vbClass[position] as Class<VM>
        returnfactory? .let { ViewModelProvider( activity, factory ).get(viewModel) } ? : let { ViewModelProvider(activity).get(viewModel)
        }
    }
}
Copy the code

Let’s modify BaseActivity again by adding a factory parameter to the constructor. Instead of calling the createViewModel extension function directly, change the createViewModel method to use ViewModelUtils:

abstract class BaseActivity<VB : ViewDataBinding, VM : ViewModel>(
    private val factory: ViewModelProvider.Factory? = null
) : AppCompatActivity(), BaseBinding<VB> {
    protected val mBinding: VB by lazy(mode = LazyThreadSafetyMode.NONE) {
       getViewBinding(layoutInflater)
    }
    protected lateinit var viewModel:VM

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setContentView(mBinding.root)
        viewModel = ViewModelUtils.createViewModel(this, factory, 1)
        mBinding.initBinding()
        initObserve()
    }
    abstract fun initObserve(a)
}
Copy the code

This time we will revise the MainActivity, we use provideMainViewModelFactory methods are used to obtain the MainViewModel built factory MainViewModelFactory:

class MainActivity : BaseActivity<ActivityMainBinding, MainViewModel>(provideMainViewModelFactory()) {

    override fun initObserve(a) {
        viewModel.mUser.observe(this){
            Log.d("MainViewModel"."user: $it")}}override fun ActivityMainBinding.initBinding(a) {
        this.mainViewModel = viewModel
    }
}

fun provideMainViewModelFactory(
): MainViewModelFactory {
    return MainViewModelFactory(MainRepository())
}
Copy the code

At this point we run our program again and everything goes back to normal. Now that we’ve handled creating a ViewModel in an Activity, how do we create a ViewModel in a Fragment?

The activityViewModels and viewModels methods in fragmentation-ktx are used to create viewModels in the Fragment. There are two types of viewModels created in the Fragment.

fun <VM : ViewModel> createViewModel(
        fragment: Fragment,
        factory: ViewModelProvider.Factory? = null,
        position: Int
    ): VM {
        val vbClass =
            (fragment.javaClass.genericSuperclass as ParameterizedType).actualTypeArguments.filterIsInstance<Class<*>>()
        val viewModel = vbClass[position] as Class<VM>
        returnfactory? .let { ViewModelProvider( fragment, factory ).get(viewModel) } ? : let { ViewModelProvider(fragment).get(viewModel)
        }
    }

    fun <VM : ViewModel> createActivityViewModel(
        fragment: Fragment,
        factory: ViewModelProvider.Factory? = null,
        position: Int
    ): VM {
        val vbClass =
            (fragment.javaClass.genericSuperclass as ParameterizedType).actualTypeArguments.filterIsInstance<Class<*>>()
        val viewModel = vbClass[position] as Class<VM>
        returnfactory? .let { ViewModelProvider( fragment.requireActivity(), factory ).get(viewModel) } ? : let { ViewModelProvider(fragment.requireActivity()).get(viewModel)
        }
    }
Copy the code

Now let’s modify the BaseFragment. Unlike in an Activity, since the Fragment exists, you can either share a ViewModel with the Activity or create a private ViewModel yourself. So we added a shareViewModel parameter to control which method we chose to use:

abstract class BaseFragment<VB : ViewDataBinding, VM : ViewModel>(
    private val shareViewModel: Boolean = false.private val factory: ViewModelProvider.Factory? = null
) : Fragment(), BaseBinding<VB> {
    protected lateinit var mBinding: VB
        private set
    protected lateinit var viewModel: VM
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup? , savedInstanceState:Bundle?).: View? {
        mBinding = getViewBinding(inflater, container)
        viewModel = if (shareViewModel) ViewModelUtils.createActivityViewModel(this, factory, 1)
            else ViewModelUtils.createViewModel(this, factory, 1)
        return mBinding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?). {
        super.onViewCreated(view, savedInstanceState)
        mBinding.initBinding()
    }

    override fun onDestroy(a) {
        super.onDestroy()
        if (::mBinding.isInitialized) {
            mBinding.unbind()
        }
    }
}
Copy the code

So far, I’ve covered the three ways in which viewModels are commonly used in development. Let’s talk about creating with annotations, some of you may have a Dagger, but instead of using Dagger, we’re going to use Hilt, one of the Android Jetpack components built around that Dagger for Android.

throughHiltAnnotations create the ViewModel

Let’s start with Hilt dependencies:

 classpath 'com. Google. Dagger hilt - android - gradle - plugin: 2.35.1'
Copy the code
plugins {
    id 'dagger.hilt.android.plugin'
}
Copy the code
 implementation Com. Google. "dagger hilt - android: 2.35.1"
 kapt Com. Google. "dagger hilt - android - the compiler: 2.35.1"
Copy the code

If you want to know more about how to use Hilt, you can go to the official website to see how to use Hilt dependency injection. If you are interested, you can leave a comment in the underground. At that time, I will write a separate article about how to use T for Hil.

We create a DemoApplication with no implementation, but add a @HiltAndroidApp annotation that triggers code generation for Hilt, including a base class for the application that acts as an application-level dependency container.

@HiltAndroidApp
class DemoApplication : Application() {... }Copy the code

Then we need to annotate the MainViewModel with @hiltViewModel and use @Inject to perform field injection

@HiltViewModel
class MainViewModel @Inject constructor(private val repository: MainRepository):ViewModel() {
    private val _user: MutableLiveData<User> = MutableLiveData(User(1."Test"))
    val mUser: LiveData<User> = _user
}
Copy the code

We also need to Inject fields in MainRepository using the @Inject annotation:

class MainRepository @Inject constructor():BaseRepository(){
}
Copy the code

Then use the AndroidEntryPoint annotation in our MainActivity. AndroidEntryPoint generates a separate Hilt component for each Android class in the project. These components can receive dependencies from their respective parent classes

@AndroidEntryPoint
class MainHiltActivity : BaseVBActivity<ActivityMainBinding>() {val viewModel:MainViewModel by viewModels()

    override fun initObserve(a) {
        viewModel.mUser.observe(this){
            Log.d("MainViewModel"."user: $it") //MainViewModel: user: user (id=1, name= test)}}override fun ActivityMainBinding.initBinding(a) {
        this.mainViewModel = viewModel
    }

}
Copy the code

We use this in conjunction with the KTX extension library, and although we pass in the build factory when creating the MainViewModel, we use Hilt annotations to help us inject.

Need source code to see here: Demo source

Originality is not easy. If you like this article, you can click “like”.

Related articles

  • Basic usage of Kotlin coroutines
  • Kotlin coroutine introduction to Android version in detail (ii) -> Kotlin coroutine key knowledge points preliminary explanation
  • Kotlin coroutine exception handling
  • Use Kotlin coroutine to develop Android applications
  • Network request encapsulation for Kotlin coroutines

Extend the series

  • Encapsulating DataBinding saves you thousands of lines of code