在 Android 中使用 Dagger-Hilt 在 ViewModel 中进行辅助依赖注入
如果你在你的 android 项目中使用 Dagger-Hilt Dependency Injection,那么你一定遇到过需要手动提供一些参数来构造一个对象以便注入它的情况。通常,当我们在一个类中注入一个依赖项/对象时,Dagger-Hilt 框架通过自动将指定的参数传递给它来在幕后构造它,但是当 dagger-hilt 不知道如何注入它们和我们想要的一些依赖项时怎么办由我们自己在运行时手动提供这些依赖项。
示例:当我们创建 ViewModel 对象时,在 viewmodel 的构造函数中我们指定了所需的依赖项(它可能需要您的网络存储库对象和您的本地数据库/DAO 对象等)由 dagger-hilt 注入以构造它。现在让我们假设当 dagger-hilt 在 viewmodel 中注入所需的指定依赖项时,您还想在运行时在 viewmodel 中手动传递一些对象/依赖项来构造它……那么,你会怎么做呢?
因此,在本文中,我们将看到如何在运行时手动辅助一些依赖项以及 dagger-hilt 提供的依赖项。如果您想了解如何设置/使用 Dagger-Hilt 依赖注入,请首先参考上面标记的 Dagger-Hilt 文章。
通过示例了解如何在 ViewModel 的情况下使用辅助注入
假设我们有 ArticlesFeedViewModel 将被注入到 ArticlesFeedFragment 中。现在假设这个片段将出现在屏幕上,您需要获取您已发布的所有文章的列表,并且必须将它们显示到 UI 中并进行 API 调用,我们正在调用 ArticlesFeedViewModel 的 init{ } 中存在的 getPublishedArticles()函数块,以便在此视图模型初始化后立即调用 api。现在 ArticlesFeedViewModel 的 getPublishedArticles() 将进一步调用 ArticlesRepository 中存在的函数以开始进行网络调用,因此我们在此视图模型中也需要一个 ArticlesRepository 对象,并且我们在 Dagger-Hilt 的帮助下将 ArticlesRepository 注入视图模型的构造函数中。
但是您是否注意到我们还有一个名为 userId 并使用 @Assisted 注释的对象/依赖项?实际上,我们需要在运行时在 getPublishedArticles()函数中传递该 userId 以获取您发布的文章,并且一旦在该片段中实例化视图模型,我们就会从 ArticlesFeedFragment 中获取 ArticleViewModel 中的此 userId。但是如何?我们稍后会看到这一点,让我们首先更好地理解 viewmodel 中的代码。
了解 ViewModel 的代码
与普通注射相比,在辅助注射的情况下,我们需要做一些改变:
- 我们使用@AssistedInject 而不是@Inject 进行构造函数注入。
- 需要在运行时提供的参数使用@Assisted 进行注释。
现在,在进行了上述更改之后,我们仍然不能直接将我们的视图模型注入片段/活动中,我们首先需要为它创建一个工厂,因为我们将使用该工厂来创建实际类的实例,即在我们的例子中的视图模型。 (请注意,我们为我们的类/视图模型创建了一个工厂作为接口)
那么,现在让我们谈谈工厂:
- 我们用@AssistedFactory 注释我们的工厂。该注释告诉 Dagger-Hilt 该接口用于创建需要辅助注入的类/视图模型的实例。
- 在这个工厂中,我们创建了一个名为“create”的函数,它将负责返回我们的类的一个实例,即 ArticlesViewModel,并且只接受那些在运行时由我们提供/协助的参数(即 userId),这意味着参数注释为@协助。
现在我们将创建一个名为 providersFactory() 的工厂提供程序函数,它将提供 ViewModelProvider.Factory 并且我们将在其中包含匿名类,该类将再次覆盖创建我们已经实现的函数以实际创建视图模型的实例。请注意,providesFactory()函数将需要在运行时提供 assistedFactory 和所有辅助参数。
Kotlin
// We are using @AssistedInject instead
// of normal @Inject as we want
// to assist one dependency by our own
// at runtime and rest by dagger-hilt
class ArticlesFeedViewModel @AssistedInject constructor(
private val articlesRepo: ArticlesRepository,
// dependency which is to be assisted by
// us should be annotated with @Assisted
@Assisted
private val userId: String
) : ViewModel {
// It's a factory of this viewmodel, we need
// to annotate this factory interface
// with @AssistedFactory in order to
// let Dagger-Hilt know about it
@AssistedFactory
interface ArticlesFeedViewModelFactory {
fun create(userId: String): ArticlesFeedViewModel
}
// Suppressing unchecked cast warning
@Suppress("UNCHECKED_CAST")
companion object {
// putting this function inside
// companion object so that we can
// access it from outside i.e. from fragment/activity
fun providesFactory(
assistedFactory: ArticlesFeedViewModelFactory,
userId: String
): ViewModelProvider.Factory = object : ViewModelProvider.Factory {
override fun create(modelClass: Class): T {
// using our ArticlesFeedViewModelFactory's create function
// to actually create our viewmodel instance
return assistedFactory.create(userId) as T
}
}
}
init {
// Calling getPublishedArticles() function
// in viewmodel's init block
// because init block just gets executed
// after primary constructor
// So, actually this api will get called as soon
// as viewmodel gets instantiated
getPublishedArticles(userId)
}
private fun getPublishedArticles(userId: String) {
// Just for sack of example we are just
// simply storing response in a variable
val response = articlesRepo.getPublishedArticles(userId).cachedIn(viewModelScope)
}
Kotlin
@AndroidEntryPoint
class ArticlesFeedFragment : Fragment() {
// using viewbinding
private var _binding: FragmentArticlesFeedBinding? = null
private val binding get() = _binding!!
// Using navargs for getting argument
// supplied from previous fragment
private val args: ArticlesFeedFragmentArgs by navArgs()
// First injecting our
// viewmodel's factory interface
@Inject
lateinit var articlesFeedViewModelFactory: ArticlesFeedViewModel.ArticlesFeedViewModelFactory
// Creating viewmodel here with
// the help of kotlin delegate property "by"
private val viewModel: ArticlesFeedViewModel by viewModels {
ArticlesFeedViewModel.providesFactory(
assistedFactory = articlesFeedViewModelFactory,
userId = args.userId
)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentArticlesFeedBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Do something here
// You can observe/handle your api response
// here coming through ArticlesViewModel
}
注入 Viewmodel 的工厂和 viewmodel 本身
在此示例中,我们使用 Jetpack 导航组件进行片段导航。现在假设我们正在从 HomeFragment 导航到 ArticlesFeedFragment,并且我们使用 Safe Args 将 userId 从 HomeFragment 发送到 ArticlesFeedFragment。
所以,首先我们注入了我们的 ArticlesViewModel 的工厂。现在我们将在 kotlin 委托属性“by”的帮助下实例化我们的视图模型,然后我们将在其中调用 ArticlesFeedViewModel.providesFactory() fun,这将需要辅助工厂,即我们上面注入的 ArticlesFeedViewModelFactory 和手动辅助的 userId,我们正在使用安全参数的帮助。
科特林
@AndroidEntryPoint
class ArticlesFeedFragment : Fragment() {
// using viewbinding
private var _binding: FragmentArticlesFeedBinding? = null
private val binding get() = _binding!!
// Using navargs for getting argument
// supplied from previous fragment
private val args: ArticlesFeedFragmentArgs by navArgs()
// First injecting our
// viewmodel's factory interface
@Inject
lateinit var articlesFeedViewModelFactory: ArticlesFeedViewModel.ArticlesFeedViewModelFactory
// Creating viewmodel here with
// the help of kotlin delegate property "by"
private val viewModel: ArticlesFeedViewModel by viewModels {
ArticlesFeedViewModel.providesFactory(
assistedFactory = articlesFeedViewModelFactory,
userId = args.userId
)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentArticlesFeedBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Do something here
// You can observe/handle your api response
// here coming through ArticlesViewModel
}
因此,通过这种方式,我们将使用在运行时提供的辅助依赖 userId 注入 ViewModel。这就是我们如何使用带有 Dagger-Hilt 的辅助依赖注入,它允许我们在运行时传递参数来构造一个对象/依赖项。