文章的标题很简陋,为的是能在搜索引擎里面快速找到这篇文章。
摘要
本文内容简单,不涉及相关内容的技术解释,只是一篇教你如何把一些新鲜的东西应用到自己的项目中或者作为尝鲜。
代码参考自 Google 的 GithubBrowserSample。在此基础上,用 Kotlin 构建一个更简单的项目。关于如何构建一个 MVVM 项目,可以参考简书作者 Kelin 的《如何构建Android MVVM应用程序》,本文重点在于如何将 Dagger 2 引入项目并成功编译。引入 Daager 2 的难点在于注解和 Java 与 Kotlin 之间的转换,跟着我的步骤能确保你的项目能跑起来。
准备工作
- 确保你的 Android Studio 能够正常编译 Kotlin
- 在 Module 里面的 build.gradle 文件中添加
#放在文件顶部
apply plugin: 'kotlin-kapt'
#放在 android {} 里外均可
kapt {
generateStubs = true
}
- 在依赖中添加
implementation 'com.google.dagger:dagger:2.13'
implementation 'com.google.dagger:dagger-android-support:2.13'
kapt 'com.google.dagger:dagger-compiler:2.13'
kapt 'com.google.dagger:dagger-android-processor:2.13'
开始
代码的顺序按照先定义后使用的顺序,避免你在创建过程中缺少相应的类。
首先,创建一个继承自 DaggerApplication 类的 App 类。
class App : DaggerApplication() {
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}
接着,来编写 ViewModel 的模块。在 ViewModel 模块编写之前,需要增加一些注解。
@MustBeDocumented
@kotlin.annotation.Target(AnnotationTarget.FUNCTION)
@kotlin.annotation.Retention
@MapKey
internal annotation class ViewModelKey(val value: KClass<out ViewModel>)
接下来就可以创建 ViewModel 的模块。这里用到的 MyViewModel 和 MyViewModelFactory 替换成你实现好的类。这里暂不给出 MyViewModel 和 MyViewModelFactory 的实现,你可以用简单的类替代它们。
@Module
abstract class ViewModelModule {
@Binds
@IntoMap
@ViewModelKey(TimelineViewModel::class)
abstract fun bindTimelineViewModel(myViewModel: MyViewModel): ViewModel
@Binds
abstract fun bindViewModelFactory(myModelFactory: MyViewModelFactory): MyViewModelProvider.Factory
}
下面是两个简单 Scope 的注解。
@Scope
@Retention(AnnotationRetention.RUNTIME)
annotation class ActivityScope
@Scope
@Retention(AnnotationRetention.RUNTIME)
annotation class FragmentScope
创建好注解后,接下来就是 Activity 和 Fragment 的部分。
'com.google.dagger:dagger-android-support:2.13'
中添加了基本组件对 Dagger 2 的支持,修改你的 Activity 和 Fragment ,使之继承自 DaggerAppCompatActivity 和 DaggerFragment 。让 Android 自动帮你完成基本组件的注入。
@SuppressLint("Registered")
open class BaseActivity : DaggerAppCompatActivity()
open class BaseFragment : DaggerFragment()
然后,创建一个 AppComponent。在 Module 中加入前面创建的 AppModule 和 ActivityBindingModule。
需要注意:不要忘了添加 AndroidSupportInjectionModule 这个模块,如果你在上一步让你的 BaseFragment 继承了 DaggerFragment ,需要对应好 Fragment 的版本来决定使用 AndroidSupportInjectionModule 还是 AndroidInjectionModule。
@Singleton
@Component(modules = [
(AndroidSupportInjectionModule::class),
(AppModule::class),
(ActivityBindingModule::class)])
interface AppComponent : AndroidInjector<App> {
@Component.Builder
interface Builder {
@BindsInstance
fun application(application: Application): Builder
fun build(): AppComponent
}
}
如果已经按照上面的步骤添加好对应的类,按下 Ctrl + F9,让 Android Studio 来编译这些文件,前面添加的 Kapt 插件能够根据注解生成对应的 Dagger 类。
编译完成后,回到最初的 App 类中,把 TODO 的内容替换成:
class App : DaggerApplication() {
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
return DaggerAppComponent.builder().application(this).build()
}
}
DaggerAppComponent 这个类是由 Kapt 插件生成的,命名规则是:Dagger+类名。如果你没有创建上一步的 AppComponent 类,Android Studio 会提示没有这个类。
到此,Dagger 的配置基本完成了。接下来就是让配置 ViewModel和 ViewModelFactory 。
ViewModel和 ViewModelFactory
ViewModel的实现比较简单,只需要添加一个 @Inject Constructor。
class MyViewModel @Inject constructor() : ViewModel() {}
可以根据你的需要,在 constructor() 里面加入 Repository。
ViewModelFactory 的实现比较关键,因为这里需要用到一个关键的注解:@JvmSuppressWildcards
。这个注解的作用是:使得被标记元素的泛型参数不会被编译成通配符。缺少这个注解会让你在编译时报错:
[dagger.android.AndroidInjector.inject(T)] java.util.Map<java.lang.Class<? extends android.arch.lifecycle.ViewModel>,? extends javax.inject.Provider<android.arch.lifecycle.ViewModel>> cannot be provided without an @Provides-annotated method.
如果你使用的是 Java 就不会遇到这个错误。但是 Kotlin 会遇到这个泛型通配符的问题。
给出 MyViewModelFactory 的实现:
@Singleton
@JvmSuppressWildcards
class MyViewModelFactory @Inject constructor(private val creators: Map<Class<out ViewModel>, Provider<ViewModel>>) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
var creator: Provider<out ViewModel>? = creators[modelClass]
if (creator == null) {
for ((key, value) in creators) {
if (modelClass.isAssignableFrom(key)) {
creator = value
break
}
}
}
if (creator == null) {
throw IllegalArgumentException("unknown model class $modelClass")
}
try {
@Suppress("UNCHECKED_CAST")
return creator.get() as T
} catch (e: Exception) {
throw RuntimeException(e)
}
}
}
不出意外的话,配置基本完成。
网友评论