- Способы инжектить ViewModel с помощью Dagger: что может пойти не так
- 1. Map , Provider > в ViewModelProvider.Factory (с мультибиндингом или без)
- 2. Используем Hilt
- 3. Получаем ViewModel из DI и передаем ссылку в фабрику во viewModels-делегате
- 4. Передаем лямбду для создания ViewModel в фабрику
- 5. Используем @AssistedInject
- 6. Бонус
- Заключение
- Unresolved reference: viewModelScope — Kotlin Android
- 7 Answers 7
- As ViewModelProviders.of() is deprecated, how should I create object of ViewModel?
- 12 Answers 12
- ViewModelProviders is deprecated in 1.1.0
- 26 Answers 26
- Import
- Using
Способы инжектить ViewModel с помощью Dagger: что может пойти не так
Инъекция зависимостей во ViewModel — очень популярная тема для статей по всему интернету. Давайте посмотрим, какие проблемы могут скрывать популярные подходы, и разберемся, есть ли способ инжектить ViewModel с помощью Dagger без огромного количества кода или потерь валидации графа зависимостей во время компиляции.
Disclaimer: чтобы разобраться в содержании этой статьи, вам потребуется знание Dagger.
Основная сложность использования DI с ViewModel заключается в том, что при создании ViewModel должна так или иначе проходить через ‘ViewModelProvider(this, factory).get(YourViewModel::class.java)’. Этот метод может быть скрыт внутри делегата ‘by viewModels < factory >’ или вызван напрямую. Без этого ViewModel не будет сохраняться при повороте экрана, а метод onCleared() не будет вызываться, когда ViewModel больше не нужна.
Чтобы сделать примеры как можно проще, я предположу, что у нас есть один компонент AppComponent. Но почти все примеры можно адаптировать к архитектуре с Subcomponent для каждой ViewModel или одним Subcomponent на все вьюмодели.
В большинстве примеров мы будем использовать такую ViewModel:
Repository предоставляется одним из модулей в AppComponent или просто имеет конструктор с аннотацией @Inject.
Также я предполагаю, что мы можем легко получить AppComponent внутри фрагмента, используя метод:
Теперь давайте посмотрим на существующие подходы и разберемся, какие проблемы они скрывают.
1. Map , Provider > в ViewModelProvider.Factory (с мультибиндингом или без)
Есть несколько вариантов реализации такого подхода. Самый простой — инжектить провайдеры в фабрику вьюмоделей и там собирать их в Map вручную:
Добавляем фабрику в компонент:
Теперь мы можем создать вьюмодель внутри фрагмента или активити:
Этот же подход можно реализовать, используя аннотации @IntoMap и @ClassKey(VM::class) для мультибайндинга, но суть будет та же.
Такой подход работает и позволяет заинжектить вьюмодель в пару строк, но у него есть и определенные ограничения:
Фабрика становится сервис-локатором. Это значит, что если мы забываем добавить несколько строк в фабрику (или модуль, если используем мультибайндинг) для новой вьюмодели, то получаем исключение во время выполнения приложения без какой-либо индикации во время компиляции. Обнаружение проблем во время компиляции — это одно из главных преимуществ Dagger, и не хотелось бы его терять.
Мы не можем передавать параметры во вьюмодель из фрагмента или активити. Этот подход не позволяет использовать @AssistedInject, хотя во всех вьюмоделях для однообразных параметров вроде SavedStateHandle можно использовать Subcomponent.
2. Используем Hilt
Hilt — это отличный инструмент от Google. С ним можно обойтись меньшим количеством кода. Пока мы не передаем никаких дополнительных параметров из фрагмента во вьюмодель, этот инструмент работает как часы:
Во фрагменте нам нужна будет только одна строчка (кроме необходимой аннотации):
Само собой это будет работать только в том случае, если корректно настроить Hilt, но для этого есть множество статей и официальная инструкция. Обратите внимание: если забыть @HiltViewModel, то приложение «упадет» во время выполнения, а не во время компиляции.
Но если мы хотим передать что-то из фрагмента во вьюмодель, то придется инжектить AssistedFactory во фрагмент и создавать фабрику вьюмоделей. ViewModel в этом случае может выглядеть примерно так:
Нам также понадобится универсальная фабрика вьюмоделей, просто чтобы избежать повторяющегося кода:
Теперь мы можем инжектить фабрику во фрагмент и использовать ее для создания вьюмодели:
В этом случае мы теряем некоторые преимущества Hilt и получаем что-то больше похожее на старый добрый Dagger с дополнительными шагами. Если это вас устраивает или вам не нужно ничего передавать во вьюмодель из фрагмента, то Hilt будет для вас отличным решением.
3. Получаем ViewModel из DI и передаем ссылку в фабрику во viewModels-делегате
Такой подход не будет работать, потому что лямбда, переданная во viewModels, будет вызываться при каждом повороте экрана и создавать новые экземпляры вьюмодели. Как ни странно, я видел такой подход в статье где-то на просторах интернета.
Метод viewModelComponent().myViewModel(), который вызывается при каждом повороте экрана, приведет к тому, что вьюмодели будут множиться. Если мы используем какие-то ресурсы внутри вьюмодели или запускаем корутины в конструкторе, то эти ресурсы и контекст для корутин не будут чиститься для всех вьюмоделей, кроме первой.
Даже если бы этот подход работал, есть шанс, что кто-нибудь вызовет ViewModelProvider().get() напрямую и получит тот же самый результат.
4. Передаем лямбду для создания ViewModel в фабрику
В принципе, этот подход работает, но в нем есть скрытая опасность. Допустим, мы используем ViewModel для сохранения какого-то утилитарного класса при повороте экрана (например, Router), а этот класс имеет конструктор, помеченный аннотацией @Inject, и наследует от ViewModel. Тогда мы можем, не глядя в код Router, добавить его как параметр в конструктор вьюмодели:
Что произойдет в таком случае? Router будет создан вместе с вьюмоделью и заинжекчен в ее конструктор, ничего необычного. Но когда будет вызван метод onCleared() вьюмодели, этот же метод не будет вызван для Router. Это может потенциально привести к утечке памяти или еще более неприятным и сложным к поимке багам.
Обратите внимание, что то же самое может произойти, если заинжектить и более очевидную вьюмодель внутрь вьюмодели. Не очень корректно использовать их таким образом, но лучший подход не оставляет места для ошибки, иначе мы бы использовали Koin или другой сервис-локатор вместо Dagger и не беспокоились бы о таких вещах.
Как же избежать этой проблемы? Например, использовать @AssistedInject.
5. Используем @AssistedInject
Если мы договоримся всегда использовать @AssistedInject для классов, наследующих от ViewModel, то указанная выше проблема не возникнет, а также у нас будет возможность передавать во вьюмодель дополнительные параметры.
Давайте немного подкорректируем нашу вьюмодель, чтобы поддержать assisted injection:
Подготовим фабрику, аналогичную предыдущему примеру:
И один метод, чтобы создавать «ленивый» делегат с фабрикой:
И, наконец, мы можем получить вьюмодель во фрагменте:
Этот подход немного сложнее предыдущих, но позволяет избежать редких проблем и передавать во вьюмодель дополнительные параметры, влючая SavedStateHandle, параметры страницы и прочее.
6. Бонус
В моей предыдущей статье я предложил подход, который освобождает от необходимости наследовать классы от ViewModel, а также альтернативный способ очистки ресурсов вьюмоделей. Похожий подход используется в этой библиотеке, так что я не один до этого додумался.
Если вы читали статью, то могли заметить, что lazyViewModel чем-то похож на getOrCreatePersisted из той статьи, хотя последний и не возвращает делегат.
Мы могли бы упаковать все зависимости из статьи в один Subcomponent примерно так:
Добавим функцию для создания сабкомпонента во фрагменте:
Добавим зависимостей и уберем наследование от ViewModel из нашей вьюмодели:
Упакуем lazy и getOrCreatePersisted в один метод:
И теперь можем легко создать нашу вьюмодель во фрагменте:
Таким образом, у нас не будет необходимости использовать @AssistedInject в том случае, когда он не нужен. Все зависимости вьюмодели, которые требуют очищения ресурсов, могут сами разобраться с ними, приняв в конструктор PersistentLifecycle в качестве параметра. Также наша вьюмодель больше не зависит напрямую от фреймворка, хотя уйти в Kotlin Multiplatform нам пока не позволит Dagger.
Заключение
Хотя это и не всегда очевидно, есть способы инжектить вьюмодель с помощью Dagger, не терять при этом валидацию графа зависимостей при компиляции и не использовать огромное количества кода. Особенно если избавиться от наследования ViewModel, всегда создавать вьюмодель через @AssistedInject или внимательно следить за тем, чтобы во вьюмодели не инджектились другие вьюмодели.
Источник
Unresolved reference: viewModelScope — Kotlin Android
I try to add viewModelScope to a basic viewModel but android studio doesn’t recognize it.
I tried to change my gradle build file with some solution I found but nothing works.
Here an extract of my build.gradle app
When I type viewModelScope in my viewModel it say Unresolved reference: viewModelScope .
7 Answers 7
for now its in alpha, so please update your gradle to use the following dependencies:
I’ve had the same issue and I’ve just imported: «androidx.navigation:navigation-fragment-ktx:2.2.0-rc03» «androidx.lifecycle:lifecycle-livedata-ktx:2.2.0-rc03» Even though I thought fragment-ktx was not really related. Took me a while to figure that out. Hope it helps!
In my case i forgot to extends ViewModel in that class, the class you use for viewModelScope must be like yourModelClass : ViewModel() in kotlin and for java yourModelClass extends ViewModel
Also check that you are in the correct file. I had the same problem for a moment and I came to this page, but later on, I realized I accidentally tried to run viewModelScope.launch on my Fragment.
viewModelScope.launch is only available in your ViewModels and lifecycleScope.launch in your lifecycle aware components.
viewModelScope was introduced with release 2.1.0 , see here.
Check whether lifecycle-viewmodel-ktx-2.2.0-alpha01.aar is installed. For me there is no error message with the settings you wrote. However, there is an error message when using an earlier version:
It looks like you’ve got two different versions of the androidX lifecycle libraries in use.
Источник
As ViewModelProviders.of() is deprecated, how should I create object of ViewModel?
I have been trying to create an Object of ViewModel in an Activity but ViewModelProviders is deprecated So what’s the alternative to create the ViewModel’s object.
12 Answers 12
This Gradle upgrade created the problem for me.
IN MAIN ACTIVITY Java/Kotlin Files
This import statement
had to be changed to
This KOTLIN viewModel statement
had to be changed to
This line of JAVA code
had to be changed to
and then it all worked for me.
Based on: An outline of the steps that created the problem for me
ViewModelProviders.of() has been deprecated.
Use ViewModelProvider constructors directly as they now handle the default ViewModelProvider.Factory role.
Instead of ViewModelProviders we should now use ViewModelProvider constructors and it has three:
1. If you are not using a ViewModelProvider.Factory to pass additional arguments to your ViewModel , you can use the first one. so:
can be replaced with:
AppCompatActivity and different kinds of Fragment s are indirect subclasses of ViewModelStoreOwner (see the complete list of its known subclasses here), so you can use them in this constructor.
2. But if you are using a ViewModelProvider.Factory , you should use the second or the third constructors:
can be replaced with:
OR based on the documentation of ViewModelStore :
Источник
ViewModelProviders is deprecated in 1.1.0
Looking at the Google docs for ViewModel , they show the below sample code on how to get a ViewModel :
When using the latest dependency android.arch.lifecycle:extensions:1.1.1 there is no such class ViewModelProviders .
Going to the documentation for ViewModelProviders , I saw a comment saying:
This class was deprecated in API level 1.1.0. Use ViewModelProvider.AndroidViewModelFactory
The problem is, when trying to use ViewModelProvider.AndroidViewModelFactory , cannot find an equivalent of method to get the instance of the ViewModel .
What i tried doing:
Hence the name of the method create , I get a new instance of the ViewModel every-time I call it, which is not what I am after.
Any ideas what is the replacement of deprecated code above?
26 Answers 26
I use lifecycle-extensions 2.2.0 version:
It should be work, using ViewModelProvider constructor.
I find another elegant way to achieve, Android KTX can help
2020/06/25: corrected the case of the delegate
As @FantasyFang mentioned in his answer, use the lastest version for the lifecycle:lifecycle-extensions which in this moment is 2.2.0-alpha03 . So you should add in your build.gradle file the following line:
For those who are using Java, to solve this, pass those arguments directly to ViewModelProvider’s constructor:
Or if you don’t use a factory, simply use:
Without passing your the factory object.
Import
Using
As of 2.2.0. the lifecycle-extensions has been deprecated. Refer to Google Documentation.
This is the cut from the page:
The APIs in lifecycle-extensions have been deprecated. Instead, add dependencies for the specific Lifecycle artifacts you need.
The new libraries are:
The new code for JAVA:
UPDATE 2020-06-16: Presently ViewModelProviders is deprecated and should no longer be used. This question and answer were from late 2018, when that was not the case. This question and answer are also for the older Architecture Components edition of ViewModelProviders , not the AndroidX edition.
When using the latest dependency android.arch.lifecycle:extensions:1.1.1 there is no such class ViewModelProviders .
Yes, there is. To demonstrate this:
Create a new project in Android Studio 3.2.1 (with Kotlin, minSdkVersion 21, «empty activity» template)
Add android.arch.lifecycle:extensions:1.1.1 to the dependencies of the app module
This will give you an app/build.gradle like:
You will then see that library show up in «External Libraries» with that class:
And you will be able to reference that class:
Going to the documentation for ViewModelProviders, I saw a comment saying: This class was deprecated in API level 1.1.0. Use ViewModelProvider.AndroidViewModelFactory
That comment is underneath the ViewModelProviders.DefaultFactory class entry and refers to that class, not ViewModelProviders :
Any ideas what is the replacement of deprecated code above?
Early in 2020, Google have deprecated the ViewModelProviders class, in version 2.2.0 of the androidx lifecycle library.
It’s no longer necessary to use ViewModelProviders to create an instance of a ViewModel, you can pass your Fragment or Activity instance to the ViewModelProvider constructor instead.
If you use the code like:
you’ll get a warning that ViewModelProviders has been deprecated.
To avoid using deprecated libraries, make the following changes:
In the build.gradle (Module: app) file, use version 2.2.0 of the lifecycle components:
If you want to use the ViewModel from a Fragment instead, use
fragment-ktx automatically includes activity-ktx, so you don’t need to specify both in the dependencies.
You need to specify Java 8 in the android section :
In your Fragment or Activity, change the import to:
import androidx.activity.viewModels
The code to create a ViewModel then becomes:
Use the viewModel object as :
where newNumer is a LiveData object
In a Fragment that you want to share the Activity’s ViewModel, you’d use
Источник