Почему мое приложение в kotlin вылетает при вызове сопрограммы?

Я пытаюсь вызвать функцию во фрагменте, который находится в моей ViewModel, но она падает каждый раз, когда ее вызывают, и я не знаю, почему. Вот это код:

Вызов сопрограммы:

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

Код функции: suspend fun insertCourse(course: Course) = courseDAO.insertCourse(course)

Если я не использую сопрограмму и просто courseViewModel.repository.insertCourse(getData()) я получаю сообщение об ошибке, в котором говорится, что я должен вызвать эту функцию из другой функции приостановки или сопрограммы.

Это ошибка:

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) 

И моя ViewModel такова:

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

Во фрагменте, где я вызываю сопрограмму, я объявляю ее так: private val courseViewModel: CourseViewModel by activityViewModels()

Давайте посмотрим трассировку стека, пожалуйста, опубликуйте ее.

Shlomi Katriel 20.03.2022 13:28

Может lifecycleScope.launch(Dispatchers.IO)? Объекты инициализированы (не нуль)? Также странно, что у вас есть прямой доступ к repository ViewModel. Может быть, вам стоит сделать это приватным и вызвать insertCourse.

CoolMind 20.03.2022 13:30

Любой вызов базы данных должен выполняться вне основного потока. Попробуйте то, что предложил @CoolMind.

Jake 20.03.2022 13:33

Что ж, журнал сбоев говорит, что вы не создали ViewModel. java.lang.Class<domain.CourseViewModel> has no zero argument constructor. См. stackoverflow.com/questions/44194579/….

CoolMind 20.03.2022 13:36
1
4
51
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Во-первых, вам, вероятно, не нужно загромождать код фрагмента сопрограммой в этой ситуации. Ваша ViewModel и/или репозиторий являются лучшими кандидатами, поскольку они отвечают за так называемую бизнес-логику вашего приложения. Поэтому в идеале измените свою функцию ViewModel на использование viewModelScope.launch(Dispatchers.IO), и тогда ваш код фрагмента может быть просто:

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

Теперь, что касается вашей ViewModel, вы говорите, что объявляете ее как:

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

Однако ViewModelProvider по умолчанию (который используется by activityViewModels() и by viewModels() для создания вашей ViewModel) будет создавать только экземпляр нулевой аргумент ViewModel, что объясняет ошибку, которую вы видите. (Примечание: текущая документация Google о том, как все это работает, просто ужасна; лучше не скажешь.)

Если вы хотите сохранить репозиторий в качестве аргумента, вам нужно создать собственный ViewModelFactory, чтобы ViewModelProvider знал, как его создать. В этом Android Kotlin Fundamentals кодовая лаборатория есть пример того, как его создать.

В качестве альтернативы вы можете использовать внедрение зависимостей с помощью Hilt и Jetpack, описанное в здесь.

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