66

I am following this documentation to learn about LiveData and ViewModel. In the doc, the ViewModel class has constructor as such,

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

However, when I run the code, I get exception:

final UserViewModelviewModel = ViewModelProviders.of(this).get(UserViewModel.class);

Caused by: java.lang.RuntimeException: Cannot create an instance of class UserViewModel Caused by: java.lang.InstantiationException: java.lang.Class has no zero argument constructor

PatrickNLT
  • 3,276
  • 1
  • 24
  • 31
Prabin Timsina
  • 1,686
  • 2
  • 14
  • 24

17 Answers17

54

While initializing subclasses of ViewModel using ViewModelProviders, by default it expects your UserModel class to have zero argument constructor. In your case your constructor has 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 will implement ViewModelProvider.Factory interface.

I have not tried this yet, but here is the link to excellent sample from google for the same: github.com/googlesamples/android-architecture-components. Specifically, checkout this class GithubViewModelFactory.java for Java code and this class GithubViewModelFactory.kt for corresponding Kotlin code

Shahbaz Ahmed
  • 1,032
  • 9
  • 11
  • 1
    Discussion around this: https://www.reddit.com/r/androiddev/comments/6bw1jj/architecture_components_introduction_google_io_17/ – Upvote May 27 '17 at 08:36
  • @LostinOWL I am getting the same error, I have checked DaggerAppComponent class and it's generating all the dependency graph properly. – Shashank Srivastava Apr 02 '18 at 22:18
  • Here is the Google sample before they changed it to kotlin: https://github.com/googlesamples/android-architecture-components/blob/b1a194c1ae267258cd002e2e1c102df7180be473/GithubBrowserSample/app/src/main/java/com/android/example/github/viewmodel/GithubViewModelFactory.java – hexicle Apr 27 '18 at 21:40
  • @ShashankSrivastava Please help: https://stackoverflow.com/questions/66396049/how-can-i-tell-dagger-hilt-that-use-my-own-viewmodelfactory-instead-of-default – Sychi Singh Feb 27 '21 at 06:01
33

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 {
    //
}
yoAlex5
  • 13,571
  • 5
  • 105
  • 98
  • I am getting an error: `[dagger.android.AndroidInjector.inject(T)] java.util.Map,javax.inject.Provider> cannot be provided without an @Provides-annotated method.` – Levon Petrosyan Jul 06 '18 at 13:22
  • It is hard to say what the problem is without seeing the whole project, I can assume that 1. child of ViewModel was not declared in `ViewModelModule`. 2. `ViewModelModule ` was not added to `Component` – yoAlex5 Jul 06 '18 at 14:25
  • @LevonPetrosyan i got same issues, create constructor with no argument and @ Inject annotation – silentsudo Jul 17 '18 at 13:28
  • @silentsudo my issue was that I was Inject -ing viewModel in Fragment,but thx anyway :)) – Levon Petrosyan Jul 17 '18 at 13:30
  • 1
    I think this is the best explanation of the above implementation: https://medium.com/@marco_cattaneo/android-viewmodel-and-factoryprovider-good-way-to-manage-it-with-dagger-2-d9e20a07084c – Maciej Beimcik Sep 11 '18 at 19:57
  • @yoAlex5 Thanks, bud! Saved my day. – code Apr 13 '19 at 17:31
  • can you provide the same for Kotlin – Venkatesh Kashyap Dec 11 '19 at 07:59
  • In newer android versions this is deprecated: `((App) getApplication()).getAppComponent().inject(this);` `userViewModel = ViewModelProviders.of(this, viewModelFactory).get(UserViewModel.class);` This worked for me `userViewModel = new ViewModelProvider(this, viewModelFactory).get(UserViewModel.class);` – Johana Lopez 1327 Jun 19 '20 at 10:10
26

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() {
....
}
Dimitri de Jesus
  • 775
  • 7
  • 12
5

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.

To avoid using deprecated libraries, 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.

kierans
  • 1,533
  • 1
  • 11
  • 32
kgandroid
  • 5,219
  • 5
  • 34
  • 65
  • I am getting a compiler error while creating an instance of viewmodel at this line val viewModel: CalculatorViewModel by viewModels() I want the CalculatorViewModel to be AndroidViewModel because i want context – yeshu Apr 26 '20 at 13:38
  • Even if you extend AndroidViewModel, this will work.What is the exact error are you getting? – kgandroid Apr 26 '20 at 15:56
  • Classifier 'CalculatorViewModel' doesn't have companion object and, this must be initialized here. – yeshu May 02 '20 at 14:50
  • I haven't encountered any such problem. Post a question and give the link here. I will have a look at that. – kgandroid May 03 '20 at 08:28
4

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


javakam
  • 61
  • 4
4

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)

nir
  • 272
  • 1
  • 11
4

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

Sirisha
  • 325
  • 2
  • 10
4

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

  • 1
    After an hour of trying different alternatives, only this worked fine for me. Thank you very much. – Ivette Valdez May 11 '21 at 11:30
  • This worked for me, But Also I would like to mention maybe this could be in new version of HILT library i.e "hilt_version = '2.35'" In the earlier version i.e "hilt_version = '2.28.3-alpha'" I guess this would not be necessary – Snehal Gongle May 28 '21 at 05:35
2

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
NOUAILI
  • 21
  • 1
1

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
        })
    }
}
Radu Topor
  • 1,364
  • 1
  • 8
  • 12
0

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.

m02ph3u5
  • 2,735
  • 6
  • 34
  • 45
0

You can pass data in viewmodel through viewmodel factory. You can also check out this example for reference.

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

You can call viewmodel in activity as below.

val userViewModel = ViewModelProviders.of(this,UserViewModelFactory(this)).get(UserViewModel::class.java)
Dhrumil Shah
  • 359
  • 2
  • 8
0

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

implementation 'androidx.navigation:navigation-ui-ktx:2.2.2'
Usama Saeed US
  • 709
  • 9
  • 12
0

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

PNDA
  • 776
  • 14
  • 39
0

For Koin:

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

Merthan Erdem
  • 1,244
  • 1
  • 8
  • 18
-2

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.

rushi
  • 132
  • 1
  • 12
  • 2
    The AndroidViewModel accepts only 1 argument, which is Application. The only way around either versions of ViewModel is to create a Factory. – zuko Jul 24 '18 at 23:11
-3

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"

  • Why do people write comments just to comment? He has @Inject, but his problem is already noted above, and it is not what you wrote about. – Slobodan Antonijević Feb 26 '19 at 11:55
  • It'll be good to write with an example, above answers were not clear much to me, so i wrote one that i could understands easily and other persons as well. – Shahid Ahmad Feb 27 '19 at 10:14