Android ViewModelFactory с рукоятью
Я сначала пробую андроид ViewModel
а также Hilt
DI
Как я понимаю из ссылки ниже, для инициализации ViewModel значением во время выполнения я должен использовать 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
как объяснено в ссылке ниже
//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 с параметрами во время выполнения?
3 ответа
Любезно предоставленные @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")!!
)
..
}
Надеюсь, это поможет кому-нибудь, и если возникнут ошибки, сообщите мне.
Передайте свой 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)
В Джилте 2.44, по крайней мере, нет@ViewModelInject
. Чтобы внедрить в конструкторViewModel
ViewMolde должен быть аннотирован@HiltViewModel
и для конструктора вы можете просто использовать@Inject
.
@HiltViewModel
class MyViewModel @Inject constructor(
private val navigationHelper: NavHelper
) : ViewModel()