如何将暂停函数的 return 值存储到变量中?
How to store the return value of suspended function to a variable?
我正在尝试了解 Kotlin 协程。所以这是我的代码(基于 this tutorial)。为了保持代码相对简单,我特意避免了 MVVM、LiveData 等。只是 Kotlin couroutine 和 Retrofit。
考虑这个登录过程。
ApiInterface.kt
interface ApiInterface {
// Login
@POST("/user/validate")
suspend fun login(@Body requestBody: RequestBody): Response<ResponseBody>
}
ApiUtil.kt
class ApiUtil {
companion object {
var API_BASE_URL = "https://localhost:8100/testApi"
fun getInterceptor() : OkHttpClient {
val logging = HttpLoggingInterceptor()
logging.level = HttpLoggingInterceptor.Level.BODY
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(logging)
.build()
return okHttpClient
}
fun createService() : ApiInterface {
val retrofit = Retrofit.Builder()
.client(getInterceptor())
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(OJIRE_BASE_URL)
.build()
return retrofit.create(ApiInterface::class.java)
}
}
fun login(userParam: UserParam): String {
val gson = Gson()
val json = gson.toJson(userParam)
var resp = ""
val requestBody = json.toString().toRequestBody("application/json".toMediaTypeOrNull())
CoroutineScope(Dispatchers.IO).launch {
val response = createService().login(requestBody)
withContext(Dispatchers.Main){
if (response.isSuccessful){
val gson = GsonBuilder().setPrettyPrinting().create()
val prettyJson = gson.toJson(
JsonParser.parseString(
response.body()
?.string()
)
)
resp = prettyJson
Log.d("Pretty Printed JSON :", prettyJson)
}
else {
Log.e("RETROFIT_ERROR", response.code().toString())
}
}
}
return resp
}
}
LoginActivity.kt
class LoginActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
edtUsername = findViewById(R.id.edtUsername)
edtPassword = findViewById(R.id.edtPassword)
btnLogin = findViewById(R.id.btnLogin)
btnLogin.setOnClickListener {
val api = ApiUtil()
val userParam = UserParam(edtMobileNo.text.toString(), edtPassword.text.toString())
val response = JSONObject(api.login(userParam))
var msg = ""
if (response.getString("message").equals("OK")){
msg = "Login OK"
}
else {
msg = "Login failed"
}
Toast.makeText(applicationContext, msg, Toast.LENGTH_SHORT).show()
}
}
}
调试登录 activity 时,API 响应在 prettyJson
上被正确捕获
问题是 resp
仍然是空的。猜猜这就是异步进程的工作方式。我想要的是等到 API 调用完成,然后结果可以很好地传递给 resp
作为 login()
的 return 值。怎么做?
好吧,你在这里有几处错误。我们会尽力解决所有问题。
首先,你描述的主要问题是需要在login()
中同步获取resp
。你遇到这个问题只是因为你首先在那里启动了一个异步操作。解决方案?不要那样做,通过删除 launch()
同步获取响应。我想 withContext()
也不是必需的,因为我们不做任何需要主线程的事情。删除它们后,代码变得更加简单且完全同步。
我们需要对 login()
做的最后一件事是让它可以暂停。它需要等待请求完成,所以它是一个挂起函数。结果 login()
应该类似于:
suspend fun login(userParam: UserParam): String {
val gson = Gson()
val json = gson.toJson(userParam)
val requestBody = json.toString().toRequestBody("application/json".toMediaTypeOrNull())
val response = createService().login(requestBody)
return if (response.isSuccessful){
val gson = GsonBuilder().setPrettyPrinting().create()
gson.toJson(
JsonParser.parseString(
response.body()
?.string()
)
)
}
else {
Log.e("RETROFIT_ERROR", response.code().toString())
// We need to do something here
}
}
现在,当我们将 login()
转换为可暂停时,我们无法直接从侦听器调用它。这里我们确实需要启动异步操作,但我们不会像您在示例中那样使用 CoroutineScope()
,因为它会泄漏后台任务和内存。我们将像这样使用 lifecycleScope
:
btnLogin.setOnClickListener {
val api = ApiUtil()
val userParam = UserParam(edtMobileNo.text.toString(), edtPassword.text.toString())
lifecycleScope.launch {
val response = JSONObject(api.login(userParam))
var msg = ""
if (response.getString("message").equals("OK")){
msg = "Login OK"
}
else {
msg = "Login failed"
}
withContext(Dispatchers.Main) {
Toast.makeText(applicationContext, msg, Toast.LENGTH_SHORT).show()
}
}
}
以上代码可能无法完全发挥作用。如果没有所有必需的数据结构等,很难提供工作示例。但我希望你明白这一点。
此外,您的代码中还有其他一些可以改进的地方,但我没有触及它们以免让您感到困惑。
我正在尝试了解 Kotlin 协程。所以这是我的代码(基于 this tutorial)。为了保持代码相对简单,我特意避免了 MVVM、LiveData 等。只是 Kotlin couroutine 和 Retrofit。
考虑这个登录过程。
ApiInterface.kt
interface ApiInterface {
// Login
@POST("/user/validate")
suspend fun login(@Body requestBody: RequestBody): Response<ResponseBody>
}
ApiUtil.kt
class ApiUtil {
companion object {
var API_BASE_URL = "https://localhost:8100/testApi"
fun getInterceptor() : OkHttpClient {
val logging = HttpLoggingInterceptor()
logging.level = HttpLoggingInterceptor.Level.BODY
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(logging)
.build()
return okHttpClient
}
fun createService() : ApiInterface {
val retrofit = Retrofit.Builder()
.client(getInterceptor())
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(OJIRE_BASE_URL)
.build()
return retrofit.create(ApiInterface::class.java)
}
}
fun login(userParam: UserParam): String {
val gson = Gson()
val json = gson.toJson(userParam)
var resp = ""
val requestBody = json.toString().toRequestBody("application/json".toMediaTypeOrNull())
CoroutineScope(Dispatchers.IO).launch {
val response = createService().login(requestBody)
withContext(Dispatchers.Main){
if (response.isSuccessful){
val gson = GsonBuilder().setPrettyPrinting().create()
val prettyJson = gson.toJson(
JsonParser.parseString(
response.body()
?.string()
)
)
resp = prettyJson
Log.d("Pretty Printed JSON :", prettyJson)
}
else {
Log.e("RETROFIT_ERROR", response.code().toString())
}
}
}
return resp
}
}
LoginActivity.kt
class LoginActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
edtUsername = findViewById(R.id.edtUsername)
edtPassword = findViewById(R.id.edtPassword)
btnLogin = findViewById(R.id.btnLogin)
btnLogin.setOnClickListener {
val api = ApiUtil()
val userParam = UserParam(edtMobileNo.text.toString(), edtPassword.text.toString())
val response = JSONObject(api.login(userParam))
var msg = ""
if (response.getString("message").equals("OK")){
msg = "Login OK"
}
else {
msg = "Login failed"
}
Toast.makeText(applicationContext, msg, Toast.LENGTH_SHORT).show()
}
}
}
调试登录 activity 时,API 响应在 prettyJson
上被正确捕获
问题是 resp
仍然是空的。猜猜这就是异步进程的工作方式。我想要的是等到 API 调用完成,然后结果可以很好地传递给 resp
作为 login()
的 return 值。怎么做?
好吧,你在这里有几处错误。我们会尽力解决所有问题。
首先,你描述的主要问题是需要在login()
中同步获取resp
。你遇到这个问题只是因为你首先在那里启动了一个异步操作。解决方案?不要那样做,通过删除 launch()
同步获取响应。我想 withContext()
也不是必需的,因为我们不做任何需要主线程的事情。删除它们后,代码变得更加简单且完全同步。
我们需要对 login()
做的最后一件事是让它可以暂停。它需要等待请求完成,所以它是一个挂起函数。结果 login()
应该类似于:
suspend fun login(userParam: UserParam): String {
val gson = Gson()
val json = gson.toJson(userParam)
val requestBody = json.toString().toRequestBody("application/json".toMediaTypeOrNull())
val response = createService().login(requestBody)
return if (response.isSuccessful){
val gson = GsonBuilder().setPrettyPrinting().create()
gson.toJson(
JsonParser.parseString(
response.body()
?.string()
)
)
}
else {
Log.e("RETROFIT_ERROR", response.code().toString())
// We need to do something here
}
}
现在,当我们将 login()
转换为可暂停时,我们无法直接从侦听器调用它。这里我们确实需要启动异步操作,但我们不会像您在示例中那样使用 CoroutineScope()
,因为它会泄漏后台任务和内存。我们将像这样使用 lifecycleScope
:
btnLogin.setOnClickListener {
val api = ApiUtil()
val userParam = UserParam(edtMobileNo.text.toString(), edtPassword.text.toString())
lifecycleScope.launch {
val response = JSONObject(api.login(userParam))
var msg = ""
if (response.getString("message").equals("OK")){
msg = "Login OK"
}
else {
msg = "Login failed"
}
withContext(Dispatchers.Main) {
Toast.makeText(applicationContext, msg, Toast.LENGTH_SHORT).show()
}
}
}
以上代码可能无法完全发挥作用。如果没有所有必需的数据结构等,很难提供工作示例。但我希望你明白这一点。
此外,您的代码中还有其他一些可以改进的地方,但我没有触及它们以免让您感到困惑。