Dagger 2 在我的视图模型中从自定义作用域注入依赖项。

huangapple go评论62阅读模式
英文:

Dagger 2 Inject Dependency from custom scope inside my viewmodel

问题

I'm here to help with the translation. Here's the translated code:

我正在尝试为我的一些对象创建自定义范围以便在视图模型和应用程序中的其他地方使用它们而这些地方不属于此范围内

这是我的组件和子组件的样子
```kotlin
@Singleton
@Component(modules = [
    AndroidSupportInjectionModule::class,
    AppModule::class,
    ViewModelModule::class,
    AppSubComponents::class
])
internal interface AppComponent : AndroidInjector<MyApplication> {
    @Component.Factory
    abstract class Factory : AndroidInjector.Factory<MyApplication>
}

@Module(subcomponents = [SessionComponent::class])
abstract class AppSubComponents

@SessionScope
@Subcomponent(modules = [SessionModule::class])
interface SessionComponent {

    @Subcomponent.Factory
    interface Factory {
        fun create(): SessionComponent
    }
}

我的 ViewModelModule:

@Module
abstract class ViewModelModule {

    @Binds
    internal abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory

    @Binds
    @IntoMap
    @ViewModelKey(MyViewModelImpl::class)
    internal abstract fun bindMyViewModelImpl(viewModel: MyViewModelImpl): ViewModel
}

以及我的 SessionModule:

@Module
class SessionModule {

    @Provides
    @SessionScope
    fun providesTestInjectClass() : TestInjectClass = TestInjectClass()
}

ViewModel 类通过构造函数注入,依赖于 TestInjectClass,并且没有使用范围注解:

class MyViewModelImpl @Inject constructor(
        private val testInject: TestInjectClass
) : ViewModel() {
    // ...
}

现在,在我的 Fragment 中,我按预期实例化我的视图模型:
viewModel = ViewModelProvider(requireActivity(), viewModelFactory)[MyViewModelImpl::class.java]

但是,我遇到了以下错误:

错误:[Dagger/MissingBinding] 无法提供 com.example.TestInjectClass,没有 @Inject 构造函数或 @Provides 注解的方法。

它还提供了如何需要此依赖项的依赖路径。

根据我目前的理解,由于我的视图模型没有范围,并且属于主组件,它无法找到针对 SessionScope 范围的 TestInjectClass 的注入逻辑。

是否有一种方法可以修复这个问题,而无需将视图模型范围限定为 SessionScope


Please note that the translated code may still contain some technical terms that are not easily translatable, so you may need to refer to the original English version for precise understanding.

<details>
<summary>英文:</summary>

I am trying to create a custom scope for some of my objects, to use them in viewmodels and other places withing my app that do not belong in this scope.

This is what my component and subcomponent look like

@Singleton
@Component(modules = [
AndroidSupportInjectionModule::class,
AppModule::class,
ViewModelModule::class,
AppSubComponents::class
])
internal interface AppComponent : AndroidInjector<MyApplication> {
@Component.Factory
abstract class Factory : AndroidInjector.Factory<MyApplication>
}

@Module(subcomponents = [SessionComponent::class])
abstract class AppSubComponents

@SessionScope
@Subcomponent(modules = [SessionModule::class])
interface SessionComponent {

@Subcomponent.Factory
interface Factory {
    fun create(): SessionComponent
}

}


My ViewModelModule:


@Module
abstract class ViewModelModule {

@Binds
internal abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory

@Binds
@IntoMap
@ViewModelKey(MyViewModelImpl::class)
internal abstract fun bindMyViewModelImpl(viewModel: MyViewModelImpl): ViewModel

}


and my SessionModule:

@Module
class SessionModule {

@Provides
@SessionScope
fun providesTestInjectClass() : TestInjectClass = TestInjectClass()

}


The viewmodel class is injected by the constructor, depends on `TestInjectClass` and is not annotated with a scope

class MyViewModelImpl @Inject constructor(
private val testInject: TestInjectClass
) : ViewModel() {

...

}


Now inside my Fragment as expected I am instantiating my viewmodel like
`viewModel = ViewModelProvider(requireActivity(), viewModelFactory)[MyViewModelImpl::class.java]`

but I end up with the following error

&gt; error: [Dagger/MissingBinding] com.example.TestInjectClass cannot be
&gt; provided without an @Inject constructor or an @Provides-annotated
&gt; method.

And it gives me the dependency path of how it reached needing this dependency.



From what I have understood so far, because my viewmodel is not scoped and belongs to the main component, it cannot find injection logic for my TestInjectClass that is scoped with `SessionScope`

Is there a way to fix this without making the viewmodel scoped to `SessionScope` ?



</details>


# 答案1
**得分**: 1

SessionComponent 中的绑定不会自动在你的 AppComponent 中生效。部分原因是为了[更好的封装性](https://dagger.dev/dev-guide/subcomponents.html#subcomponents-for-encapsulation),部分原因是因为你可能正在进行作用域控制,以便在同一会话内重用相同的 @SessionScope 对象实例,而 Dagger 不知道如何找到相关的会话(即 SessionComponent 实例),当你请求在 SessionComponent 中绑定的对象时。

值得一提的是,SessionComponent *可以* 使用其父组件(这里是 AppComponent)中绑定的任何东西,无论它们是否具有作用域(@Singleton 这里)。此外,因为你已经在一个模块中安装了子组件,所以你可以在 AppComponent 中的任何地方注入 SessionComponent.Factory。

你将不得不自己管理 SessionComponent 的访问:通过 SessionComponent.Factory,你可以在需要的任何地方创建这些组件实例,并在它们仍然相关时保持它们。另一种方法是创建自己的会话管理器类,该类创建或检索 SessionComponent 实例,并在必要时进行清理。

要使 TestInjectClass 可用,你需要通过 SessionComponent 上的 getter 方法或类似的方式将其公开(比如成员注入,或者提供一个包含 TestInjectClass 的容器对象)。

```kt
@SessionScope
@Subcomponent(modules = [SessionModule::class])
interface SessionComponent {
    fun testInjectClass(): TestInjectClass

    // [factory here]
}

如果你的 Android 对象图与会话关联良好,你还可以在你的 Activity 或 Fragment 模块/子组件中绑定一个 SessionComponent 绑定,以便你可以直接从这些位置注入一个 SessionComponent。你甚至可以通过查找/获取相关的 SessionComponent 并在其上调用 testInjectClass() 来直接使 TestInjectClass 可用。其中最简单的方式如下:

fun provideTestInjectClass(factory: SessionComponent.Factory) =
    factory.create().testInjectClass()

然而,请记住,每当你请求 Dagger 的 TestInjectClass 时,这将创建一个新的 SessionComponent,因此只有在 TestInjectClass 是你的 SessionComponent 中的主要类并且你不介意小心传递它时,才是正确的选择。

英文:

Bindings within SessionComponent are not automatically available across your AppComponent. This is in part to enable better encapsulation and in part because you're presumably scoping in order to reuse the same @SessionScope object instances within the same session and Dagger has no idea how to find the relevant session (i.e. SessionComponent instance) when you ask for an object bound in SessionComponent.

For what it's worth, SessionComponent can use anything bound in its parent components (AppComponent here) whether or not they have scoping (@Singleton here). Furthermore, because you've installed the subcomponent in a module, you can inject SessionComponent.Factory from anywhere within AppComponent.

You'll have to manage your SessionComponent access yourself: Through your SessionComponent.Factory you can create those component instances wherever you need them and keep them as long as they are relevant. Another way to do this is to create your own session manager class that creates or retrieves SessionComponent instances and cleans them up when necessary.

To make TestInjectClass available, you'll need to expose it through a getter on SessionComponent or other similar means (like members injection or by providing a container object that contains your TestInjectClass).

@SessionScope
@Subcomponent(modules = [SessionModule::class])
interface SessionComponent {
    fun testInjectClass(): TestInjectClass

    // [factory here]
}

If your Android object graph tracks well to sessions, you can also bind a SessionComponent binding within your Activity or Fragment module/subcomponent so you can directly inject a SessionComponent from those places. You could even make TestInjectClass available directly by finding/fetching a relevant SessionComponent and calling testInjectClass() on it. The simplest of those would look like this:

fun provideTestInjectClass(factory: SessionComponent.Factory) =
    factory.create().testInjectClass()

However, remember that that's going to create a new SessionComponent every time you ask Dagger for a TestInjectClass, so it's probably only the right call if TestInjectClass is the main class in your SessionComponent and you don't mind passing it around carefully.

huangapple
  • 本文由 发表于 2023年5月17日 23:42:37
  • 转载请务必保留本文链接:https://go.coder-hub.com/76273898.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定