Dagger Hilt 教程 - 分步指南

2022-09-14  本文已影响0人  安安_660c

在 Android 项目上工作,我们需要集成很多不同的依赖项,为了管理这些依赖项,我们使用像 Dagger 这样的依赖注入框架。

但是要设置和使用 Dagger 需要大量的样板代码和非常陡峭的学习曲线。它就像没有 Android 支持的 Dagger 的原始版本。然后 Dagger-Android 来了,它减少了样板代码但没有成功。

现在,随着 Dagger-Hilt 作为 Jetpack 库的一部分发布,它现在是 Google 推荐的使用方式。根据 Dagger-Hilt 的说法,它可以帮助我们:

在本文中,我们将学习:

那么,让我们开始学习吧。

了解Dagger

在开始使用 Dagger-Hilt 之前,我们需要了解 Dagger 的基础知识。在本节中,我们将帮助您了解 Dagger 及其术语。

基本上,要理解 Dagger 我们必须理解 4 个主要注解,

为了以基本的方式更好地理解它,请将模块视为依赖项的提供者,并将活动或任何其他类视为消费者。现在为了提供从提供者到消费者的依赖关系,我们在它们之间建立了一座桥梁,在 Dagger 中,组件作为特定的桥梁工作。

现在,一个模块就是一个类,我们用 @Module 注释它,以便 Dagger 将其理解为模块。

组件是一个接口,它使用@Component 注释并在其中接受模块。(但现在,Dagger-Hilt 中不需要这个注解)

Provides 是在 Module 类中用于提供依赖关系的注解,Inject 是一个注解,用于定义消费者内部的依赖关系。

强烈建议在迁移到 Dagger-Hilt 之前了解原始 Dagger。

如果你是 Dagger 的新手,并且想详细了解这些东西,我建议你观看这个视频。

如果您已经了解 Dagger 的基础知识,则可以跳过该视频。

建立一个新项目

在这里,我们将设置 Android 项目。

我们的最终项目可以在这里找到

创建项目

添加依赖项

在应用程序的 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

上一篇下一篇

猜你喜欢

热点阅读