Android ViewModelFactory с рукоятью

Сначала пробую android ViewModel и Hilt DI

Как я понимаю из ссылки ниже, для инициализации ViewModel значением во время выполнения я должен использовать ViewModelFactory

Используйте ViewModelFactory

//ViewModel
class ScoreViewModel(finalScore: Int) : ViewModel() {
   // The final score
   var score = finalScore
   init {
       Log.i("ScoreViewModel", "Final score is $finalScore")
   }
}

//ViewModelFactory
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
   if (modelClass.isAssignableFrom(ScoreViewModel::class.java)) {
       return ScoreViewModel(finalScore) as T
   }
   throw IllegalArgumentException("Unknown ViewModel class")
}


//Fragment
viewModelFactory = ScoreViewModelFactory(ScoreFragmentArgs.fromBundle(arguments!!).score)

И чтобы использовать ViewModel с рукоятью, я должен использовать @ViewModelInject, как описано в ссылке ниже.

Интеграция Hilt и Jetpack

//ViewModel
class ExampleViewModel @ViewModelInject constructor(
  private val repository: ExampleRepository,
  @Assisted private val savedStateHandle: SavedStateHandle
) : ViewModel() {
  ...
}

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

Но как использовать Hilt с ViewModelFactory?

Кажется, ответ находится в @Assisted, но я не могу понять, как

Как сказать рукоятке, что мне нравится вводить интерфейсы репозитория в ViewModel, при этом позволяя ViewModelFactory инициализировать ViewModel с параметрами во время выполнения?


person epic    schedule 30.06.2020    source источник
comment
В этой статье может быть подробно рассказано, как работать с этой проблемой medium.com/mobile-app-development-publication/   -  person Elye    schedule 02.07.2020


Ответы (2)


любезно предоставленные @Elye, следующие статьи очень помогли. Рекомендую прочитать.

Передача данных о намерениях действий в ViewModel посредством внедрения

Внедрение ViewModel с рукоятью кинжала

Кажется, что в основном Factory не нужен, поскольку в основном начальные параметры viewmodel взяты из предыдущего фрагмента и могут быть доступны через SavedStateHandle, который автоматически вводится, если он помечен как @Assisted

Для настройки рукояти я использовал следующий учебник code-labs

Использование Hilt на Android-устройстве приложение

Затем viewModel инъекция выполняется автоматически с использованием только следующего кода

Обратите внимание, что, как отмечает здесь fabioCollini, кажется, savedStateHandle также может получать значения из безопасные аргументы, просто поместив имя аргумента в качестве ключа. Фактически, это то, что я сделал в следующем примере. ps: В попытке сделать безопасные аргументы более безопасными, я попытался заменить SavedStateHandle на ItemsFragmentArgs, надеясь, что это сработает, но приложение не скомпилировалось. Я очень надеюсь, что это будет реализовано в будущем (и если уже, дайте мне знать)

//ItemFragment file
@AndroidEntryPoint
class ItemsFragment : Fragment() {

    private val viewModel: ItemsViewModel by viewModels()

    //use viewModel as you would. No need to initialize.
}

//Module file - if you have any repository, remember to bind it 
//or provide the exact implementation as noted in code-labs
@InstallIn(ApplicationComponent::class)
@Module
abstract class DatabaseModuleBinder {

    @Binds
    abstract fun bindGlistRepository(impl: FirestoreGlistRepository): GlistRepository

}


//ItemsViewModel file - lastly, anotate as follows and take your arguments 
//from savedStateHandle (for safe args, use variable name as key)
class ItemsViewModel @ViewModelInject constructor(private val glistRepo: GlistRepository,
                     @Assisted private val savedStateHandle: SavedStateHandle) : ViewModel() {

    private val glistLiveDate = glistRepo.getGlistLiveData(
        savedStateHandle.get<String>("listId")!!
    )

..
}

Надеюсь, это поможет кому-нибудь, и если возникнут какие-либо ошибки, пожалуйста, дайте мне знать

person epic    schedule 02.07.2020
comment
Моя проблема заключалась в том, что я использовал модель просмотра во фрагменте, а во фрагменте я не упоминал @AndroidEntryPoint. Я упоминал об этом в Activity, но не в Fragment. - person Sharad; 06.08.2020
comment
Чтобы использовать safe-args в модели представления, вы можете реализовать что-то вроде того, что я показал здесь: stackoverflow.com/a/64814055/1842900 < / а> - person Aleksandr Belkin; 13.11.2020
comment
@ViewModelInject и @Assisted устарели. - person Ibrahim Disouki; 27.02.2021

Передайте свой ScoreViewModelFactory во встроенное ktx-расширение viewModel. Также вы можете использовать аргументы Activity / Fragment, используя сам SavedStateHandle с defaultViewModelProviderFactory.

/*
Gradle Dependencies
def lifecycle_version = "2.2.0"
def hiltLifeVersion = "1.0.0-alpha01"
def hiltVersion = "2.28.1-alpha"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
implementation "com.google.dagger:hilt-android:$hiltVersion"
implementation "androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha01"
implementation "androidx.hilt:hilt-work:$hiltLifeVersion"
implementation "androidx.hilt:hilt-common:1.0.0-alpha01"
kapt "com.google.dagger:hilt-android-compiler:$hiltVersion"
kapt "androidx.hilt:hilt-compiler:$hiltLifeVersion"
*/

import androidx.fragment.app.viewModels

@AndroidEntryPoint
class ExampleFragment : Fragment(R.layout.example_fragment) {

    //internally using defaultViewModelProviderFactory 
    private val viewModel : ExampleViewModel by viewModels()

    //or you own viewmodal factory instance --> scoreViewModelFactory
    private val viewModel : ExampleViewModel by viewModels { scoreViewModelFactory }

}

class ExampleViewModel @ViewModelInject constructor(
    private val repository: ExampleRepository,
    @Assisted override val savedStateHandle: SavedStateHandle
) : ViewModel() {

    //bundle args -> String, Int, Parcelable etc.. 
    private val arg1LiveData: MutableLiveData<String> = 
                         savedStateHandle.getLiveData("arg1", "")

}

Во встроенном ktx-расширении модели просмотра фрагментов

@MainThread
inline fun <reified VM : ViewModel> Fragment.viewModels(
    noinline ownerProducer: () -> ViewModelStoreOwner = { this },
    noinline factoryProducer: (() -> Factory)? = null
) = createViewModelLazy(VM::class, { ownerProducer().viewModelStore }, factoryProducer)
person Ravi    schedule 02.07.2020