Android ViewModel не имеет конструктора с нулевым аргументом
Я следую этой документации, чтобы узнать о LiveData и ViewModel. В документе класс ViewModel имеет конструктор как таковой,
public class UserModel extends ViewModel {
private MutableLiveData<User> user;
@Inject UserModel(MutableLiveData<User> user) {
this.user = user;
}
public void init() {
if (this.user != null) {
return;
}
this.user = new MutableLiveData<>();
}
public MutableLiveData<User> getUser() {
return user;
}
}
Однако, когда я запускаю код, я получаю исключение:
final UserViewModelviewModel = ViewModelProviders.of(this).get(UserViewModel.class);
Причина: java.lang.RuntimeException: невозможно создать экземпляр класса UserViewModel. Причина: java.lang.InstantiationException: java.lang.Class не имеет конструктора с нулевым аргументом.
25 ответов
При инициализации подклассов ViewModel
с помощью ViewModelProviders
по умолчанию ожидает вашего UserModel
класс с нулевым аргументом конструктора. В вашем случае ваш конструктор имеет аргумент MutableLiveData<User> user
Один из способов это исправить - использовать конструктор по умолчанию без аргументов для вашего UserModel
В противном случае, если вы хотите иметь конструктор ненулевого аргумента для вашего класса ViewModel, возможно, вам придется создать пользовательский ViewModelFactory
класс для инициализации вашего экземпляра ViewModel, который будет реализовывать ViewModelProvider.Factory
интерфейс.
Я еще не пробовал это, но вот ссылка на отличный пример от Google для того же: https://github.com/googlesamples/android-architecture-components/tree/master/GithubBrowserSample. В частности, проверьте этот класс GithubViewModelFactory.java для кода Java и этот класс GithubViewModelFactory.kt для соответствующего кода Kotlin
В моем случае, когда я использую HILT, не хватало одной аннотации над фрагментом, имеющим ViewModel: @AndroidEntryPoint
@AndroidEntryPoint
class BestFragment : Fragment() {
....
Конечно, в вашем классе ViewModel вам также необходимо добавить аннотации к тому, что нужно HILT: @ViewModelInject
class BestFragmentViewModel @ViewModelInject constructor(var userManager: UserManager) : ViewModel() {
....
}
ViewModelFactory
что даст нам правильную ViewModel из ViewModelModule
public class ViewModelFactory implements ViewModelProvider.Factory {
private final Map<Class<? extends ViewModel>, Provider<ViewModel>> viewModels;
@Inject
public ViewModelFactory(Map<Class<? extends ViewModel>, Provider<ViewModel>> viewModels) {
this.viewModels = viewModels;
}
@Override
public <T extends ViewModel> T create(Class<T> modelClass) {
Provider<ViewModel> viewModelProvider = viewModels.get(modelClass);
if (viewModelProvider == null) {
throw new IllegalArgumentException("model class " + modelClass + " not found");
}
return (T) viewModelProvider.get();
}
}
ViewModelModule
отвечает за связывание всех классов ViewModel в Map<Class<? extends ViewModel>, Provider<ViewModel>> viewModels
@Module
public abstract class ViewModelModule {
@Binds
abstract ViewModelProvider.Factory bindViewModelFactory(ViewModelFactory viewModelFactory);
//You are able to declare ViewModelProvider.Factory dependency in another module. For example in ApplicationModule.
@Binds
@IntoMap
@ViewModelKey(UserViewModel.class)
abstract ViewModel userViewModel(UserViewModel userViewModel);
//Others ViewModels
}
ViewModelKey
это аннотация для использования в качестве ключа на карте и выглядит как
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@MapKey
@interface ViewModelKey {
Class<? extends ViewModel> value();
}
Теперь вы можете создать ViewModel и удовлетворить все необходимые зависимости из графика
public class UserViewModel extends ViewModel {
private UserFacade userFacade;
@Inject
public UserViewModel(UserFacade userFacade) { // UserFacade should be defined in one of dagger modules
this.userFacade = userFacade;
}
}
Создавая ViewModel
public class MainActivity extends AppCompatActivity {
@Inject
ViewModelFactory viewModelFactory;
UserViewModel userViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
((App) getApplication()).getAppComponent().inject(this);
userViewModel = ViewModelProviders.of(this, viewModelFactory).get(UserViewModel.class);
}
}
И не надо подделывать ViewModelModule
в modules
список
@Singleton
@Component(modules = {ApplicationModule.class, ViewModelModule.class})
public interface ApplicationComponent {
//
}
Для рукояти:
Простое добавление
@AndroidEntryPoint
для основной активности и фрагментов, и
@HiltViewModel
для viewModels
Пример после:
@HiltViewModel
class SplashViewModel @Inject constructor(
@AndroidEntryPoint
class SplashFragment : Fragment() {
private lateinit var b: SplashFragmentBinding
private val vm: SplashViewModel by viewModels()
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
У меня были проблемы с
@ViewModelInject
так как использование HILT устарело. Чтобы решить проблему, измените этот код:
class MainViewModel @ViewModelInject constructor(
val mainRepository: MainRepository
): ViewModel()
с участием:
@HiltViewModel
class MainViewModel @Inject constructor(
val mainRepository: MainRepository
): ViewModel()
Конечно, не забудьте добавить
@AndroidEntryPoint
аннотация над вашим фрагментом или действием (где бы вы ни создавали свой экземпляр), например:
@AndroidEntryPoint
class UsageFragment : Fragment(R.layout.fragment_usage) {
.
.
.
}
Окончательный совет:
Вы можете сразу увидеть, работает ли HILT, посмотрев, есть ли значки слева в вашем
ViewModel
.
Здесь не работает :
Вот и работает :
Если вы не видите их после обновления кода, нажмите
Build -> Rebuild Project
В начале 2020 года Google исключил класс ViewModelProviders в версии 2.2.0 библиотеки жизненного цикла androidx.
Больше нет необходимости использовать ViewModelProviders для создания экземпляра ViewModel, вместо этого вы можете передать свой фрагмент или экземпляр Activity в конструктор ViewModelProvider.
Если вы используете такой код:
val viewModel = ViewModelProviders.of(this).get(CalculatorViewModel::class.java)
вы получите предупреждение о том, что ViewModelProviders устарел.
Чтобы избежать использования устаревших библиотек, внесите следующие изменения.
В файле build.gradle (Module: app) используйте версию 2.2.0 компонентов жизненного цикла:
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
Также добавьте
implementation "androidx.activity:activity-ktx:1.1.0"
Если вы хотите вместо этого использовать ViewModel из фрагмента, используйте
implementation "androidx.fragment:fragment-ktx:1.2.2"
fragment-ktx автоматически включает activity-ktx, поэтому вам не нужно указывать оба в зависимостях.
В разделе android нужно указать Java 8:
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.kgandroid.calculator"
minSdkVersion 17
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
kotlinOptions {
jvmTarget = "1.8"
}
}
В вашем фрагменте или действии измените импорт на:
импортировать androidx.activity.viewModels
Код для создания ViewModel становится таким:
val viewModel: CalculatorViewModel by viewModels()
вместо того
val viewModel = ViewModelProviders.of(this).get(CalculatorViewModel::class.java)
Используйте объект viewModel как:
val viewModel: CalculatorViewModel от viewModels()
viewModel.newNumber.observe(this, Observer {stringResult -> newNumber.setText(stringResult)})
где newNumer - объект LiveData
Во фрагменте, который вы хотите поделиться ViewModel Activity, вы должны использовать
val viewModel: CalculatorViewModel by activityViewModels()
Это эквивалент передачи экземпляра Activity в (устаревшей) функции ViewModelProviders.of().
У меня была такая же ошибка. Я использую Hilt, и в моем случае это отсутствовала зависимость компилятора второй рукоятки
теперь у меня есть оба:
kapt com.google.dagger:hilt-android-compiler:#version
и
kapt androidx.hilt:hilt-compiler:#version
в моем файле build.gradle уровня приложения, и он работает.
com.google.dagger: hilt-android-compiler необходим, когда вы используете плагин Hilt Android Gradle (см. документацию) и androidx.hilt:hilt-compiler:#version, по-видимому, требуется, когда вы хотите интегрировать Hilt и Jetpack, например, для внедрения Android Модель просмотра Jetpack (см. Документацию)
2020-07-29 10:13:25
За lifecycle_version = '2.2.0'
API ViewProviders.of устарел. Это моя ситуация:
class MainActivityViewModel(application: Application) : AndroidViewModel(application) {
private var repository: UserRepository
val allUsers: LiveData<List<User>>
......
error:
val userViewModel = ViewModelProvider(this).get(MainActivityViewModel::class.java)
success:
val factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application)
userViewModel = ViewModelProvider(this,factory).get(MainActivityViewModel::class.java)
Проходят application
по api ViewModelProvider.AndroidViewModelFactory.getInstance
если вы используете рукоять, возможно, вы забыли аннотировать свою активность или фрагмент с помощью @AndroidEntryPoint
Если вы используете навигацию-компоновку и вызываете свой экран внутри блока NavHost, рукоять не может внедрить модель представления. Для этого вы можете использовать этот способ;
NavHost(navHostController, startDestination = "HomeScreen") {
composable("HomeScreen") {
HomeScreen(homeScreenViewModel = hiltViewModel())
}
}
Не забудьте добавить эту зависимость для
hiltViewModel()
->
implementation("androidx.hilt:hilt-navigation-compose:1.0.0-alpha02")
Наиболее частой причиной этого сбоя является отсутствие @AndroidEntryPoint в начале вашего фрагмента / действия, как показано ниже:
**@AndroidEntryPoint**
class MyFragment : Fragment {
val viewModel by viewModels<MyViewModel>()
}
Точно так же ваша ViewModel должна быть аннотирована HiltViewModel, как показано ниже:
@HiltViewModel
class MyViewModel@Inject constructor(
private val var1: Type1
) : ViewModel()
В моем случае плагин отсутствовал , внедрил его и теперь он работает.
Итак, есть несколько шагов , которые мы должны проверить
@HiltAndroidApp
в классе приложения , и приложение зарегистрировано вAndroid Manifest
Activity , а также Fragment должны быть аннотированы с помощью
@AndroidEntryPoint
Убедитесь, что зависимости и плагины реализованы правильно, например, в моем случае текущие зависимости :
реализации:
implementation("com.google.dagger:hilt-android:2.44") implementation("androidx.hilt:hilt-navigation-fragment:1.0.0") implementation ("androidx.lifecycle:lifecycle-livedata-ktx:2.5.1") kapt("com.google.dagger:hilt-android-compiler:2.44")
Плагин:id 'dagger.hilt.android.plugin'
путь к классам:classpath("com.google.dagger:hilt-android-gradle-plugin:$hiltVersion")
Для всех, у кого есть эта проблема, я столкнулся с ней следующим образом:
1- В вашей модели просмотра не создавайте конструктор, просто создайте функцию, которая принимает контекст и другие ваши параметры
public class UserModel extends ViewModel {
private Context context;
private ..........;
public void init(Context context, ......) {
this.context = context;
this..... = ....;
}
// Your stuff
}
2- В вашей деятельности:
UserViewModel viewModel = ViewModelProviders.of(this).get(UserViewModel.class);
viewModel.init(this, .....);
// You can use viewModel as you want
3- В вашем фрагменте
UserViewModel viewModel = ViewModelProviders.of(getActivity()).get(UserViewModel.class);
viewModel.init(getContext(), .....);
// You can use viewModel as you want
Для коина:
У меня была эта проблема, оказывается, я только что импортировал viewModels() из AndroidX вместо viewModel() из Koin
Я написал библиотеку, которая должна сделать достижение этого более простым и понятным, не требуя многосвязных или заводских шаблонов, а также предоставляя возможность дальнейшей параметризации ViewModel
во время выполнения: https://github.com/radutopor/ViewModelFactory
@ViewModelFactory
class UserViewModel(@Provided repository: Repository, userId: Int) : ViewModel() {
val greeting = MutableLiveData<String>()
init {
val user = repository.getUser(userId)
greeting.value = "Hello, $user.name"
}
}
По мнению:
class UserActivity : AppCompatActivity() {
@Inject
lateinit var userViewModelFactory2: UserViewModelFactory2
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_user)
appComponent.inject(this)
val userId = intent.getIntExtra("USER_ID", -1)
val viewModel = ViewModelProviders.of(this, userViewModelFactory2.create(userId))
.get(UserViewModel::class.java)
viewModel.greeting.observe(this, Observer { greetingText ->
greetingTextView.text = greetingText
})
}
}
У меня была такая же проблема, я исправил ее, добавив в свой проект библиотеку пользовательского интерфейса:
implementation 'androidx.navigation:navigation-ui-ktx:2.2.2'
Вы можете передавать данные в viewmodel через фабрику viewmodel. Вы также можете ознакомиться с этим примером.
class UserViewModelFactory(private val context: Context) : ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return UserViewModel(context) as T
}
}
class UserViewModel(private val context: Context) : ViewModel() {
private var listData = MutableLiveData<ArrayList<User>>()
init{
val userRepository : UserRepository by lazy {
UserRepository
}
if(context.isInternetAvailable()) {
listData = userRepository.getMutableLiveData(context)
}
}
fun getData() : MutableLiveData<ArrayList<User>>{
return listData
}
}
Вы можете вызвать viewmodel в действии, как показано ниже.
val userViewModel = ViewModelProviders.of(this,UserViewModelFactory(this)).get(UserViewModel::class.java)
В моем случае я использовалKoin
и получаю эту ошибку. Я создавал свою модель представления следующим образом:
val viewModel = ViewModelProvider(requireActivity())[HomeViewModel::class.java]
Все, что вам нужно сделать, это инициализировать вашу модель представления следующим образом:
import org.koin.android.ext.android.get
val viewModel = get<HomeViewModel>()
Что касается принятого ответа, если вы используете Hilt и только что добавили свою ViewModel, не забудьте перестроить свой проект. Простой запуск проекта не создает необходимых фабричных классов (которые должны генерироваться автоматически), как было обнаружено на собственном опыте.
Приведенные ниже классы не существовали до перестройки:
Создайте конструктор без аргументов, т.е.
Default/ No-arg constructor
в классе viewmodel.
В моем случае я забыл сгенерировать этот конструктор и потратил 30 минут на обучение - после этого у меня все заработало.
В документации для разработчиков Android есть примеры создания собственной фабрики ViewModel: https://developer.android.com/topic/libraries/architecture/viewmodel#viewmodel-with-dependencies.
По сути, если вы используете Kotlin, вы определяете фабрику ViewModel внутри объекта-компаньона — здесь они передаются в репозиторий и сохраняются в конструктор ViewModel, но вы можете передать любые зависимости, которые нужны вашей ViewModel:
class MyViewModel(
private val myRepository: MyRepository,
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
// ViewModel logic
// ...
// Define ViewModel factory in a companion object
companion object {
val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(
modelClass: Class<T>,
extras: CreationExtras
): T {
// Get the Application object from extras
val application = checkNotNull(extras[APPLICATION_KEY])
// Create a SavedStateHandle for this ViewModel from extras
val savedStateHandle = extras.createSavedStateHandle()
return MyViewModel(
(application as MyApplication).myRepository,
savedStateHandle
) as T
}
}
}
}
Затем вы используете свою фабрику, чтобы получить ViewModel из своей деятельности:
class MyActivity : AppCompatActivity() {
private val viewModel: MyViewModel by viewModels { MyViewModel.Factory }
// Rest of Activity code
}
Если вы используете рукоять кинжала и версию 2.31 или выше, не используйте "ViewModelInject" в классе модели представления. Dagger предоставляет новый способ использования модели просмотра, поэтому следуйте инструкциям ниже.
1: Добавьте @HiltViewModel поверх класса 2: Используйте Inject intead of ViewModelInject
@HiltViewModel
class AuthViewModel @Inject constructor(
private val authRepository: AuthRepository,
...
) : ViewModel()
{...}
Проблема может быть решена путем расширения UserModel
от AndroidViewModel
который является контекстно-зависимым ViewModel и требует Application
конструктор только для параметров. (документация)
Экс- (в котлине)
class MyVm(application: Application) : AndroidViewModel(application)
Это работает для версии 2.0.0-alpha1
,
Если у вас есть параметр в конструкторе, то:
Открытый конструктор DAGGER 2 для зависимости @inject
@Inject
public UserViewModel(UserFacade userFacade)
{
this.userFacade = userFacade;
}
В противном случае Dagger 2 отправит вам сообщение об ошибке "невозможно создать экземпляр объекта модели"