Ответ 1
То, что я закончил, вместо того, чтобы иметь контекст непосредственно в ViewModel, я создал классы провайдера, такие как ResourceProvider, которые предоставили бы мне нужные мне ресурсы, и я включил эти классы провайдеров в мой ViewModel
Я пытаюсь реализовать шаблон MVVM в приложении для Android. Я прочитал, что в ViewModels не должно быть специфического кода для Android (для облегчения тестирования), однако мне нужно использовать контекст для разных вещей (получение ресурсов из xml, инициализация настроек и т.д.). Каков наилучший способ сделать это? Я видел, что AndroidViewModel
имеет ссылку на контекст приложения, однако содержит специальный код для Android, поэтому я не уверен, что это должно быть в ViewModel. Также они привязываются к событиям жизненного цикла Activity, но я использую кинжал для управления областью компонентов, поэтому я не уверен, как это повлияет на это. Я новичок в модели MVVM и кинжале, поэтому любая помощь оценивается!
То, что я закончил, вместо того, чтобы иметь контекст непосредственно в ViewModel, я создал классы провайдера, такие как ResourceProvider, которые предоставили бы мне нужные мне ресурсы, и я включил эти классы провайдеров в мой ViewModel
Вы можете использовать контекст Application
который предоставляется AndroidViewModel
, вы должны расширить AndroidViewModel
который является просто ViewModel
который включает в себя ссылку на Application
.
Это не то, что ViewModels не должны содержать специальный код для Android, чтобы упростить тестирование, поскольку это абстракция, облегчающая тестирование.
Причина, по которой ViewModels не должна содержать экземпляр Context или что-либо вроде Views или других объектов, которые содержат контекст, состоит в том, что он имеет отдельный жизненный цикл, чем операции и фрагменты.
Я имею в виду, что, скажем, вы делаете изменение вращения в своем приложении. Это заставляет вашу активность и фрагмент уничтожать себя, чтобы воссоздать себя. ViewModel предназначен для сохранения во время этого состояния, поэтому есть вероятность сбоев и других исключений, если он все еще держит View или Context для уничтоженного Действия.
Что касается того, как вы должны делать то, что хотите, MVVM и ViewModel отлично работают с компонентом Databinding JetPack. Для большинства вещей, которые вы обычно храните String, int или и т.д., Вы можете использовать привязку данных, чтобы отображать представление непосредственно, поэтому не нужно хранить значение внутри ViewModel.
Но если вы не хотите привязывать данные, вы все равно можете передать контекст внутри конструктора или методов для доступа к ресурсам. Просто не держите экземпляр этого Контекста внутри вашего ViewModel.
Для модели архитектуры Android View View Model,
Не рекомендуется передавать ваш Контекст активности в Activity ViewModel как утечку памяти.
Следовательно, чтобы получить контекст в вашей ViewModel, класс ViewModel должен расширять класс модели представления Android. Таким образом, вы можете получить контекст, как показано в примере кода ниже.
class ActivityViewModel(application: Application) : AndroidViewModel(application) {
private val context = getApplication<Application>().applicationContext
//... ViewModel methods
}
вы можете получить доступ к контексту приложения из getApplication().getApplicationContext()
из ViewModel. Это то, что вам нужно для доступа к ресурсам, настройкам и т.д.
имеет ссылку на контекст приложения, однако содержит специальный код для Android
Хорошие новости, вы можете использовать Mockito.mock(Context.class)
и сделать контекст Mockito.mock(Context.class)
что вы хотите в тестах!
Поэтому просто используйте ViewModel
как обычно, и дайте ему ApplicationContext через ViewModelProviders.Factory, как обычно.
MVVM - хорошая архитектура, и это определенно будущее Android-разработки, но есть пара вещей, которые все еще зеленые. Возьмем, к примеру, коммуникацию уровня в архитектуре MVVM, я видел, что разные разработчики (очень известные разработчики) используют LiveData для обмена различными слоями по-разному. Некоторые из них используют LiveData для связи ViewModel с пользовательским интерфейсом, но затем они используют интерфейсы обратного вызова для связи с репозиториями или имеют Interactors/UseCases, и они используют LiveData для связи с ними. Укажите здесь, что не все 100% определить еще.
При этом мой подход с вашей конкретной проблемой заключается в наличии контекста приложения, доступного через DI, для использования в моих моделях ViewModels для получения таких вещей, как String, из моего strings.xml
Если я занимаюсь загрузкой изображений, я пытаюсь передать объекты View из методов адаптера привязки данных и использовать контекст View для загрузки изображений. Зачем? потому что некоторые технологии (например, Glide) могут столкнуться с проблемами, если вы используете контекст приложения для загрузки изображений.
TL; DR: Внесите контекст приложения через кинжал в свои ViewModels и используйте его для загрузки ресурсов. Если вам нужно загрузить изображения, передайте экземпляр View через аргументы из методов Databinding и используйте контекст View.
Надеюсь, поможет!
Вы не должны использовать объекты, связанные с Android, в вашем ViewModel, поскольку мотив использования ViewModel заключается в том, чтобы отделить Java-код и код Android, чтобы вы могли тестировать свою бизнес-логику отдельно, и у вас будет отдельный слой компонентов Android и ваша бизнес-логика и данные. У вас не должно быть контекста в ViewModel, так как это может привести к сбоям
Короткий ответ - не делай этого
Зачем?
Это побеждает всю цель просмотра моделей
Почти все, что вы можете сделать в модели представления, можно выполнить в виде действия/фрагмента, используя экземпляры LiveData и различные другие рекомендуемые подходы.
Как уже упоминали другие, есть AndroidViewModel
которой вы можете получить Context
приложения, но из того, что я собираю в комментариях, вы пытаетесь манипулировать @drawable
из вашей ViewModel
что почти наверняка не соответствует цели выполнения всего MVVM.,
В целом, необходимость иметь Context
в вашей ViewModel
почти повсеместно предполагает, что вам следует подумать о переосмыслении того, как вы разделяете логику между вашими View
и ViewModels
.
Например, вместо того, чтобы ViewModel
разрешал рисовать и передавать их в Activity/Fragment, рассмотрите возможность использования Fragment/Activity для манипулирования рисованными объектами на основе данных, которыми обладает ViewModel
. Например, если у вас есть какое - то на индикаторе/выключено, он в ViewModel
, который должен держать (возможно, логические) состояние, но его View
бизнес, чтобы выбрать правильную вытяжку соответственно.
В случае, если вам нужен Context
для некоторых компонентов/сервисов, не связанных напрямую с представлением (например, внутренними запросами) для конструктора ViewModel
(вручную/путем внедрения) - таким образом, нет явной зависимости от Context
и, следовательно, простое использование в тестах (просто передайте фиктивные сервисы/компоненты конструктору или предоставьте их для внедрения по выбору, без фактического Context
)
Я создал это так:
@Module
public class ContextModule {
@Singleton
@Provides
@Named("AppContext")
public Context provideContext(Application application) {
return application.getApplicationContext();
}
}
А потом я просто добавил в AppComponent ContextModule.class:
@Component(
modules = {
...
ContextModule.class
}
)
public interface AppComponent extends AndroidInjector<BaseApplication> {
.....
}
А потом я ввел контекст в моей ViewModel:
@Inject
@Named("AppContext")
Context context;