【问题标题】:Hilt - How to inject ViewModel interface?Hilt - 如何注入 ViewModel 接口?
【发布时间】:2021-06-28 16:50:52
【问题描述】:

根据 Hilt 教程,ViewModels 需要通过以下方式注入:

@HiltViewModel
class ExampleViewModel @Inject constructor(
  private val savedStateHandle: SavedStateHandle,
  private val repository: ExampleRepository
) : ViewModel() {
  ...
}

但是,就我而言,我想使用一个界面:

interface ExampleViewModel()

@HiltViewModel
class ExampleViewModelImp @Inject constructor(
  private val savedStateHandle: SavedStateHandle,
  private val repository: ExampleRepository
) : ExampleViewModel, ViewModel() {
  ...
}

那我想通过接口注入

@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() {
  private val exampleViewModel: ExampleViewModel by viewModels()
  ...
}

如何做到这一点?

【问题讨论】:

    标签: android kotlin dagger-hilt


    【解决方案1】:

    viewModels 需要 ViewModel 类的子级

    val viewModel: ExampleViewModel by viewModels<ExampleViewModelImp>()
    

    【讨论】:

      【解决方案2】:

      有一个类似的问题,我想通过接口注入 ViewModel,主要是因为在测试时用假实现来切换它。我们正在从 Dagger Android 迁移到 Hilt,并且我们进行了使用假视图模型的 UI 测试。在此处添加我的发现,以便它可以帮助面临类似问题的人。

      1. by viewModels()ViewModelProviders.of(...) 都需要扩展 ViewModel() 的类型。所以接口是不可能的,但我们仍然可以使用扩展ViewModel()的抽象类
      2. 我认为没有办法为此目的使用 @HiltViewModel,因为无法切换实现。
      3. 因此,尝试将ViewModelFactory 注入Fragment。您可以在测试期间切换工厂,从而切换 ViewModel。
      @AndroidEntryPoint
      class ListFragment : Fragment() {
          
          @ListFragmentQualifier
          @Inject
          lateinit var factory: AbstractSavedStateViewModelFactory
      
          private val viewModel: ListViewModel by viewModels(
              factoryProducer = { factory }
          )
      }
      
      abstract class ListViewModel : ViewModel() {
          abstract fun load()
          abstract val title: LiveData<String>
      }
      
      class ListViewModelImpl(
          private val savedStateHandle: SavedStateHandle
      ) : ListViewModel() {
          override val title: MutableLiveData<String> = MutableLiveData()
          override fun load() {
              title.value = "Actual Implementation"
          }
      }
      
      class ListViewModelFactory(
          owner: SavedStateRegistryOwner,
          args: Bundle? = null
      ) : AbstractSavedStateViewModelFactory(owner, args) {
          override fun <T : ViewModel?> create(
              key: String,
              modelClass: Class<T>,
              handle: SavedStateHandle
          ): T {
              return ListViewModelImpl(handle) as T
          }
      }
      
      @Module
      @InstallIn(FragmentComponent::class)
      object ListDI {
      
          @ListFragmentQualifier
          @Provides
          fun provideFactory(fragment: Fragment): AbstractSavedStateViewModelFactory {
              return ListViewModelFactory(fragment, fragment.arguments)
          }
      }
      
      @Qualifier
      annotation class ListFragmentQualifier
      

      这里,ListViewModel 是抽象类,ListViewModelImpl 是实际实现。您可以在使用TestInstallIn 进行测试时切换ListDI 模块。有关这方面的更多信息和工作项目,请参阅this article

      【讨论】:

      • 我找到了一个更简单的解决方案。查看我的答案;)
      【解决方案3】:

      找到了一个解决方案,使用HiltViewModel 作为我希望注入的实际类的代理。它很简单,就像一个魅力;)

      模块

      @Module
      @InstallIn(ViewModelComponent::class)
      object MyClassModule{
          @Provides
          fun provideMyClas(): MyClass = MyClassImp()
      }
      
      class MyClassImp : MyClass {
          // your magic goes here
      }
      

      片段

      @HiltViewModel
      class Proxy @Inject constructor(val ref: MyClass) : ViewModel()
      
      @AndroidEntryPoint
      class MyFragment : Fragment() {
         private val myClass by lazy {
              val viewModel by viewModels<Proxy>()
              viewModel.ref
          }
      }
      

      现在你得到了MyClass 类型的myClass 接口,绑定到viewModels&lt;Proxy&gt;() lifeCycle

      【讨论】:

      • 也许我误解了这个问题,但Proxy 不是还是硬连线吗? ViewModel Proxy 没有作为接口注入。而是 ViewModel 的依赖项之一,MyClass / MyClassImpl 作为接口注入。所以我们在 ViewModel 类Proxy 中的任何逻辑都不能在测试期间修改,对吧?我的要求是在测试期间用FakeProxy 而不是ProxyImpl 交换Proxy
      • @Henry 你说对了 :) 是的,Proxy 类不能用于测试,但这就是它是代理的原因。来自ProxyViewModel 的所有业务逻辑都移至MyClass。希望使用/测试MyClass 的每个人都将创建自己的Proxy viewModel 来代理MyClassProxy 里面没有逻辑,它实际上是一行定义。
      • 创建测试时要明确,您需要使用TestInstallInMyClassModule 替换为将MyClass 映射到MyTestclassImp 的测试模块。
      • 好的,所以增加了一层间接性。然后我们必须将 ViewModel 特定的东西从 Proxy 传递给 MyClass,例如 SavedStateHandle、ViewModelScope 等……因为 MyClass 不是 ViewModel。
      • @Henry 是的,这是不利的一面,但是您可以很好地构建它,以便您的基础架构(super of MyClass)将引用ViewModel,这个答案太多了; )
      【解决方案4】:

      注入接口就是这么简单,你传递一个接口但是注入注入的是一个Impl。

      @InstallIn(ViewModelComponent::class)
      @Module
      class DIModule {
      
      @Provides
      fun providesRepository(): YourRepository = YourRepositoryImpl()
      
      }
      

      【讨论】:

      • ViewModelComponent 不是ViewModelViewModelComponent 必须注入 ViewModel
      • @InstallIn(ViewModelComponent::class)这是viewmodel的范围
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-04-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多