简体   繁体   中英

How to return value from coroutine scope

Is there a way to return value from a coroutine 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. 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:

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. However, in your case you don't have more than a request and so it could be sequantial . As mentioned in previous answer you can use withContext . Here is the small explaination from 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). 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 ).

According to Android's tutorial: Kotlin coroutines on Android

Assume that the code snippet of the HTTP request as below

  1. Remember to add the 'suspend' for the function
  2. Add @withContext to return the Result 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.

Outline:

  • You make a ViewModel with LiveData (internal) and MutableLiveData (external)
  • 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. 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 . 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
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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