在上一章节【Android Jetpack实战追踪】从一个简单的登录页开始
我们知道如何利用Jetpack搭一个简单的MVVM项目的框架,但很多细节并没有介绍,比如ViewModel的创建。
那么今天我们就分别说下ViewModel的几种创建方式。
ViewModelProvider(s)
在lifecycle1.1.1(android.arch.lifecycle:extensions:1.1.1)之前,我们使用以下方式创建ViewModel。
ViewModelProviders.of(this).get(LoginViewModel::class.java)
其中this可以是activity也可以是fragment,of返回的是一个ViewModelProvider对象。
然而在1.1.1及后续版本中,直接使用ViewModelProvider创建ViewModel。
// instantiate ViewModel with its no-args constructor.
val loginViewModel = ViewModelProvider(this, ViewModelProvider.NewInstanceFactory()).get(LoginViewModel::class.java)
其中NewInstanceFactory为创建无参构造器的ViewModel使用。
有时候我们在ViewModel中使用Context相关信息,则可以继承AndroidViewModel。
那么这时候就可以使用ViewModelProvider.AndroidViewModelFactory来创建ViewModel。
那如果还有其他参数怎么办呢?只能自定义Factory了。
比如我们在LoginViewModel的构造方法中添加一个参数UserRepository。
class LoginViewModel(private val repository: UserRepository): ObservableViewModel()
那么这种情况我们只能使用自定义Factory。
private fun createViewModel(): LoginViewModel {
return ViewModelProvider(this, object : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
// Create VM with passing a test UserRepository.
return modelClass.getConstructor(UserRepository::class.java).newInstance(UserRepository())
}
}).get(LoginViewModel::class.java)
}
viewModels()
上述做法虽然不复杂,但是代码量挺多的,那么有没有比较优雅的方式来实例化VM呢?
有!google就帮我们干了这事。
先引入implementation “androidx.activity:activity-ktx:1.2.1”。
然后使用扩展方法viewModels()。
/**
* ViewModel to provide data for Views.
*/
private val mLoginViewModel: LoginViewModel by viewModels {
LoginViewModelFactory()
}
这个LoginViewModelFactory就是把我们上面自定义的Factory给单独出来了。
private class LoginViewModelFactory(): ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
// Create VM with passing a test UserRepository.
return modelClass.getConstructor(UserRepository::class.java).newInstance(UserRepository())
}
}
dagger的配合使用
然而这样还是觉得挺麻烦,一个Factory只能实例化一个VM类,能不能写个万金油的Factory呢?
其实通过factory难点就在于你不知道每个VM类构造器中要传入哪些参数,所以在create方法中就无法正确实例化。
那就真的没辙了吗?
NO!
我们可以借助三方lib,比如dagger,这里我们引入dagger2:0.5.2。
// dagger start
implementation "com.google.dagger:dagger:2.16"
implementation "com.google.dagger:dagger-android:2.16"
implementation "com.google.dagger:dagger-android-support:2.16"
compileOnly 'com.squareup.inject:assisted-inject-annotations-dagger2:0.5.2'
kapt 'com.google.dagger:dagger-compiler:2.16'
kapt 'com.google.dagger:dagger-android-processor:2.16'
kapt 'com.squareup.inject:assisted-inject-processor-dagger2:0.5.2'
implementation "javax.inject:javax.inject:1"
// dagger end
其中kapt是帮我们处理dagger注解。
注意:由于引入了三方包,请在gradle.properties中把三方包中使用的support转换为androidx。
android.enableJetifier=true
下面开始分步骤集成dagger2。
第一步:创建通用的ViewModelFactory
@Singleton
class AppViewModelFactory @Inject constructor(
private val creators: Map<Class<out ViewModel>, @JvmSuppressWildcards Provider<ViewModel>>
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
val creator = creators[modelClass] ?: creators.entries.firstOrNull {
modelClass.isAssignableFrom(it.key)
}?.value ?: throw IllegalArgumentException("unknown model class $modelClass")
@Suppress("UNCHECKED_CAST")
return creator.get() as T
}
}
第二步:修改application
自定义application并实现HasActivityInjector,处理activity注入,其他组件如service等类似。
class LocalApplication: Application(), HasActivityInjector {
@Inject
lateinit var dispatchingActivityInjector: DispatchingAndroidInjector<Activity>
override fun onCreate() {
super.onCreate()
// AppInjector见下方
AppInjector.init(this)
}
override fun activityInjector() = dispatchingActivityInjector
}
第三步:添加支持类
指定Activity的注入方式,每个需要dagger处理的activity都要在这里声明。
@Suppress("unused")
@Module
abstract class ActivityModule {
@ContributesAndroidInjector(modules = [FragmentBuildersModule::class])
abstract fun contributeLoginActivity(): LoginActivity
}
指定Fragment的注入方式,与Activity雷同。
@Suppress("unused")
@Module
abstract class FragmentBuildersModule {
}
指定获取VM及factory的方式,每个VM均要记得在这里注册。
@Suppress("unused")
@Module
abstract class ViewModelModule {
@Binds
abstract fun bindViewModelFactory(factory: AppViewModelFactory): ViewModelProvider.Factory
@Multibinds
abstract fun viewModels(): Map<Class<out ViewModel>, @JvmSuppressWildcards ViewModel>
@Binds
@IntoMap
@ViewModelKey(LoginViewModel::class)
abstract fun bindLoginViewModel(viewModel: LoginViewModel): ViewModel
}
上面的ViewModelKey注解:
@MustBeDocumented
@Target(
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER
)
@Retention(AnnotationRetention.RUNTIME)
@MapKey
annotation class ViewModelKey(val value: KClass<out ViewModel>)
生成注入辅助类:kapt会根据AppComponent生成DaggerAppComponent类(make后)。
@Singleton
@Component(
modules = [
AndroidInjectionModule::class,
AppModule::class,
ActivityModule::class]
)
interface AppComponent {
@Component.Builder
interface Builder {
@BindsInstance
fun application(application: Application): Builder
fun build(): AppComponent
}
fun inject(application: LocalApplication)
}
接着我们就可以使用AppInjector处理注解。
/**
* Helper class to automatically inject fragments if they implement [Injectable].
*/
object AppInjector {
fun init(githubApp: LocalApplication) {
DaggerAppComponent.builder().application(githubApp)
.build().inject(githubApp)
}
}
第四步:声明注入方法
对VM、Repository等构造方法声明注入,告诉dagger如何去实例化对象。
class UserRepository @Inject constructor() {
}
class LoginViewModel @Inject constructor(private val repository: UserRepository): ObservableViewModel() {}
最后,在Activity中使用就行了。
class LoginActivity : AppCompatActivity() {
@Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
/**
* ViewModel to provide data for Views.
*/
private val mLoginViewModel: LoginViewModel by viewModels {
viewModelFactory
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
AndroidInjection.inject(this)
}
}
针对Activity中包含Fragment的情形实现HasSupportFragmentInjector并按上第三步添加fragment、AndroidSupportInjection.inject(fragment)即可。
@Inject
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Fragment>
override fun supportFragmentInjector() = dispatchingAndroidInjector
说实话其实dagger集成起来挺繁琐并容易出错的,关键要细心,但是集成之后使用起来就会非常方便。
好了,今天就讲这么多吧,有问题的话可以在下方留言,最后附上git。