Функция не может вернуть элемент с блоком Coroutine - PullRequest
1 голос
/ 09 мая 2020

Я получаю запись из базы данных Room, используя Coroutines, потому что она должна выполняться в фоновом потоке. Я хочу вернуть результат через функцию.

class LessonRepository(val app: Application) {

    private val courseDao = MyDatabase.getDatabase(app).courseDao()
}

    fun getCourseData(): Course {

        var course: Course

        CoroutineScope(Dispatchers.IO).launch {
            course = courseDao.getCourse(globalSelectedCourse)
        }
        return course
    }

ViewModel

class LessonViewModel(app: Application): AndroidViewModel(app) {

    private val lessonDataRepository = LessonRepository(app)
    val lessonData = lessonDataRepository.lessonData
    val selectedLesson = MutableLiveData<Lesson>()

    fun getCourseData() : Course {
        return lessonDataRepository.getCourseData()
    }
}

Я хочу использовать возвращаемое значение в моем фрагменте:

class DetailFragment : Fragment(), LessonRecyclerAdapter.LessonItemListener {
.
.
.
        viewModel = ViewModelProvider(this).get(LessonViewModel::class.java)

        val course = viewModel.getCourseData()
.
.
.
    }

Однако , Android Studio выдает мне индикатор ошибки в операторе возврата return course, который должен быть инициализирован course. Как я могу успешно вернуть значение course?

- ОБНОВЛЕНИЕ: -

Я пытаюсь получить значение этой записи и использовать его в мой фрагмент выглядит следующим образом:

val course = viewModel.viewModelScope.launch { viewModel.getCourseData() }

textViewName.text = course.Name
textViewInstructor.text = course.instructor

Ответы [ 2 ]

2 голосов
/ 10 мая 2020

Вы должны воспользоваться поддержкой сопрограмм, предоставляемой Room начиная с версии 2.1.0. В официальной документации указано:

Вы можете добавить ключевое слово suspend Kotlin к своим методам DAO, чтобы сделать их асинхронными, используя функциональность Kotlin сопрограмм. Это гарантирует, что они не могут быть выполнены в основном потоке.

Вы можете проверить, что здесь .

Поэтому вам следует добавить ключевое слово suspend в свой getCourse Метод DAO:

Интерфейс DAO

@Query("MY SQL QUERY")
suspend fun getCourse(selectedCourse: SelectedCourseType): Course

Если вы это сделаете, вы можете просто вернуть результат независимо от того, делаете ли вы запрос в основном потоке:

Репозиторий

suspend fun getCourseData() = courseDao.getCourse(globalSelectedCourse)

ViewModel

suspend fun getCourseData() = lessonDataRepository.getCourseData()

Вы также должны использовать lifecycleScope, который доступен во фрагментах как здесь вы можете увидеть :

Фрагмент

fun doSomething() {
    ...    
    viewLifecycleOwner.lifecycleScope.launch {
        val course = viewModel.getCourseData()
        textViewName.text = course.Name
        textViewInstructor.text = course.instructor
    }
    ...    
}

PS: Рассмотрите возможность передачи выбранного курса в качестве аргумента через целая цепочка вызовов вместо использования глобальной переменной в вашем репо.

1 голос
/ 09 мая 2020

Вы делаете это неправильно. Возможно, у вас есть неправильные представления о параллелизме или задачах, которые выполняются одновременно.

Позвольте мне развеять ваши сомнения.

  1. запуск не блокируется, он запускается и забывается. Никогда не знаешь, когда установлено значение. Единственное, что вы можете сделать, - это вызвать join(), чтобы убедиться, что это выполнено.
  2. Но все же использование блока запуска не очень хорошо оптимизировано для задач, где вы хотите получить результат от сопрограммы. Здесь мы используем async / withContext.
  3. async вызывается в CoroutineScope, а withContext - это функция верхнего уровня, которая ожидает CoroutineContext в качестве своего параметра.
  4. withContext приостанавливает выполнение вызывающая сопрограмма, пока она не будет завершена. В то время как async этого не делает, возвращаемое значение async - Deferred<T>, на котором, когда вы вызываете .await(), вызывающая сопрограмма приостанавливается до тех пор, пока не будет завершена задача, аналогичная withContext.

Таким образом, вы можете выполнить свою задачу следующим образом.

Вариант 1: наиболее оптимизированная версия

Сделайте вашу функцию приостановленной и используйте withContext. Он приостановит вызывающую сопрограмму до тех пор, пока курс не будет выбран.

suspend fun getCourseData(): Course {
    return withContext(Dispatchers.IO) {
        courseDao.getCourse(globalSelectedCourse)
    }
}

// or simpler
suspend fun getCourseData(): Course =
    withContext(Dispatchers.IO) {
        courseDao.getCourse(globalSelectedCourse)
    }

Вариант 2: используйте asyn c и верните Deferred.

// declare scope elsewhere. It is not intended to create scope everytime you want to launch a task
val scope = CoroutineScope(Dispatchers.IO)

// using async at the end of function is a naming scheme by Kotlin recommendation.
fun getCourseDataAsync(): Deferred<Course> =
    scope.async {
        courseDao.getCourse(globalSelectedCourse)
    }

//Now when you call the function, call await(), it is suspending, it will suspend the calling coroutine till the course is fetched.
val course: Course = getCourseDataAsync().await()

Обновление в обновлении OP

Как я также предлагал в комментариях, вы не можете брать блокировку кода за пределами блока сопрограммы. Потому что вы не можете приостановить функцию без приостановки.

Сделайте следующее:

// in fragment
suspend fun getCourseData() : Course {
    return lessonDataRepository.getCourseData()
}

viewModel.viewModelScope.launch {
    val course = viewModel.getCourseData()

    textViewName.text = course.Name
    textViewInstructor.text = course.instructor
}
...