kotlin 如何注入ViewModel接口?

unftdfkk  于 2023-08-06  发布在  Kotlin
关注(0)|答案(5)|浏览(124)

基于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()
  ...
}


如何让这一切顺利进行呢?

dnph8jn4

dnph8jn41#

viewModels需要ViewModel类的子级

val viewModel: ExampleViewModel by viewModels<ExampleViewModelImp>()

字符串

vsaztqbk

vsaztqbk2#

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

  1. by viewModels()ViewModelProviders.of(...)都需要扩展ViewModel()的类型。因此接口将是不可能的,但我们仍然可以使用一个扩展ViewModel()的抽象类
    1.我不认为有办法使用@HiltViewModel来实现这个目的,因为没有办法切换实现。
    1.因此,尝试将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

xriantvc

xriantvc3#

找到了一个使用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<Proxy>()生命周期

axzmvihb

axzmvihb4#

interface IProjectViewModel

abstract class AbstractViewModel(
    protected val savedStateHandle: SavedStateHandle
): ViewModel(), IProjectViewModel {

    private val _uiState = MutableStateFlow<Result<*>?>(null)
    val uiState: StateFlow<Result<*>>
        get() = _uiState

    // Why shouldn't we put this function signature in the IProjectViewModel interface ?
    protected fun execute(
        safeBlock: suspend () -> Result<*>
    ) { 
        viewModelScope.launch {
            flow {
                emit(Loading)
                emit(safeBlock.invoke())
            }.collect {
                _uiState.value = it
            }
        }
    }
}

/* 
* Notice IVM and VM are nullable, if you don't want to use ViewModel for your fragment, 
* then use Nothing? as the generic in your implementation fragment.
*/
abstract class AbstractFragment<VB : ViewBinding, IVM : IProjectViewModel?, VM : AbstractViewModel?>()
: Fragment() {
    abstract protected val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> VB

    protected val viewModel: IVM by viewModels<VM>()

    private var _binding: VB
    protected val binding
        get() = _binding

    override protected fun onCreateView(
        inflater: LayoutInflater,
        viewGroup: ViewGroup?,
        attachToRoot: Boolean 
    ) = bindingInflater(inflater, viewGroup, attachToRoot).also { _binding = it }.root
}

interface IFeatureViewModel: IProjectViewModel {

    // Declare your feature viewModel functions
}

@HiltViewModel
class FeatureViewModel @Inject constructor(
    savedStateHandle: SavedStateHandle,
    repository: IFeatureRepository
): AbstractViewModel(savedStateHandle), IFeatureViewModel {

    // Implement your feature viewModel functions
}

@AndroidEntryPoint
class FeatureFragment(): AbstractFragment<FragmentLayoutBinding, IFeatureViewModel, FeatureViewModel>() {

    override protected val bindingInflater = FragmentLayoutBinding::inflate
    
    override protected fun onViewCreated(...) {
        // Do your code magic because you should have access to both binding and viewModel here by now
        lifecycleScope.repeatOnLifecycle(STARTED) {
            viewModel?.uiState?.collectLatest {
                // You know the rest... binding.id. yada yada yada
            }
        }

        // Call your viewModel functions
    }
}

字符串

zazmityj

zazmityj5#

注入一个接口是如此简单,你传递了一个接口,但注入注入了一个Impl。

@InstallIn(ViewModelComponent::class)
@Module
class DIModule {

@Provides
fun providesRepository(): YourRepository = YourRepositoryImpl()

}

字符串

相关问题