1

I am trying to call a function in a Fragment that is in my ViewModel but it crashes everytime its called and I don´t know why. Here it´s the code:

Call to a coroutine:

binding.button.setOnClickListener {
            lifecycleScope.launch(Dispatchers.IO){
                courseViewModel.repository.insertCourse(getData())
            }
            Navigation.findNavController(it).popBackStack()
        }

The code of the function: suspend fun insertCourse(course: Course) = courseDAO.insertCourse(course)

If I don´t use the coroutine and just courseViewModel.repository.insertCourse(getData()) i get an error that says I have to call this function from other suspend function or a coroutine.

This is the error:

2022-03-20 13:33:43.893 28396-28437/com.example.cursosmvvm E/AndroidRuntime: FATAL EXCEPTION: DefaultDispatcher-worker-1
    Process: com.example.cursosmvvm, PID: 28396
    java.lang.RuntimeException: Cannot create an instance of class domain.CourseViewModel
        at androidx.lifecycle.ViewModelProvider$NewInstanceFactory.create(ViewModelProvider.kt:188)
        at androidx.lifecycle.ViewModelProvider$AndroidViewModelFactory.create(ViewModelProvider.kt:238)
        at androidx.lifecycle.SavedStateViewModelFactory.create(SavedStateViewModelFactory.java:112)
        at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.kt:169)
        at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.kt:139)
        at androidx.lifecycle.ViewModelLazy.getValue(ViewModelLazy.kt:44)
        at androidx.lifecycle.ViewModelLazy.getValue(ViewModelLazy.kt:31)
        at ui.NewCourseFragment.getCourseViewModel(NewCourseFragment.kt:23)
        at ui.NewCourseFragment.access$getCourseViewModel(NewCourseFragment.kt:20)
        at ui.NewCourseFragment$onViewCreated$1$1.invokeSuspend(NewCourseFragment.kt:58)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
        at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
        at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
     Caused by: java.lang.InstantiationException: java.lang.Class<domain.CourseViewModel> has no zero argument constructor
        at java.lang.Class.newInstance(Native Method)
        at androidx.lifecycle.ViewModelProvider$NewInstanceFactory.create(ViewModelProvider.kt:186)
        at androidx.lifecycle.ViewModelProvider$AndroidViewModelFactory.create(ViewModelProvider.kt:238) 
        at androidx.lifecycle.SavedStateViewModelFactory.create(SavedStateViewModelFactory.java:112) 
        at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.kt:169) 
        at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.kt:139) 
        at androidx.lifecycle.ViewModelLazy.getValue(ViewModelLazy.kt:44) 
        at androidx.lifecycle.ViewModelLazy.getValue(ViewModelLazy.kt:31) 
        at ui.NewCourseFragment.getCourseViewModel(NewCourseFragment.kt:23) 
        at ui.NewCourseFragment.access$getCourseViewModel(NewCourseFragment.kt:20) 
        at ui.NewCourseFragment$onViewCreated$1$1.invokeSuspend(NewCourseFragment.kt:58) 
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) 
        at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106) 
        at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571) 
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750) 
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678) 
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665) 

And my ViewModel is this:

class CourseViewModel(val repository: CourseRepository): ViewModel(){

In the fragment where I call the coroutine I declarate it like this: private val courseViewModel: CourseViewModel by activityViewModels()

Angel
  • 25
  • 4
  • 1
    Let's see the stack trace, please post it – Shlomi Katriel Mar 20 '22 at 12:28
  • Maybe `lifecycleScope.launch(Dispatchers.IO)`? Are objects initialized (not null)? Also it's strange that you have a direct access to `repository` of ViewModel. Maybe you should make it private and call `insertCourse`. – CoolMind Mar 20 '22 at 12:30
  • 1
    Any call to database must be done outside of the main thread. Try what @CoolMind suggested. – Jake Mar 20 '22 at 12:33
  • 1
    Well, crash log says you haven't created the ViewModel. `java.lang.Class has no zero argument constructor`. See https://stackoverflow.com/questions/44194579/android-viewmodel-has-no-zero-argument-constructor. – CoolMind Mar 20 '22 at 12:36

1 Answers1

0

First of all, you probably don't need to clutter up your fragment code with a coroutine in this situation. Your ViewModel and/or Repository are better candidates, as they are responsible for the so-called business logic of your app. So ideally change your ViewModel function to use viewModelScope.launch(Dispatchers.IO) and then your fragment code can simply be:

binding.button.setOnClickListener {
    courseViewModel.insertCourse(getData())
    Navigation.findNavController(it).popBackStack()
}

Now regarding your ViewModel, you say you're declaring it as:

class CourseViewModel(val repository: CourseRepository): ViewModel()

However, the default ViewModelProvider (which is what is used by by activityViewModels() and by viewModels() to create your ViewModel) will only instantiate a zero argument ViewModel, which explains the error you are seeing. (Side note: Google's current documentation of how all of this works is simply terrible; no nicer way to put it.)

If you want to keep the repository as an argument, then you need to create a custom ViewModelFactory so that the ViewModelProvider knows how to instantiate it. This Android Kotlin Fundamentals codelab has an example of how to create one.

As an alternative to that, you can use dependency injection with Hilt and Jetpack, described here.

zen_of_kermit
  • 1,404
  • 2
  • 13
  • 19