Я создаю минимальную ViewModel в своей MainActivity, используя механизм by viewModels
.
В настоящее время он просто поддерживает репозиторий с помощью Dependency Injection. Для этого требуются параметры конструкции, поэтому я предоставил для этого фабричный класс.
Я создаю экземпляр объекта Factory внутри MainActivity
и передаю его с помощью by viewModels
для инициализации первого и единственного экземпляра этой ViewModel.
private val Context.dataStore by preferencesDataStore("settings")
class MainActivity : AppCompatActivity() {
//..
private val viewModel : AutomationViewModel by viewModels {
AutomationViewModelFactory(
this.application, SettingsRepository(dataStore))
}
//...
ViewModel выглядит следующим образом:
class AutomationViewModel(
private val mApplication: Application, // not really needed.
private val repository: SettingsRepository
): ViewModel() {
// example property to setup an observable state
val loginParametersState: StateFlow<LoginParameters> =
repository.LoginParametersFlow.stateIn(
viewModelScope,
SharingStarted.Eagerly, LoginParameters()
)
// This is the dummy method, that seems to be necessary to
// preheat.
var dummy: Int = 0; private set
fun setDummy(x: Int) {
dummy = x
}
}
И вот связанный класс Factory
class AutomationViewModelFactory(
private val mApplication: Application, // not really needed
private val repository: SettingsRepository
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return when(modelClass) {
AutomationViewModel::class.java -> AutomationViewModel(mApplication, repository)
else -> throw IllegalArgumentException("Unknown ViewModel class")
} as T
}
// Not sure what this is about
override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T {
return super.create(modelClass, extras)
}
Затем этот AutomationViewModel
используется в одном из моих фрагментов, также используя механизм by viewModels
. Идея, конечно, состоит в том, что я воспользуюсь ранее кэшированным экземпляром уже созданного экземпляра AutomationViewModel
.
В своих фрагментах я получаю объект ViewModel, используя тот же механизм «по». Однако на данном этапе я не передаю объект Factory, потому что теперь он выходит за рамки области действия и ограничивается действием.
class LoginFragment : Fragment() {
private val connector: AutomationViewModel by activityViewModels()
//.. Usual boilerplate code for onCreate, onCreateView, onViewCreated. Nothing particular to see there.
}
В любом случае я не хочу использовать фабричный объект во фрагменте, потому что у меня нет к нему доступа, и я не хочу в конечном итоге связывать с фрагментом все, что управляется MainActivity, и я я ожидаю использовать уже кэшированный экземпляр виртуальной машины.
Однако это не работает. Объект My Factory никогда не создается; Вместо этого используется фабрика по умолчанию, которая выдает исключение, поскольку не знает, как создать экземпляр моего конкретного класса ViewModel.
Я обнаружил, что виртуальная машина на самом деле никогда не создавалась в MainActivity, потому что в настоящее время она не затрагивается ею.
Похоже, что создание экземпляра виртуальной машины откладывается до Фрагмента, где, как я уже сказал выше, Фабрика больше не доступна.
Правильно ли я понимаю, что здесь происходит?
Является ли моя структура передачи Factory в оператор by
только в Activity, просто используя голый by
для создания экземпляра (извлечения из кэша) во фрагментах, правильно?
Я заметил, что могу заставить все это работать, просто имея на своей виртуальной машине фиктивную функцию, которая ничего не делает, а вызывает ее «предварительный нагрев», принудительно создавая экземпляр в этот момент в MyActivity
.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//.. boilerplate from Wizard
// Seems to work, only if I add this below, which does
// nothing
viewModel.setDummy(1)
}
}
Во всех других сообщениях, которые я видел, говорится, что механизмы by
и get
взаимозаменяемы.
Это мой главный вопрос:
Действительно ли нет способа быстро создать экземпляр виртуальной машины? Должен ли я использовать метод фиктивного предварительного нагрева в качестве постоянного решения?
«Эта ViewModel затем используется в некоторых моих фрагментах, также используя механизм «by viewModels». - пожалуйста, покажите этот код. Возможно, вам нужно вместо этого использовать ActivityViewModels.
Извините за тяжелый текст. Я сочинял на телефоне. Суть моего вопроса: «Как создать экземпляр ViewModel, для которого требуется класс Factory, если ленивая оценка означает, что он не будет создан, пока не станет слишком поздно?» Я добавил код и действительно использую ActivityViewModels. Однако это не помогает, потому что я до сих пор понятия не имею, будет ли создан экземпляр виртуальной машины до того, как будет создан какой-либо фрагмент. Логично, что мне пришлось бы передать этот объект Factory каждому фрагменту на случай, если /этот/фрагмент окажется тем, который действительно вызывает ленивое создание экземпляров.
@Саран Шанкаран. Насколько я понимаю, виртуальная машина инициализируется в контексте действия, когда она впервые вызывается любым фрагментом. Однако, похоже, это не так: если виртуальная машина не «действительно» инициализируется в действии, тогда инициализация происходит впервые во фрагменте, но ленивая оценка, которая была настроена в действии, кажется потерять свой фабричный параметр: он просто впервые создается во фрагменте без какого-либо фабричного объекта.
Lazy
, который объясняет поведение, которое вы видите.
by viewModels()
возвращает Lazy
, поэтому он будет инициализирован только при первом использовании, в вашем случае фрагмент. Вы можете вручную инициализировать ViewModel в своем MainActivity.onCreate()
, используя ViewModelProvider
.
MainActivity.kt
private lateinit viewModel: AutomationViewModel
override fun onCreate(...) {
super.onCreate(...)
viewModel = ViewModelProvider(
this,
AutomationViewModelFactory(this.application, SettingsRepository(dataStore))
)
by viewModels()
также использует ViewModelProvider
, но, поскольку он ленив, он не инициализируется, пока вы его не используете впервые.
Значит, действие инициализации «вручную» отличается от метода «по»? Меня заставили поверить, что они были просто синтаксическим сахаром друг для друга, поскольку оба были ленивыми. Я попробую инициализацию вручную в своей деятельности, но буду использовать метод «by» в тех фрагментах, которые его используют (и чье поведение зависит от его существования). Думаю, меня смутили люди, говорящие, что использование lateinit — это плохо и что ленивый («by») — единственный способ.
Мы использовали ViewModelProvider
до того, как появился by viewModels()
. Если вы прочитаете исходный код viewModels()
, вы увидите, что он использует ViewModelProvider
для ЛЕНИВОЙ инициализации ViewModel и ничего более, это всего лишь синтаксический сахар для private val viewModel: ViewModel by lazy { ViewModelProvider() }
. Итак, если вы хотите лениво инициализировать ViewModel, используйте by viewModels()
, если вы хотите инициализировать ViewModel быстро, используйте ViewModelProvider
напрямую.
Ах хорошо. Я вообще не использовал виртуальные машины до появления механизма «by», поэтому меня смущали все люди, говорящие, что один метод эквивалентен другому. Итак, viewModels() — это просто синтаксический сахар, но не такой, какой я думал. Меня заставили поверить, что ленивое создание экземпляров — единственный путь. Сейчас это явно не так.
@flatCurrency, я сказал тебе то же самое, что и Альрадж... Пожалуйста, будьте внимательнее, читая документацию и ответы людей.
Определение ViewModel согласно документации Android:
ViewModel всегда создается в связи с областью действия (фрагмент или действие) и будет сохраняться до тех пор, пока область действия жива. Например. если это действие, до тех пор, пока оно не будет завершено.
Другими словами, это означает, что ViewModel не будет уничтожена, если его владелец уничтожается при изменении конфигурации (например, ротации). новый экземпляр владельца просто повторно подключится к существующему Модель просмотра.
Цель ViewModel — получить и сохранить информацию. это необходимо для действия или фрагмента. Деятельность или Фрагмент должен иметь возможность наблюдать изменения в ViewModel. Модели ViewModels обычно предоставляют эту информацию через LiveData или Android. Привязка данных. Вы также можете использовать любую вашу конструкцию наблюдаемости. любимый фреймворк.
Единственная ответственность ViewModel — управлять данными пользовательского интерфейса. Это никогда не должен получать доступ к вашей иерархии представлений или удерживать ссылку на Активность или Фрагмент.
Целью ViewModelProvider
является оптимизация инициализации модели представления. by activityViewModel()
для фрагментов и by viewModels()
для действий — это простые ярлыки для написания ленивой инициализации модели представления. Это то же самое, что написать:
by lazy {
ViewModelProvider(myDesiredOwner, MyViewModelFactory(viewId))[YourViewModelClass::class.java]
}
Если вы хотите использовать одну и ту же модель представления как для своей активности, так и для фрагмента без ее инициализации, 2ce замените myDesiredOwner
на this
в классе активности и на requireActivity()
в классе фрагмента.
Модель My View содержит не что иное, как потоки и различные наблюдаемые. Он ничего не знает о фрагментах, приложениях или иерархиях представлений. Он спроектирован так, чтобы быть полностью автономным, за исключением ссылки на хранилище данных. Это действие создает ссылку на хранилище данных и инициализирует с ее помощью виртуальную машину. После этого с виртуальной машиной ничего не взаимодействует, кроме моих фрагментов. Я создал объект фабричного класса, содержащий только ссылку на хранилище данных, и инициализировал виртуальную машину на его основе.
@flatCurrency, вы сказали: «В любом случае, я не хочу использовать фабричный объект во фрагменте, потому что у меня нет к нему доступа, и я не хочу в конечном итоге связывать с фрагментом все, что есть управляемый MainActivity, и я ожидаю использовать уже кэшированный экземпляр виртуальной машины». Это работа ViewModelProvider. Вы должны написать код точно так, как я предоставил. После того, как я немного изучил проблему, оказалось, что by viewModels()
и by activtyViewMoodels()
всегда используют фабрику по умолчанию.
По моему опыту, с помощью viewModels() и ActivityViewModels() /do/ при первом создании используется фабрика, передаваемая как лямбда. Однако я ожидал, что лямбда будет каким-то образом «связана» с ViewModel, чтобы ее можно было вызвать позже, если она еще не была создана к моменту появления обнаженной модели ActivityViewModels. Похоже, это не так: лямбда, являющаяся инициализацией, доступна только тогда, когда она вызывается таким образом. Я не до конца понимаю все эти вещи о передаче блоков кода в качестве параметров, поэтому вполне возможно, что я совершенно неправильно понял.
Теперь я изменил модель инициализации моего приложения. Теперь я использую механизм Factory для создания экземпляра /fragments/. Таким образом, моя пользовательская фабрика фрагментов имеет полное представление о том, какие модели представлений следует внедрить в каждый фрагмент. Таким образом, у меня есть фрагменты, не привязанные к конкретной виртуальной машине, и виртуальные машины, которые не имеют понятия о существовании фрагментов. Моя ActivityMain — единственное, что связывает их вместе.
Эй, у вас было много текста, но вопрос, который вы задаете, не ясен. Можете ли вы быть более конкретным с вопросом. Однако, насколько я понимаю, ViewModelFactory должен быть одноэлементным и может использоваться как действием, так и фрагментом. Таким образом, виртуальная машина будет инициализирована в области активности всякий раз, когда она вызывается из фрагмента.