Hilt 循环依赖

2023-11-21

我正在使用 Hilt 创建一个宠物项目,也许我遇到这个问题是因为我安装一切都在SingletonComponent::class,也许我应该为每一个创建组件。

宠物项目有一个NetworkModule, UserPrefsModule,当我尝试创建一个时,问题出现了Authenticator for OkHttp3.

这是我的网络模块

@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {

    @Singleton
    @Provides
    fun providesHttpLoggingInterceptor() = HttpLoggingInterceptor()
        .apply {
            if (BuildConfig.DEBUG) level = HttpLoggingInterceptor.Level.BODY
        }

    @Singleton
    @Provides
    fun providesErrorInterceptor(): Interceptor {
        return ErrorInterceptor()
    }

    @Singleton
    @Provides
    fun providesAccessTokenAuthenticator(
        accessTokenRefreshDataSource: AccessTokenRefreshDataSource,
        userPrefsDataSource: UserPrefsDataSource,
    ): Authenticator = AccessTokenAuthenticator(
        accessTokenRefreshDataSource,
        userPrefsDataSource,
    )

    @Singleton
    @Provides
    fun providesOkHttpClient(
        httpLoggingInterceptor: HttpLoggingInterceptor,
        errorInterceptor: ErrorInterceptor,
        authenticator: Authenticator,
    ): OkHttpClient =
        OkHttpClient
            .Builder()
            .authenticator(authenticator)
            .addInterceptor(httpLoggingInterceptor)
            .addInterceptor(errorInterceptor)
            .build()

}

Then my UserPrefsModule is :

@Module
@InstallIn(SingletonComponent::class)
object UserPrefsModule {

    @Singleton
    @Provides
    fun provideSharedPreference(@ApplicationContext context: Context): SharedPreferences {
        return context.getSharedPreferences("user_prefs", Context.MODE_PRIVATE)
    }

    @Singleton
    @Provides
    fun provideUserPrefsDataSource(sharedPreferences: SharedPreferences): UserPrefsDataSource {
        return UserPrefsDataSourceImpl(sharedPreferences)
    }
}

然后我有一个AuthenticatorModule

@Module
@InstallIn(SingletonComponent::class)
object AuthenticationModule {

    private const val BASE_URL = "http://10.0.2.2:8080/"

    @Singleton
    @Provides
    fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit = Retrofit.Builder()
        .addConverterFactory(MoshiConverterFactory.create())
        .baseUrl(BASE_URL)
        .client(okHttpClient)
        .build()

    @Singleton
    @Provides
    fun provideApiService(retrofit: Retrofit): AuthenticationService =
        retrofit.create(AuthenticationService::class.java)


    @Singleton
    @Provides
    fun providesAccessTokenRefreshDataSource(
        userPrefsDataSource: UserPrefsDataSource,
        authenticationService: AuthenticationService,
    ): AccessTokenRefreshDataSource = AccessTokenRefreshDataSourceImpl(
        authenticationService, userPrefsDataSource
    )
}

当我创建时问题就开始发生AccessTokenRefreshDataSourceImpl我需要AuthenticationService and UserPrefsDataSource,我收到此错误:

错误:[Dagger/DependencyCycle] 发现依赖循环: 公共抽象静态类 SingletonC 实现 App_GenerateInjector,

对于每个功能,例如登录、登录、验证等。我正在创建一个新的@Module像这样:

@Module
@InstallIn(SingletonComponent::class)
interface SignInModule {

    @Binds
    fun bindIsValidPasswordUseCase(
        isValidPasswordUseCaseImpl: IsValidPasswordUseCaseImpl,
    ): IsValidPasswordUseCase

    @Binds
    fun bindIsValidEmailUseCase(
        isValidEmailUseCase: IsValidEmailUseCaseImpl,
    ): IsValidEmailUseCase

     //Here in that Datasource I'm using the AuthenticationService from AuthenticationModule and it works
    @Binds
    fun bindSignInDataSource(
        signInDataSourceImpl: SignInDataSourceImpl
    ): SignInDataSource
}

的构造函数AccessTokenAutenticator

class AccessTokenAuthenticator @Inject constructor(
    private val accessTokenRefreshDataSource: AccessTokenRefreshDataSource,
    private val userPrefsDataSource: UserPrefsDataSource,
) : Authenticator {

的构造函数AccessTokenRefreshDatasource

class AccessTokenRefreshDataSourceImpl @Inject constructor(
    private val authenticationService: AuthenticationService,
    private val userPrefsDataSource: UserPrefsDataSource,
) : AccessTokenRefreshDataSource {

注意我的所有内容都在@Module按功能分开,以便将来能够模块化应用程序。


在大多数编程语言中,如果您需要 B 的实例来构造 A,并需要 A 的实例来构造 B,那么您将无法构造其中任何一个。

Here:

  • AccessTokenRefreshDataSource 需要 AuthenticationService
  • AuthenticationService 需要 Retrofit
  • 改造需要OkHttpClient
  • OkHttpClient 需要验证器
  • 身份验证器需要 AccessTokenRefreshDataSource

...因此,无论您的模块或组件结构如何,Dagger 都无法首先创建任何这些实例.

但是,如果您的 AccessTokenRefreshDataSourceImpl 不需要在构造函数本身内使用其 AuthenticationService 实例,则可以将其替换为Provider<AuthenticationService>:匕首自动让你注射Provider<T>对于图中的任意 T,其他有用的绑定。这允许 Dagger 创建 AccessTokenRefreshDataSource,而无需首先创建 AuthenticationService,并承诺一旦创建了对象图,您的 AccessTokenRefreshDataSource 就可以接收它所需的单例 AuthenticationService 实例。注入提供程序后,只需调用authenticationServiceProvider.get()在您需要的地方获取实例(大概在构造函数之外)。

当然,您可以在您控制的图表中的其他任何地方使用相同的重构来解决您的问题。 AccessTokenAuthenticator 也是一个合理的重构点,假设您自己编写了它,因此可以修改它的构造函数。


评论中讨论的要点:

  • You can always inject a Provider<T> instead of any binding T in your graph. In addition to being valuable for breaking dependency cycles, it can also be handy if your dependency-injected class needs to instantiate an arbitrary number of that object, or if creating the object takes a lot of memory or classloading and you want to delay it until later. Of course, if the object is cheap to construct without dependency cycles and you expect to call get() exactly once, then you can skip that and directly inject T as you've done here.
    • Provider<T>是一个单方法对象。呼唤get()它与调用类型的 getter 相同T在组件本身上。如果该对象没有作用域,您将得到一个新对象;如果该对象是有作用域的,您将获得 Dagger 存储在组件中的对象。

    • 一般来说,您只需注入 Provider 并调用即可get直接在上面:

      class AccessTokenRefreshDataSourceImpl @Inject constructor(
        private val authenticationServiceProvider:
          Provider<AuthenticationService>,
        private val userPrefsDataSource: UserPrefsDataSource,
      ) : AccessTokenRefreshDataSource {
      

      ...然后而不是使用this.authenticationService.someMethod()直接使用this.authenticationServiceProvider.get().someMethod()。 Ma3x在评论中指出,如果你声明val authenticationService get() = authenticationServiceProvider.get()作为一个类字段,Kotlin 可以抽象出以下事实:get()涉及,您不需要对 AccessTokenRefreshDataSourceImpl 进行任何其他更改。

    • 您还需要更改@Provides方法在你的模块中,但只是因为你没有充分利用@InjectAccessTokenRefreshDataSourceImpl 上的注释如下。

      @Singleton
      @Provides
      fun providesAccessTokenRefreshDataSource(
          userPrefsDataSource: UserPrefsDataSource,
          authenticationServiceProvider: Provider<AuthenticationService>,  // here
      ): AccessTokenRefreshDataSource = AccessTokenRefreshDataSourceImpl(
          authenticationServiceProvider /* and here */, userPrefsDataSource
      )
      
  • It is generally not necessary to use @Provides to refer to an @Inject-annotated constructor. @Provides is useful when you can't change the constructor to make it @Inject. @Inject can be less maintenance because then you don't need to copy @Provides method arguments to your constructor; Dagger will do that for you. Read more here.
    • 如果你使用@Inject并删除你的@Provides方法,您可能仍然想使用@Binds指示您的 AccessTokenRefreshDataSource 应绑定到 AccessTokenRefreshDataSourceImpl,不过您需要决定如何放置@Binds and @Provides在同一模块中。在 Java 8 中,你可以通过使你的@Provides方法static并将它们放在一个界面上,但在 Kotlin 中,创建一个嵌套界面并使用它安装可能会更容易@Module(includes = ...). 在这里阅读更多内容。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Hilt 循环依赖 的相关文章

  • 如何在 Android 中保存相机的临时照片?

    在尝试从相机拍照并将其保存到应用程序的缓存文件夹中时 我没有得到任何可见的结果 应用程序不会崩溃 但在 LogCat 上 当我尝试将 ImageView src 字段设置为刚刚获取的文件的 URI 时 我收到此消息 09 17 14 03
  • 类型容器“Android 依赖项”引用不存在的库 android-support-v7-appcompat/bin/android-support-v7-appcompat.jar

    我在尝试在我的项目中使用 Action Bar Compat 支持库时遇到了某种错误 我不知道出了什么问题 因为我已按照此链接中的说明进行操作 gt http developer android com tools support libr
  • 卸载后 Web 应用程序不显示“添加到主屏幕”

    这是我第一次创建网络应用程序 我设法解决了这个问题 所以我得到了实际的 chrome 提示 将其添加到主屏幕 然后我从手机上卸载了该网络应用程序 因为我想将其展示给我的同事 但是 屏幕上不再出现提示 问题 这是有意为之的行为还是我的应用程序
  • CardView 圆角获得意想不到的白色

    When using rounded corner in CardView shows a white border in rounded area which is mostly visible in dark environment F
  • 如何以编程方式检查 AndroidManifest.xml 中是否声明了服务?

    我正在编写一个库 该库提供了一项服务 其他开发人员可以通过将其包含在他们的项目中来使用该服务 因此 我无法控制 AndroidManifest xml 我在文档中解释了要做什么 但一个常见的问题是人们忽略了将适当的 标记添加到其清单中 或者
  • java.lang.NoClassDefFoundError:org.apache.batik.dom.svg.SVGDOMImplementation

    我在链接到我的 Android LibGDX 项目的 Apache Batik 库时遇到了奇怪的问题 但让我们从头开始 在 IntelliJ Idea 中我有一个项目 其中包含三个模块 Main Android 和 Desktop 我强调的
  • Android Activity 生命周期函数基础知识

    我正在测试这段代码 它显示活动所处的状态 public class Activity101Activity extends Activity String tag Lifecycle Called when the activity is
  • 找不到处理意图 com.instagram.share.ADD_TO_STORY 的活动

    在我们的 React Native 应用程序中 我们试图让用户根据视图 组件中的选择直接将特定图像共享到提要或故事 当我们尝试直接使用 com instagram share ADD TO FEED 进行共享时 它以一致的方式完美运行 但是
  • 在画布上绘图

    我正在编写一个 Android 应用程序 它可以在视图的 onDraw 事件上直接绘制到画布上 我正在绘制一些涉及单独绘制每个像素的东西 为此我使用类似的东西 for int x 0 x lt xMax x for int y 0 y lt
  • CollapsingToolBarLayout - 状态栏稀松布颜色不改变

    几天前我更新了我的 android studio 并开始使用 CoordinatorLayout 和 CollapsingToolbarLayout 只是尝试一些东西 工具栏稀松布颜色似乎覆盖了状态栏初始颜色和状态栏稀松布颜色 从 xml
  • 使用 Android 发送 HTTP Post 请求

    我一直在尝试从 SO 和其他网站上的大量示例中学习 但我无法弄清楚为什么我编写的示例不起作用 我正在构建一个小型概念验证应用程序 它可以识别语音并将其 文本 作为 POST 请求发送到 node js 服务器 我已确认语音识别有效 并且服务
  • 在gradle插件中获取应用程序变体的包名称

    我正在构建一个 gradle 插件 为每个应用程序变体添加一个新任务 此新任务需要应用程序变体的包名称 这是我当前的代码 它停止使用最新版本的 android gradle 插件 private String getPackageName
  • JavaMail 只获取新邮件

    我想知道是否有一种方法可以在javamail中只获取新消息 例如 在初始加载时 获取收件箱中的所有消息并存储它们 然后 每当应用程序再次加载时 仅获取新消息 而不是再次重新加载它们 javamail 可以做到这一点吗 它是如何工作的 一些背
  • 获取当前 android.intent.category.LAUNCHER 活动的实例

    我创建了一个库项目 并在多个应用程序之间共享 我实现了一个简单的会话过期功能 该功能将在一段时间后将用户踢回到登录屏幕 登录屏幕活动是我的主要活动 因此在清单中它看起来像这样
  • Ubuntu 16.04 - Genymotion:找不到 /dev/hw_random

    I install Genymotion on the Ubuntu 16 04 64Bit I created a virtual emulator for Android 6 0 then I run this emulator but
  • 我的设备突然没有显示在“Android 设备选择器”中

    我正在使用我的三星 Galaxy3 设备来测试过去两个月的应用程序 它运行良好 但从今天早上开始 当我将设备连接到系统时 它突然没有显示在 Android 设备选择器 窗口中 我检查过 USB 调试模式仅在我的设备中处于选中状态 谁能猜出问
  • Android 中麦克风的后台访问

    是否可以通过 Android 手机上的后台应用程序 服务 持续监控麦克风 我想做的一些想法 不断聆听背景中的声音信号 收到 有趣的 音频信号后 执行一些网络操作 如果前台应用程序需要的话 后台应用程序必须能够智能地放弃对麦克风的访问 除非可
  • .isProviderEnabled(LocationManager.NETWORK_PROVIDER) 在 Android 中始终为 true

    我不知道为什么 但我的变量isNetowrkEnabled总是返回 true 我的设备上是否启用互联网并不重要 这是我的GPSTracker class public class GPSTracker extends Service imp
  • 如何根据 gradle 风格设置变量

    我想传递一个变量test我为每种风格设置了不同的值作为 NDK 的定义 但出于某种原因 他总是忽略了最后味道的价值 这是 build gradle apply plugin com android library def test andr
  • 实现滚动选择 ListView 中的项目

    我想使用 ListView 您可以在其中滚动列表来选择一个项目 它应该像一个 Seekbar 但拇指应该是固定的 并且您必须使用该栏来调整它 我面临的一个问题是 我不知道这种小部件是如何调用的 这使得我很难搜索 所以我制作了下面这张图片 以

随机推荐

  • 捕获 Android 应用程序何时进入后台

    我需要有关 Android 的帮助 这是我需要在应用程序进入后台时捕获事件的问题 例如 按下主页或将我的应用程序推入后台的任何其他操作 目前我可以抓住 onPause 进行活动 Override protected void onPause
  • jquery 中的可滚动、轮播、滑块,无需任何插件 - 最简单的方法

    我正在寻找有关带有循环动画的 jQuery 滑块 如可滚动插件 的教程 无需任何插件 最简单的方法 教程 更新日期 2014 年 8 月 27 日 DEMO http so lucafilosofi com scrollable carou
  • 使用 HtmlAgilityPack 设置 InnerText

    我尝试使用以下方法设置 InnerText 但不允许设置 InnerText 属性 node InnerText node InnerText Remove 100 原因是我只想删除文本 而不是实际元素 div Lorem ipsum do
  • 有没有办法将自然语言日期 NSString 转换为 NSDate

    假设我有 NSString tomorrow 是否有任何库可以接受这样的字符串并将它们转换为 NSDates 我想象 希望有这样的事情 NSString humanDate tomorrow at 4 15 NSDateFormatter
  • Javascript window.open 在 Win7 x64 上的 32 位 IE8 中返回 null

    我已阅读有关此主题的相关问题 但尚未找到此问题的解决方案 我有一个简单的 javascript 函数 在单击链接时调用 window open var newwindow function pop url newwindow window
  • 低级鼠标挂钩和 DirectX

    我正在构建一个需要在系统范围内过滤一些鼠标点击的应用程序 也就是说 我需要让系统在特殊场合忽略一些鼠标按钮的点击 我使用低级鼠标钩SetWindowsHookEx过滤掉这些点击 它的效果相对较好 除了WPF应用程序 我想这是因为这些应用程序
  • grid.Call(L_textBounds, as.graphicsAnnot(x$label), x$x, x$y, 中的错误:找不到多边形边缘

    我刚刚在 Mac OS X 版本 10 7 3 上安装了 RStudio 执行以下命令后 library ggplot2 qplot mpg wt data mtcars 我收到以下错误 Error in grid Call L textB
  • 如何在特定目录中安装 Bower 依赖项?

    我正在为简单的网站构建一个自耕农生成器 我想在我的脚手架中包含一个流行的 JavaScript 库 这很容易bower install
  • 在复杂对象图上使用 IXmlSerialized 接口

    如果使用自定义 XML 序列化 IXmlSerialiable 在一个复杂对象上 该对象包含具有组成复杂对象的属性 这些属性NOT使用自定义IXmlSerializable接口 你如何指定 在IXmlSerializable ReadXml
  • IBOutletCollection 的实际高效使用

    IBOutletCollection的实际用法看起来怎么样 不幸的是 苹果文档简单地提到了它 但没有给出更广泛的用法 好的 它与IB保持了一对多的关系 但是如何高效地访问和使用特定的对象呢 与标签名 如何保证对象的顺序 我最近使用它来轻松初
  • 如何将日期和时间从自然语言翻译为日期时间? [关闭]

    Closed 此问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 目前不接受答案 我正在寻找一种方法将 明天早上 6 点 或 下周一中午 翻译为适当的日期时间对象 我想过设计一套复杂的规则 但还有其他方法吗 解析日期时间 能够解析
  • 如何在 Java 中将文件读取为无符号字节?

    如何在 Java 中将文件读取为字节 需要注意的是 所有字节都必须为正数 即不能使用负数范围 这可以在 Java 中完成吗 如果可以 怎么做 我需要能够将文件的内容乘以一个常量 我假设我可以将字节读入 BigInteger 然后相乘 但是由
  • 多线程安全日志记录

    我们有一个在多个线程中运行并使用 Log4Net 作为日志记录框架的应用程序 我们遇到了一些日志事件未记录的情况 正如文档中提到的 文件附加器其他 Appender 是 not对于多线程操作是安全的 我在网上搜索解决方案或 Appender
  • 如何清除或清空 StringBuilder? [复制]

    这个问题在这里已经有答案了 我正在使用一个字符串生成器在循环和每 x 迭代中我想清空它并从一个空开始StringBuilder 但我看不到任何类似于 NET的方法StringBuilder Clear在文档中 只是delete方法看起来过于
  • Python 中 exec 和 eval 的使用

    所以我明白了什么exec and eval并且compile做 但为什么我需要使用它们呢 我不太清楚使用场景 谁能给我一些例子 以便我更好地理解这个概念 因为我知道这都是理论 我将举一个我使用过的例子eval我认为这是最好的选择 我正在编写
  • .net 中转换为短路径的标准方法

    寻找标准的防错误方法将 长名称 例如 C Documents and settings 转换为等效的 短名称 C DOCUME 1 我需要它来从我的 C 应用程序中运行外部进程 如果我用 长名称 中的路径提供它 它就会失败 如果您准备开始调
  • python 中未定义名称 exit

    以下是代码 当我运行时 我收到一条错误消息 指出 名称退出未定义 谁能告诉我为什么 非常感谢您的时间和关注 if len sys argv 4 do something pass else print usage something her
  • 将二进制转换为浮点值的计算器 - 我做错了什么?

    我有以下代码 它将 6 个浮点数以二进制形式写入磁盘并读回 include
  • 在本地计算机上使用 Jupyter Notebook 在远程计算机上运行代码

    我使用 Jupyter Notebook 来运行生物信息学分析 我喜欢它 然而 只有当我在个人计算机上运行它时 它才真正发挥得很好 不过 我经常使用具有多核的远程计算机进行分析 以减少处理时间 我希望能够在我的个人计算机上使用 Jupyte
  • Hilt 循环依赖

    我正在使用 Hilt 创建一个宠物项目 也许我遇到这个问题是因为我安装一切都在SingletonComponent class 也许我应该为每一个创建组件 宠物项目有一个NetworkModule UserPrefsModule 当我尝试创