美文网首页
Dagger Hilt 教程 - 分步指南

Dagger Hilt 教程 - 分步指南

作者: 安安_660c | 来源:发表于2022-09-14 15:40 被阅读0次

    在 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注解可用于,

    1. 活动

    2. 分段

    3. 看法

    4. 服务

    5. 广播接收器

    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 之上的新依赖注入库。

    文章来源:https://blog.mindorks.com/dagger-hilt-tutorial

    相关文章

      网友评论

          本文标题:Dagger Hilt 教程 - 分步指南

          本文链接:https://www.haomeiwen.com/subject/ufarortx.html