Dagger Hilt 教程 - 分步指南
在 Android 项目上工作,我们需要集成很多不同的依赖项,为了管理这些依赖项,我们使用像 Dagger 这样的依赖注入框架。
但是要设置和使用 Dagger 需要大量的样板代码和非常陡峭的学习曲线。它就像没有 Android 支持的 Dagger 的原始版本。然后 Dagger-Android 来了,它减少了样板代码但没有成功。
现在,随着 Dagger-Hilt 作为 Jetpack 库的一部分发布,它现在是 Google 推荐的使用方式。根据 Dagger-Hilt 的说法,它可以帮助我们:
- 使Dagger代码对开发人员来说简单易行。
- 为不同的构建类型提供一组不同的绑定。
- 只需注意在哪里注入依赖项并休息所有代码生成都由 dagger 本身通过使用注释来完成,从而删除所有样板代码。
在本文中,我们将学习:
- 了解Dagger
- 建立一个新项目
- 项目结构
- 整合 Dagger-Hilt
- 带有 Dagger-Hilt 的 WorkManager
- 限定符
那么,让我们开始学习吧。
了解Dagger
在开始使用 Dagger-Hilt 之前,我们需要了解 Dagger 的基础知识。在本节中,我们将帮助您了解 Dagger 及其术语。
基本上,要理解 Dagger 我们必须理解 4 个主要注解,
- 模块
- 零件
- 提供
- 注入
为了以基本的方式更好地理解它,请将模块视为依赖项的提供者,并将活动或任何其他类视为消费者。现在为了提供从提供者到消费者的依赖关系,我们在它们之间建立了一座桥梁,在 Dagger 中,组件作为特定的桥梁工作。
现在,一个模块就是一个类,我们用 @Module 注释它,以便 Dagger 将其理解为模块。
组件是一个接口,它使用@Component 注释并在其中接受模块。(但现在,Dagger-Hilt 中不需要这个注解)
Provides 是在 Module 类中用于提供依赖关系的注解,Inject 是一个注解,用于定义消费者内部的依赖关系。
强烈建议在迁移到 Dagger-Hilt 之前了解原始 Dagger。
如果你是 Dagger 的新手,并且想详细了解这些东西,我建议你观看这个视频。
如果您已经了解 Dagger 的基础知识,则可以跳过该视频。
建立一个新项目
在这里,我们将设置 Android 项目。
我们的最终项目可以在这里找到。
创建项目
-
启动一个新的 Android Studio 项目
-
选择空活动和下一步
-
名称: Dagger-Hilt-Tutorial
-
包名:com.mindorks.framework.mvvm
-
语言:kotlin
-
结束
-
您的起始项目现已准备就绪
添加依赖项
在应用程序的 build.gradle 文件中添加以下依赖项,
implementation "androidx.recyclerview:recyclerview:{latest-version}"
implementation 'android.arch.lifecycle:extensions:{latest-version}'
implementation 'com.github.bumptech.glide:glide:{latest-version}'
implementation 'androidx.activity:activity-ktx:{latest-version}'
现在我们的项目已经准备好依赖了。
项目结构
对于该项目,我们将遵循 MVVM 的基本版本。我们在项目中的包如下所示:
我们需要枚举来表示 UI 状态。我们将在 utils 包中创建它。
package com.mindorks.framework.mvvm.utils
enum class Status {
SUCCESS,
ERROR,
LOADING
}
我们需要一个实用程序类来负责将网络调用的当前状态传达给 UI 层。我们将其命名为资源。因此,在同一个 utils 包中创建一个 Kotlin 数据类 Resource 并添加以下代码。
package com.mindorks.framework.mvvm.utils
data class Resource<out T>(val status: Status, val data: T?, val message: String?) {
companion object {
fun <T> success(data: T?): Resource<T> {
return Resource(Status.SUCCESS, data, null)
}
fun <T> error(msg: String, data: T?): Resource<T> {
return Resource(Status.ERROR, data, msg)
}
fun <T> loading(data: T?): Resource<T> {
return Resource(Status.LOADING, data, null)
}
}
}
我们的 utils 包现在已经准备好了。
整合 Dagger-Hilt
要在项目中设置 Dagger,我们将在应用程序的 build.gradle 文件中添加以下内容,
implementation 'com.google.dagger:hilt-android:{latest-version}'
kapt 'com.google.dagger:hilt-android-compiler:{latest-version}'
然后作为下一步,我们将在应用程序的 build.gradle 顶部应用dagger.hilt插件,就像,
apply plugin: 'dagger.hilt.android.plugin'
最后,我们将在项目的 build.gradle 的类路径中添加以下内容,例如,
classpath "com.google.dagger:hilt-android-gradle-plugin:{latest-version}"
这是开始在项目中使用 Dagger-Hilt 所需的设置。
设置Dagger-Hilt
我们将逐步打破项目中设置Dagger-Hilt的步骤。
步骤 01:
我们将首先更新我们的应用程序类应用程序,例如,
class App : Application()
我们将更新 Manifest 文件,例如,
android:name=".App"
现在,要开始使用 Dagger,我们需要使用@HiltAndroidApp注释应用程序类。更新后的代码看起来像,
@HiltAndroidApp
class App : Application()
如果您打算在您的应用程序中使用 Dagger-Hilt,则上述步骤是强制性的。它生成了我们在使用 Dagger 时必须手动完成的所有组件类。
步骤 02:
现在,我们将在应用程序的 build.gradle 中添加 Retrofit 和Kotlin-Coroutines的依赖项,例如,
// Networking implementation "com.squareup.retrofit2:retrofit:{latest-version}"
implementation "com.squareup.retrofit2:converter-moshi:{latest-version}"
implementation "com.squareup.okhttp3:okhttp:{latest-version}"
implementation "com.squareup.okhttp3:logging-interceptor:{latest-version}" // Coroutine implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:{latest-version}"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:{latest-version}"
现在,在项目中,我们将执行 API 调用并显示用户列表。我们还将使用 Kotlin-Coroutine 进行多线程处理。
现在,我们将在数据层中创建api、model、repository包。它会有类似的文件,
然后,ApiService 看起来像,
interface ApiService {
@GET("users")
suspend fun getUsers(): Response<List<User>>
}
ApiHelper 看起来像,
interface ApiHelper {
suspend fun getUsers(): Response<List<User>>
}
最后,在 ApiHelperImpl 中,我们将使用@Inject在构造函数中注入 ApiService并实现 ApiHelper。
class ApiHelperImpl @Inject constructor(private val apiService: ApiService) : ApiHelper {
override suspend fun getUsers(): Response<List<User>> = apiService.getUsers()
}
在这里,@Inject 有助于在构造函数本身中传递 ApiHelperImpl 所需的依赖项。
用户数据类看起来像,
data class User(
@Json(name = "id")
val id: Int = 0,
@Json(name = "name")
val name: String = "",
@Json(name = "email")
val email: String = "",
@Json(name = "avatar")
val avatar: String = ""
)
最后,在MainRepository中,我们将在存储库的构造函数中传递 ApiHelper。MainRepository看起来像,
class MainRepository @Inject constructor(private val apiHelper: ApiHelper) {
suspend fun getUsers() = apiHelper.getUsers()
}
现在,如果您可以看到我们分别在MainRepository和ApiHelperImpl中传递了ApiHelper和ApiService。因此,要在构造函数中注入所有内容,我们还需要在 Dagger 中使用@Provide注释来提供它。
步骤 03:
现在,我们将创建一个包di -> module,并在其中创建 ApplicationModule。如您所见,我们没有创建 ApplicationComponent,因为我们将使用 Dagger-Hilt 本身提供的组件。
我们将创建一个类 ApplicationModule 并使用@Module 对其进行注释。使用这个注解会让dagger明白这个类是一个模块。
@Module
class ApplicationModule { }
现在,我们需要将此模块类插入特定组件中。在这种情况下,我们需要在应用程序级别执行此操作,因此我们将其安装在 ApplicationComponent 中,例如,
@Module
@InstallIn(ApplicationComponent::class)
class ApplicationModule {}
在这里,您可以看到我们已经使用@InstallIn注解将其安装在ApplicationComponent 中。ApplicationComponent 由 Dagger-Hilt 提供。
这意味着此处提供的依赖项将在整个应用程序中使用。假设我们要在安装模块的活动级别使用,
@InstallIn(ActivityComponent::class)
与 ApplicationComponent/ActivityComponent 类似,我们有不同类型的组件,例如,
FragmentComponent 为 Fragments,ServiceComponent 为 Service 等。
步骤 04:
现在,在 ApplicationModule 内部,我们将一一提供所有依赖项,ApplicationModule 类的更新代码如下所示,
@Module
@InstallIn(ApplicationComponent::class)
class ApplicationModule {
@Provides
fun provideBaseUrl() = BuildConfig.BASE_URL
@Provides
@Singleton
fun provideOkHttpClient() = if (BuildConfig.DEBUG) {
val loggingInterceptor = HttpLoggingInterceptor()
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY)
OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.build()
} else OkHttpClient
.Builder()
.build()
@Provides
@Singleton
fun provideRetrofit(okHttpClient: OkHttpClient, BASE_URL: String): Retrofit =
Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create())
.baseUrl(BASE_URL)
.client(okHttpClient)
.build()
@Provides
@Singleton
fun provideApiService(retrofit: Retrofit) = retrofit.create(ApiService::class.java)
@Provides
@Singleton
fun provideApiHelper(apiHelper: ApiHelperImpl): ApiHelper = apiHelper
}
在这里,我们使用@Provide注释提供了依赖项,可以跨应用程序访问。
@Singleton注释有助于在整个应用程序中创建和使用一次实例。
类似地,就像一直到应用程序生命周期的 Singleton 一样,我们也有 @ActivityScoped、@FragmentScoped 等,其中依赖关系的范围一直到 Activity 和 Fragment 的生命周期。
现在,如果你还记得在上一步中,我们分别在 MainRepository 和 ApiHelperImpl 中传递了 ApiHelper 和ApiService ,并且要成功注入它,我们需要提供这两个依赖项。
在 ApplicationModule 中,最后两个函数 即provideApiService和provideApiHelper提供 和 的ApiService实例ApiHelper。
此外,对于BASE_URLdefaultConfig ,我们将在应用程序的 build.gradle 文件的块中添加以下内容,
buildConfigField 'String', 'BASE_URL', "\"https://5e510330f2c0d300147c034c.mockapi.io/\""
步骤 05:
现在,既然一切都设置好了,现在我们需要在 Android 类中使用/注入它们。在我们的例子中,我们需要我们的活动来开始使用它们。
因此,要使我们使用的 Dagger-Hilt 支持任何 Android 类,
@AndroidEntryPoint
因此,在我们的代码中,我们将创建另一个包ui,并在其中创建另一个名为main的子包,其中包含 MainActivity、MainViewModel 和 MainAdapter 以显示用户列表。
现在,我们将AndroidEntryPoint在 MainActivity 中添加注释,例如,
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {}
在这里,@AndroidEntryPoint意味着 Dagger-Hilt 现在可以在此类中注入依赖项。
@AndroidEntryPoint注解可用于,
-
活动
-
分段
-
看法
-
服务
-
广播接收器
Hilt 目前仅支持ComponentActivity扩展 androidx 库的活动和片段Fragment.
步骤 06:
MainActivity 看起来像,
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private val mainViewModel : MainViewModel by viewModels()
private lateinit var adapter: MainAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setupUI()
setupObserver()
}
private fun setupUI() {
recyclerView.layoutManager = LinearLayoutManager(this)
adapter = MainAdapter(arrayListOf())
recyclerView.addItemDecoration(
DividerItemDecoration(
recyclerView.context,
(recyclerView.layoutManager as LinearLayoutManager).orientation
)
)
recyclerView.adapter = adapter
}
private fun setupObserver() {
mainViewModel.users.observe(this, Observer {
when (it.status) {
Status.SUCCESS -> {
progressBar.visibility = View.GONE
it.data?.let { users -> renderList(users) }
recyclerView.visibility = View.VISIBLE
}
Status.LOADING -> {
progressBar.visibility = View.VISIBLE
recyclerView.visibility = View.GONE
}
Status.ERROR -> {
progressBar.visibility = View.GONE
Toast.makeText(this, it.message, Toast.LENGTH_LONG).show()
}
}
})
}
private fun renderList(users: List<User>) {
adapter.addData(users)
adapter.notifyDataSetChanged()
}
}
MainAdapter 类看起来像,
class MainAdapter(
private val users: ArrayList<User>
) : RecyclerView.Adapter<MainAdapter.DataViewHolder>() {
class DataViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(user: User) {
itemView.textViewUserName.text = user.name
itemView.textViewUserEmail.text = user.email
Glide.with(itemView.imageViewAvatar.context)
.load(user.avatar)
.into(itemView.imageViewAvatar)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
DataViewHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.item_layout, parent,
false
)
)
override fun getItemCount(): Int = users.size
override fun onBindViewHolder(holder: DataViewHolder, position: Int) =
holder.bind(users[position])
fun addData(list: List<User>) {
users.addAll(list)
}
}
在这里,您可以看到 MainViewModel 用于管理数据更改。
步骤 07:
在这里,我们要在 ViewModel 的构造函数中传递以下内容,
private val mainRepository: MainRepository
private val networkHelper: NetworkHelper
要通过它,我们需要首先创建一个 NetworkHelper,例如,
@Singleton
class NetworkHelper @Inject constructor(@ApplicationContext private val context: Context) {
fun isNetworkConnected(): Boolean {
var result = false
val connectivityManager =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val networkCapabilities = connectivityManager.activeNetwork ?: return false
val activeNetwork =
connectivityManager.getNetworkCapabilities(networkCapabilities) ?: return false
result = when {
activeNetwork.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
activeNetwork.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true
activeNetwork.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true
else -> false
}
} else {
connectivityManager.run {
connectivityManager.activeNetworkInfo?.run {
result = when (type) {
ConnectivityManager.TYPE_WIFI -> true
ConnectivityManager.TYPE_MOBILE -> true
ConnectivityManager.TYPE_ETHERNET -> true
else -> false
}
}
}
}
return result
}
}
在这里,您可以看到我们正在 NetworkHelper 的构造函数中传递上下文。我们还在此处使用@ApplicationContext注释上下文,这意味着context我们将使用context应用程序的上下文。
注意:如果我们想应用 Activity 的上下文,我们可以使用必须在模块中提供的@ActivityContext 。
步骤 08:
现在,我们必须在 MainViewModel 中传递 NetworkHelper 和 MainRepository。Dagger-Hilt 不直接支持 ViewModel,为了在 ViewModel 中使用 Dagger-Hilt,我们使用 Jetpack Extensions。
首先,我们需要在 gradle 中为 Jetpack 扩展设置依赖项。
让我们在应用程序的 build.gradle 中添加以下内容,例如,
implementation 'androidx.hilt:hilt-lifecycle-viewmodel:{latest-version}'
kapt 'androidx.hilt:hilt-compiler:{latest-version}'
为了支持 kapt,我们将在 app 的 build.gradle 中添加 kapt 的支持插件,如下所示,
apply plugin: 'kotlin-kapt'
现在,要传递 NetworkHelper 和 MainRepository,我们不会在这里使用 ViewModelFactory,而是直接传递它们并使用 @ViewModelInject 注释,例如,
class MainViewModel @ViewModelInject constructor(
private val mainRepository: MainRepository,
private val networkHelper: NetworkHelper
) : ViewModel() {
}
在这里,ViewModelInject 注解将使用构造函数注入依赖项,现在我们将在 MainViewModel 中执行操作,例如,
class MainViewModel @ViewModelInject constructor(
private val mainRepository: MainRepository,
private val networkHelper: NetworkHelper
) : ViewModel() {
private val _users = MutableLiveData<Resource<List<User>>>()
val users: LiveData<Resource<List<User>>>
get() = _users
init {
fetchUsers()
}
private fun fetchUsers() {
viewModelScope.launch {
_users.postValue(Resource.loading(null))
if (networkHelper.isNetworkConnected()) {
mainRepository.getUsers().let {
if (it.isSuccessful) {
_users.postValue(Resource.success(it.body()))
} else _users.postValue(Resource.error(it.errorBody().toString(), null))
}
} else _users.postValue(Resource.error("No internet connection", null))
}
}
}
在这里,我们在 init 块和 viewModelScope 中获取用户,我们将检查互联网连接,如果连接正常,则我们通过 API 调用,否则我们将值设置为 LiveData 并出现错误。
然后在 MainActivity 中观察此用户 LiveData 以显示 recyclerView 中的项目。
如果您在上述步骤中看到,我们通过使用获取 ViewModel 的实例by viewModels()
@ViewModelInject 注解的 ViewModel 只能被 @AndroidEntryPoint 注解的 Views 引用
作为最后一步,在您的清单文件中添加以下权限,
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
现在,我们已经完成了项目的设置,如果您运行该项目,您将在 recyclerView 中看到用户列表。
这样我们就可以在我们的 Android 项目中实现 dagger-hilt。
现在,让我们了解在我们的 Android 应用程序开发过程中可能出现的更多可能性。
带Dagger-Hilt的 WorkManger
我们如何使用 Dagger-Hilt 和 WorkManager?
如果我们使用 WorkManger,我们使用 @WorkerInject 使用 Jetpack Extensions 在构造函数中注入依赖项。
我们还需要为 WorkManager 添加以下依赖项,
implementation 'androidx.hilt:hilt-work:{latest-version}'
限定符
考虑一个例子,我们有两个函数返回字符串值。但是当通过 Dagger 提供它时,dagger 怎么知道哪个类需要哪个字符串值,因为它们都是相同的类型。
为了解决这个问题,我们在 Dagger 中使用了限定符。
考虑一个示例,我们必须提供两个不同的字符串,一个用于 API 密钥,另一个用于某些库初始化,例如,
@Provides
@Singleton
fun provideApiKey() = "My ApiKey"
@Provides
@Singleton
fun provideLibraryKey() = "My Library Key"
在这里,Dagger-Hilt 永远不会成功构建,因为 dagger 会认为两者相同,因为它们都有字符串作为类型,并且会抛出错误,
error: [Dagger/DuplicateBindings] java.lang.String is bound multiple times:
现在,为了提供相同返回类型的不同类型的实现,我们需要 Dagger-Hilt 使用限定符提供多个绑定。
限定符是一种注释,当该类型定义了多个绑定时,您可以使用它来标识该类型的特定绑定。
现在,要定义一个限定符,我们将在di包中创建一个文件名 qualifier.kt 并将文件更新为,
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class ApiKey
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class LibraryKey
在这里,我们创建了两个不同的注释 ApiKey 和 LibraryKey 并且都被标记为@Qualifier。
这些注释将帮助我们区分ApiKey和LibraryKey的实现。
现在,在 ApplicationModule 中,我们将通过附加我们刚刚创建的注释来更新密钥的提供者,例如,
@ApiKey
@Provides
@Singleton
fun provideApiKey():String = "My ApiKey"
@LibraryKey
@Provides
@Singleton
fun provideLibraryKey():String = "My Library Key"
现在,在这里您可以看到我们为每个字符串提供程序附加了单独的限定符,现在 Dagger-Hilt 将在内部生成代码以提供这些字符串值。
现在,要单独注入它们,我们将转到 MainActivity 并注入字符串,例如,
@ApiKey
@Inject
lateinit var apiKey:String
@LibraryKey
@Inject
lateinit var libraryKey:String
现在,如果我们单独记录它们,我们将得到,
/MainActivity: My ApiKey
/MainActivity: My Library Key
这就是您可以使用限定符提供相同类型的多个依赖项的方式。
如果您还记得在 NetworkHelper 中我们使用了 @ApplicationContext,它也是一种限定符,但由 Dagger-Hilt 本身提供。
这就是你可以使用 Dagger-Hilt 的方式,它是在你的项目中构建在 Dagger 之上的新依赖注入库。