简体   繁体   English

如何从协程 scope 返回值

[英]How to return value from coroutine scope

Is there a way to return value from a coroutine scope?有没有办法从协程 scope 返回值? for example like this:例如像这样:

suspend fun signUpUser(signUpData : SignUpUserRequest) : User {

CoroutineScope(Dispatchers.IO).launch() {

        val response = retrofitInstance.socialSignUP(signUpData)
        if (response.success) {
            obj = response.data
        } else {
                obj = response.message
        }
    }.invokeOnCompletion {
        obj = response.message
    }

    return obj
}

the problem is as soon as signupUser function called, return obj suspend statement run right away, not with response value.. its not running sequentially, what I expect is first response come and then function return obj but its not happening.问题是一旦调用 signupUser function,返回 obj 挂起语句立即运行,而不是响应值。它没有按顺序运行,我期望的是第一个响应来,然后 function 返回 obj 但它没有发生。 why?为什么?

I tried run blocking, it served the purpose but is there better approach to do same task without run blocking?我尝试了运行阻塞,它达到了目的,但是有没有更好的方法可以在不阻塞运行的情况下完成相同的任务? or is it alright?或者没关系?

Thanks in advance!提前致谢!

You can use withContext function to run a code in background thread and return some result:您可以使用withContext function 在后台线程中运行代码并返回一些结果:

suspend fun signUpUser(signUpData : SignUpUserRequest): User = withContext(Dispatchers.IO) {    
    val response = retrofitInstance.socialSignUP(signUpData)
    if (response.success) response.data else response.message
}

If you need to run works in parallel then you can use coroutineScope or async coroutine builder functions.如果您需要并行运行工作,那么您可以使用coroutineScopeasync协程构建器函数。 However, in your case you don't have more than a request and so it could be sequantial .但是,在您的情况下,您只有一个请求,因此它可能是sequantial As mentioned in previous answer you can use withContext .如上一个答案中所述,您可以使用withContext Here is the small explaination from docs :这是来自docs的小解释:

Calls the specified suspending block with a given coroutine context, suspends until it completes, and returns the result.使用给定的协程上下文调用指定的挂起块,挂起直到完成,并返回结果。

suspend fun getUser(signUpData: SignUpUserRequest): User = withContext(Dispatchers.IO) {
    // do your network request logic here and return the result
}

Please note that, when you are working with expression body with functions, always try to annotate return type explicitly.请注意,当您使用带有函数的表达式主体时,请始终尝试显式注释返回类型。

EDIT编辑

Coroutines use the regular Kotlin syntax for handling exceptions: try/catch or built-in helper functions like runCatching (which uses try/catch internally).协程使用常规的 Kotlin 语法来处理异常: try/catchrunCatching等内置辅助函数(在内部使用try/catch )。 Beware of that uncaught exceptions will always be thrown .请注意,总会抛出未捕获的异常 However, different coroutines builders treat exceptions in different ways.但是,不同的协程构建器以不同的方式处理异常。 It's always good to read official documentation or other sources (like this ).阅读官方文档或其他来源(如this )总是好的。

According to Android's tutorial: Kotlin coroutines on Android根据Android的教程: Kotlin coroutines on Android

Assume that the code snippet of the HTTP request as below假设 HTTP 请求的代码片段如下

  1. Remember to add the 'suspend' for the function请记住为 function 添加“暂停”
  2. Add @withContext to return the Result object添加@withContext 以返回结果 object
// Function that makes the network request, blocking the current thread
suspend fun makeLoginRequest(
    jsonBody: String
): Result<LoginResponse> {

    // Move the execution of the coroutine to the I/O dispatcher
    return withContext(Dispatchers.IO) {
        // Blocking network request code
        val url = URL(loginUrl)
        (url.openConnection() as? HttpURLConnection)?.run {
            requestMethod = "POST"
            setRequestProperty("Content-Type", "application/json; utf-8")
            setRequestProperty("Accept", "application/json")
            doOutput = true
            outputStream.write(jsonBody.toByteArray())
            return@withContext Result.Success(responseParser.parse(inputStream))
        }
        return@withContext Result.Error(Exception("Cannot open HttpURLConnection"))
    }

}

In the Google codelab "Load and display images from the Internet" , there's a nice and very elegant example with MutableLiveData.在 Google 代码实验室“从 Internet 加载和显示图像”中,有一个使用 MutableLiveData 的漂亮且非常优雅的示例。

Outline:大纲:

  • You make a ViewModel with LiveData (internal) and MutableLiveData (external)您使用 LiveData(内部)和 MutableLiveData(外部)制作 ViewModel
  • Now you can use the data directly in views, fragments or activities现在您可以直接在视图、片段或活动中使用数据

The benefit being that this data is lifecycle-aware, and you can use the returned values with observers.好处是这些数据是生命周期感知的,并且您可以将返回的值用于观察者。

class OverviewViewModel : ViewModel() {

    enum class MarsApiStatus { LOADING, ERROR, DONE }

    private val _status = MutableLiveData<MarsApiStatus>()
    val status: LiveData<MarsApiStatus> = _status

    private val _photos = MutableLiveData<List<MarsPhoto>>()
    val photos: LiveData<List<MarsPhoto>> = _photos

    /**
     * Call getMarsPhotos() on init so we can display status immediately.
     */
    init {
        getMarsPhotos()
    }

    /**
     * Gets Mars photos information from the Mars API Retrofit service and updates the
     * [MarsPhoto] [List] [LiveData].
     */
    private fun getMarsPhotos() {
        viewModelScope.launch {
            _status.value = MarsApiStatus.LOADING
            Log.d(TAG, "loading")
            try {
                _photos.value = MarsApi.retrofitService.getPhotos()
                _status.value = MarsApiStatus.DONE
                Log.d(TAG, "done")
            } catch (e: Exception) {
                _status.value = MarsApiStatus.ERROR
                _photos.value = listOf()
                Log.d(TAG, "error")
            }
        }
    }
}

You can use the status or photos values by observing them or directly using them in a view like this:您可以通过观察它们或直接在如下视图中使用它们来使用状态或照片值:

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/photos_grid"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:listData="@{viewModel.photos}"
        app:spanCount="2"
        tools:itemCount="16"
        tools:listitem="@layout/grid_view_item" />

...after you've "registered" and wired everything in the f.ex. ...在您“注册”并连接 f.ex 中的所有内容之后。 regarding fragment, like this:关于片段,像这样:

override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {

    try {
        val binding = FragmentOverviewBinding.inflate(inflater)
        //val binding = GridViewItemBinding.inflate(inflater)

        // Allows Data Binding to Observe LiveData with the lifecycle of this Fragment
        binding.lifecycleOwner = this

        // Giving the binding access to the OverviewViewModel
        binding.viewModel = viewModel

        binding.photosGrid.adapter = PhotoGridAdapter()

        return binding.root
    } catch (e: Exception) {
        Log.e("OverviewFragment", "${e.localizedMessage}")
    }
    return null
}

Return value using return@coroutineScope .使用return@coroutineScope返回值。 It will work.它会起作用的。

suspend fun signUpUser(signUpData : SignUpUserRequest) : User = coroutineScope {

launch() {

    val response = retrofitInstance.socialSignUP(signUpData)
    if (response.success) {
        obj = response.data
    } else {
            obj = response.message
    }
}.invokeOnCompletion {
    obj = response.message
}

return@coroutineScope obj
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM