Home > other >  "No virtual method ~~" error occurs if the suspend modifier is removed
"No virtual method ~~" error occurs if the suspend modifier is removed

Time:12-16

I am writing code to fetch data from Room.

as shown in the image below, the IDE indicates that suspend is a useless modifier. enter image description here

So, if I remove suspend, the following error occurs.

E/AndroidRuntime: FATAL EXCEPTION: DefaultDispatcher-worker-1
    Process: com.example.lightweight, PID: 6483
    java.lang.NoSuchMethodError: No virtual method getWorkoutList(Lkotlin/coroutines/Continuation;)Ljava/lang/Object; in class Lcom/example/lightweight/data/repository/WorkoutListRepository; or its super classes (declaration of 'com.example.lightweight.data.repository.WorkoutListRepository' appears in /data/data/com.example.lightweight/code_cache/.overlay/base.apk/classes13.dex)
        at com.example.lightweight.presentation.viewmodel.WorkoutListViewModel$getList$1.invokeSuspend(WorkoutListViewModel.kt:23)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
        at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
        at kotlinx.coroutines.internal.LimitedDispatcher.run(LimitedDispatcher.kt:42)
        at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:95)
        at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:570)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:749)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:677)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:664)
        Suppressed: kotlinx.coroutines.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@6975010, Dispatchers.IO]
I/Process: Sending signal. PID: 6483 SIG: 9

However, if I leave suspend as is, it runs normally. For what reason?


DAO

@Dao
interface WorkoutDao {
    @Query("SELECT * From WorkoutListTest")
    fun getWorkoutList() : List<WorkoutListTest>

}

ViewModel

class WorkoutListViewModel(
    private val repository: WorkoutListRepository
) : ViewModel() {

    private var _list = MutableLiveData<List<String>>()
    val list: LiveData<List<String>>
        get() = _list


    fun getList() {
        viewModelScope.launch(Dispatchers.IO) {
            repository.getWorkoutList()
        }
    }
}

Repository

class WorkoutListRepository(private val dao: WorkoutDao) {
    fun getWorkoutList() : List<WorkoutListTest> {
        val list: List<WorkoutListTest> = dao.getWorkoutList()
        return list
    }
}

The part that uses ViewModel in Fragment.

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    vm.getList()
    vm.list.observe(viewLifecycleOwner) { list ->

    }
}

CodePudding user response:

It's because your DAO function is not marked suspend. Since the DAO function is a regular synchronous, blocking function, calling through to it in your suspend function doesn't take advantage of the suspend modifier. Incidentally, this would also mean your suspend function is blocking, which breaks convention and would cause your coroutines to block threads incorrectly. But the Kotlin compiler cannot detect that the DAO function is blocking, so the warning message doesn't mention that.

You need to mark your DAO function suspend:

@Dao
interface WorkoutDao {
    @Query("SELECT * From WorkoutListTest")
    suspend fun getWorkoutList() : List<WorkoutListTest>
}

You said that leaving suspend, it ran normally. This is because you launched your coroutine using Dispatchers.IO. But you shouldn't have to use Dispatchers.IO unless you're calling a blocking function. By convention, suspend functions never block. So if you were following conventions, you would not use Dispatchers.IO to call a suspend function, but then your incorrectly blocking suspend function would hang the main thread.


Side note, there's a much easier way to fetch an item from a suspend function one time to fill in a LiveData. Your ViewModel class could be changed to the following and it would have the exact same behavior:

class WorkoutListViewModel(
    private val repository: WorkoutListRepository
) : ViewModel() {

    val list: LiveData<List<String>> = liveData {
        emit(repository.getWorkoutList())
    }
}
  • Related