#android #android-fragments #dependency-injection #android-viewmodel #dagger-hilt
#Android #android-фрагменты #внедрение зависимостей #android-viewmodel #кинжал-рукоять
Вопрос:
Мне интересно, как передать параметры времени выполнения конструктору ViewModel при использовании Hilt для DI? До использования Hilt у меня была ViewModel, которая выглядит следующим образом:
class ItemViewModel(private val itemId: Long) : ViewModel() {
private val repo = ItemRepository(itemId)
}
class ItemViewModelFactory(private val itemId: Long) : ViewModelProvider.Factory {
@Suppress("unchecked_cast")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(ItemViewModel::class.java)) {
return ItemViewModel(itemId) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
Я создаю вышеупомянутую ViewModel в своем фрагменте следующим образом:
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val args: ItemScreenFragmentArgs by navArgs()
val itemId = args.itemId
//Create the view model factory
val viewModelFactory = ItemViewModelFactory(application, itemId)
// Get a reference to the ViewModel associated with this fragment.
val itemViewModel = ViewModelProvider(this, viewModelFactory).get(ItemViewModel::class.java)
}
Если бы в моем конструкторе ItemViewModel не было параметра ItemId, моя ViewModel и фрагментация с использованием Hilt выглядели бы так:
class ItemViewModel
@ViewModelInject
constructor(private val repo: ItemRepository) : ViewModel() { }
@AndroidEntryPoint
class ItemFragment : Fragment() {
private val itemViewModel: ItemViewModel by viewModels ()
}
Я пытаюсь выяснить, как передать ItemId, который я получаю из наваргов ItemFragment, в конструктор ItemViewModel? Есть ли способ сделать это с помощью Hilt?
Ответ №1:
Для тех, кто еще хочет передать параметры времени выполнения ViewModel при использовании Dagger Hilt, вот как я это сделал:
Я следовал коду из этого примера, в котором используется библиотека AssistedInject .
Теперь мой код выглядит следующим образом:
class ItemViewModel
@AssistedInject
constructor(private val repo: ItemRepository, @Assisted private val itemId: Long) : ViewModel() {
init {
repo.itemId = itemId
}
@AssistedInject.Factory
interface AssistedFactory {
fun create(itemId: Long): ItemViewModel
}
companion object {
fun provideFactory(
assistedFactory: AssistedFactory,
itemId: Long
): ViewModelProvider.Factory = object : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return assistedFactory.create(itemId) as T
}
}
}
}
@InstallIn(FragmentComponent::class)
@AssistedModule
@Module
interface AssistedInjectModule {}
@AndroidEntryPoint
class ItemFragment : Fragment() {
private val args: ItemScreenFragmentArgs by navArgs()
@Inject lateinit var itemViewModelAssistedFactory: ItemViewModel.AssistedFactory
private val itemViewModel: ItemViewModel by viewModels {
ItemViewModel.provideFactory(itemViewModelAssistedFactory, args.itemId)
}
}
Комментарии:
2. @gturedi вам нужно удалить HiltViewModel в вашей viewmodel, чтобы использовать AssistedInject, но в последнем обновлении вы могли бы передать аргумент с помощью SavedStateHandle . смотрите это здесь, github.com/google/dagger/issues/2287#issuecomment-771671159
3. да, я заметил это позже, и я работал. спасибо
4. Я следовал этому подходу. Я продолжаю получать
AssistedInjectModule is missing an @InstallIn annotation
, хотя я сохранил@InstallIn(SingletonComponent::class)
Ответ №2:
Вспомогательная инъекция теперь поддерживается Dagger, а InflationInjection получил собственное репозиторий. Теперь синтаксис будет выглядеть так :
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
...
class ItemViewModel
@AssistedInject
constructor(private val repo: ItemRepository, @Assisted private val itemId: Long) : ViewModel() {
init {
repo.itemId = itemId
}
//-@AssistedInject.Factory
@AssistedFactory
interface AssistedFactory {
fun create(@Named("item_id") itemId: Long): ItemViewModel
}
companion object {
fun provideFactory(
assistedFactory: AssistedFactory,
itemId: Long
): ViewModelProvider.Factory = object : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return assistedFactory.create(itemId) as T
}
}
}
}
@InstallIn(FragmentComponent::class)
//-@AssistedModule
@Module
interface AssistedInjectModule {}
@AndroidEntryPoint
class ItemFragment : Fragment() {
private val args: ItemScreenFragmentArgs by navArgs()
@Inject lateinit var itemViewModelAssistedFactory:ItemViewModel.AssistedFactory
private val itemViewModel: ItemViewModel by viewModels {
ItemViewModel.provideFactory(itemViewModelAssistedFactory, args.itemId)
}
}
Основывайтесь на ответе Redek. Подробнее об этом здесь
Ответ №3:
На самом деле вы можете использовать шаблон проектирования фабрики для создания объекта, который нужно передать
Это работает, но я не уверен, правильно это или нет
class ItemRepository constructor(private val id: Int) {
}
class RepositoryFactory @Inject constructor() {
private var id: Int = 0
fun setId(id: Int) {
this.id = id
}
fun create(): ItemRepository = ItemRepository(id)
}
class ItemViewModel @ViewModelInject constructor(private val repositoryFactory: RepositoryFactory) : ViewModel() {
private var itemRepository: ItemRepository
init {
repositoryFactory.setId(45)
itemRepository = repositoryFactory.create()
}
}
@AndroidEntryPoint
class ItemFragment : Fragment() {
private val viewModel: ItemViewModel by viewModels ()
}
Комментарии:
1. Проблема в том, что идентификатор поступает не из ViewModel, а из фрагмента, создающего его
Ответ №4:
Есть лучшее решение без AssistedInject, просто используйте SavedStateHandle .
если аргументы в вашем фрагменте содержат идентификатор пользователя:
val args: UserFragmentArgs by navArgs()
...
args.userId
Тогда просто в вашем savedStateHandle он будет доступен без дополнительной работы.
class UserViewModel @Inject constructor (
private val repo: UserRepo,
private val state: SavedStateHandle
) : ViewModel() {
val user = repo.getUser(
state.get<String>("userId")
)
}
Полная информация о реализации: https://mattrobertson.dev/passing-safe-args-to-your-viewmodel-with-hilt-366762ff3f57
Комментарии:
1. Добавление к этому: теперь вы даже можете сделать это типобезопасным способом.
val args = UserFragmentArgs.fromSavedStateHandle(state); val userId = args.user