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 同学们 没有零参数构造函数

69617 次浏览

While initializing subclasses of ViewModel using ViewModelProviders, by default it expects your UserModel class to have a zero argument constructor. In your case your constructor has the argument MutableLiveData<User> user.

One way to fix this is to have a default no arg constructor for your UserModel.

Otherwise, if you want to have a non-zero argument constructor for your ViewModel class, you may have to create a custom ViewModelFactory class to initialise your ViewModel instance, which implements the ViewModelProvider.Factory interface.

I have not tried this yet, but here's a link to an excellent Google sample for this: github.com/googlesamples/android-architecture-components. Specifically, check out this class GithubViewModelFactory.java for Java code and this class GithubViewModelFactory.kt for the corresponding Kotlin code.

ViewModelFactory that will provide us a right ViewModel from 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 is responsible for binding all over ViewModel classes into
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 is an annotation for using as a key in the Map and looks like

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@MapKey
@interface ViewModelKey {
Class<? extends ViewModel> value();
}

Now you are able to create ViewModel and satisfy all necessary dependencies from the graph

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;
}
}

Instantiating 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);


}
}

And do not forger to add ViewModelModule into modules list

@Singleton
@Component(modules = {ApplicationModule.class, ViewModelModule.class})
public interface ApplicationComponent {
//
}

If you have parameter in constructor then :

DAGGER 2 public constructor for @inject dependency

@Inject
public UserViewModel(UserFacade userFacade)
{
this.userFacade = userFacade;
}

Otherwise dagger 2 will send you error "can not instantiate viewmodel object"

The problem can be resolved by extending UserModel from AndroidViewModel which is application context aware ViewModel and requires Application parameter-only constructor. (documentation)

Ex- (in kotlin)

class MyVm(application: Application) : AndroidViewModel(application)

This works for version 2.0.0-alpha1.

I wrote a library that should make achieving this more straightforward and way cleaner, no multibindings or factory boilerplate needed, while also giving the ability to further parametrise the ViewModel at runtime: 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"
}
}

In the view:

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
})
}
}

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:

val viewModel = ViewModelProviders.of(this).get(CalculatorViewModel::class.java)

you'll get a warning that ViewModelProviders has been deprecated.

You can instead do:

val viewModel = ViewModelProvider(this).get(CalculatorViewModel::class.java)

Or alternatively, to use a delegate, make the following changes.

  1. In the build.gradle (Module: app) file, use version 2.2.0 of the lifecycle components: implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'

    Also add implementation "androidx.activity:activity-ktx:1.1.0"

    If you want to use the ViewModel from a Fragment instead, use

    implementation "androidx.fragment:fragment-ktx:1.2.2"

    fragment-ktx automatically includes activity-ktx, so you don't need to specify both in the dependencies.

  2. You need to specify Java 8 in the android section :

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. In your Fragment or Activity, change the import to:

    import androidx.activity.viewModels

  2. The code to create a ViewModel then becomes:

    val viewModel: CalculatorViewModel by viewModels()

    instead of

    val viewModel = ViewModelProviders.of(this).get(CalculatorViewModel::class.java)

    Use the viewModel object as :

    val viewModel: CalculatorViewModel by viewModels()

    viewModel.newNumber.observe(this, Observer { stringResult -> newNumber.setText(stringResult) })

where newNumer is a LiveData object

In a Fragment that you want to share the Activity's ViewModel, you'd use


`val viewModel: CalculatorViewModel by activityViewModels()`


**That's the equivalent of passing the Activity instance in the (deprecated)
ViewModelProviders.of() function.**

Create a constructor with out any arguments i.e.

Default/ No-arg constructor

in the viewmodel class .

In my case, I forgot to generate this constructor and wasted 30 minutes when I'm learning - after that it worked for me.

For everyone who has this problem, I encountered it this way:
1- In Your ViewModel, don't create a constructor, just create a function that takes a Context and your other parameters

public class UserModel extends ViewModel {
private Context context;
private ..........;


public void init(Context context, ......) {
this.context = context;
this..... = ....;
}




// Your stuff
}


2- In your activity:


UserViewModel viewModel = ViewModelProviders.of(this).get(UserViewModel.class);
viewModel.init(this, .....);


// You can use viewModel as you want

3- In your fragment

UserViewModel viewModel = ViewModelProviders.of(getActivity()).get(UserViewModel.class);
viewModel.init(getContext(), .....);


// You can use viewModel as you want

i had the same issue, fixed it by adding navigation ui library to my project:

implementation 'androidx.navigation:navigation-ui-ktx:2.2.2'

In my case as I'm using HILT, it was lacking one annotation above the Fragment that has a ViewModel: @AndroidEntryPoint

@AndroidEntryPoint
class BestFragment : Fragment() {
....

Of course in your ViewModel class you also need to Annotate with what HILT needs: @ViewModelInject

class BestFragmentViewModel @ViewModelInject constructor(var userManager: UserManager) : ViewModel() {
....
}

2020-07-29 10:13:25

For lifecycle_version = '2.2.0' ViewProviders.of API is deprecated . It`s my situation :

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)

Pass application by api ViewModelProvider.AndroidViewModelFactory.getInstance


With regards to the accepted answer, if you are using Hilt and you just added your ViewModel, don't forget to rebuild your project. Simply running the project does not create the needed factory classes (which are supposed to be automatically generated), as discovered the hard way.

The classes below did not exist before the rebuild:

enter image description here

I had the same error. I'm using Hilt, and in my case it was a missing second hilt compiler dependency

now i have both:

kapt com.google.dagger:hilt-android-compiler:#version

and

kapt androidx.hilt:hilt-compiler:#version

in my app level build.gradle file and it works.

com.google.dagger:hilt-android-compiler is needed when your using the Hilt Android Gradle plugin (see docs) and androidx.hilt:hilt-compiler:#version is apparently needed when you want Hilt and Jetpack integration, like injecting Android Jetpack ViewModel (see docs)

if you're using hilt, you probably might forgot to annotate your activity or fragment with @AndroidEntryPoint

I had some issues with @ViewModelInject since it has been deprecated using HILT. To solve the problem change this code:

class MainViewModel @ViewModelInject constructor(
val mainRepository: MainRepository
): ViewModel()

with:

@HiltViewModel
class MainViewModel @Inject constructor(
val mainRepository: MainRepository
): ViewModel()

Of course, remember to add the @AndroidEntryPoint annotation above your fragment or activity (wherever you are instantiating your ViewModel) like this:

@AndroidEntryPoint
class UsageFragment : Fragment(R.layout.fragment_usage) {
.
.
.
}

Ultimate tip:

You can immediately see if HILT is working looking if there are the icons on the left in your ViewModel.

Here it does not work:

enter image description here

Here it does work:

enter image description here

If you don't see them after updating the code click on Build -> Rebuild Project

For Koin:

I had this issue, turns out I just imported viewModels() from AndroidX instead of viewModel() from Koin

The most common reason for this failure is Missing @AndroidEntryPoint at the start of your Fragment/Activity as shown below:

@AndroidEntryPoint
class MyFragment : Fragment {
val viewModel by viewModels<MyViewModel>()
}

Similarly, you ViewModel should be annotated by HiltViewModel as shown following:

@HiltViewModel
class MyViewModel@Inject constructor(
private val var1: Type1
) : ViewModel()

If you are using navigation-compose and calling your screen inside the NavHost block, the hilt can't inject the view model. For this, you can use this way;

    NavHost(navHostController, startDestination = "HomeScreen") {
composable("HomeScreen") {
HomeScreen(homeScreenViewModel = hiltViewModel())
}
}

Don't forget to add this dependency for hiltViewModel() -> implementation("androidx.hilt:hilt-navigation-compose:1.0.0-alpha02")

If you are using dagger hilt and version 2.31 or higher then don't use "ViewModelInject" in view model class. Dagger is providing new way to use viewmodel so please follow below instruction.

1: Add @HiltViewModel on top of class 2: Use Inject intead of ViewModelInject

@HiltViewModel
class AuthViewModel @Inject constructor(
private val authRepository: AuthRepository,
...
) : ViewModel()
{...}

For Hilt:

Simple add @AndroidEntryPoint for main acticity and fragments, and @HiltViewModel for viewModels

Example after:

@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

Android Developer documentation has examples for how to create a custom ViewModel factory: https://developer.android.com/topic/libraries/architecture/viewmodel#viewmodel-with-dependencies

Basically if you are using Kotlin you define the ViewModel factory inside of a companion object - here they are passing in a repository and saved state to the constructor of the ViewModel but you can pass in whatever dependencies your ViewModel needs:

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
}
}
}
}

Then you use your factory to get the ViewModel from your Activity:

class MyActivity : AppCompatActivity() {


private val viewModel: MyViewModel by viewModels { MyViewModel.Factory }


// Rest of Activity code
}

In my case, I was using Koin and getting this error. I was creating my viewModel isntance like this:

 val viewModel = ViewModelProvider(requireActivity())[HomeViewModel::class.java]

All you need to do is initialize your viewmodel like this:

import org.koin.android.ext.android.get


val viewModel = get<HomeViewModel>()

In my case, the id 'dagger.hilt.android.plugin' plugin was missing, implemented it and now it is working.

So there are some steps which we have to check

  1. @HiltAndroidApp at the application class, and the application is registered in the Android Manifest

  2. Activity, as well as the Fragment, should be annotated with @AndroidEntryPoint

  3. Check the dependencies and plugins are the right implemented, such as now in my case the current dependencies are:

implementations:

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")

Plugin: id 'dagger.hilt.android.plugin'

classpath: classpath("com.google.dagger:hilt-android-gradle-plugin:$hiltVersion")