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 устарел.

Чтобы избежать использования устаревших библиотек, внесите следующие изменения.

  1. В файле 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, поэтому вам не нужно указывать оба в зависимостях.

  2. В разделе 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"
  }
}
  1. В вашем фрагменте или действии измените импорт на:

    импортировать androidx.activity.viewModels

  2. Код для создания 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()

В моем случае плагин отсутствовал , внедрил его и теперь он работает.

Итак, есть несколько шагов , которые мы должны проверить

  1. @HiltAndroidAppв классе приложения , и приложение зарегистрировано вAndroid Manifest

  2. Activity , а также Fragment должны быть аннотированы с помощью@AndroidEntryPoint

  3. Убедитесь, что зависимости и плагины реализованы правильно, например, в моем случае текущие зависимости :

реализации:

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() 
{...}

Просто расширьте «AndroidViewModel» вместо «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 отправит вам сообщение об ошибке "невозможно создать экземпляр объекта модели"

Другие вопросы по тегам