You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在Retrofit与Kotlin中从API调用函数返回响应体并在Fragment中使用

如何在Retrofit2(Kotlin)中封装API调用并返回响应体到Fragment

我明白你现在的困扰——Retrofit的enqueue是异步执行的,你没法直接在回调里return数据,因为函数在回调触发前就已经执行完毕了。下面给你两种常用的解决方案,都是Kotlin开发里很实用的方式:

方案一:使用高阶函数(回调模式)

这是最直接的适配方案,我们给API函数添加成功/失败的回调参数,让Fragment在调用时能拿到返回的数据:

修改后的API封装函数

fun getRandomComputerDetails(
    context: Context,
    onSuccess: (RandomComputerApiResponse) -> Unit,
    onError: (String) -> Unit
) {
    if (isNetworkAvailable(context)) {
        val retrofit: Retrofit = Retrofit.Builder()
            .baseUrl(BuildConfig.API_BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
        val service: RandomComputerApiService = retrofit.create(RandomComputerApiService::class.java)
        val listCall: Call<RandomComputerApiResponse> = service.getRandomComputer()
        
        listCall.enqueue(object : Callback<RandomComputerApiResponse> {
            override fun onResponse(
                call: Call<RandomComputerApiResponse>,
                response: Response<RandomComputerApiResponse>
            ) {
                if (response.isSuccessful) {
                    val randomComputerList: RandomComputerApiResponse? = response.body()
                    randomComputerList?.let(onSuccess) // 成功时回调传递数据
                } else {
                    val errorMsg = when (response.code()) {
                        400 -> "Bad Connection"
                        404 -> "Not Found"
                        500 -> "Server Error"
                        else -> "Generic Error"
                    }
                    onError(errorMsg)
                    // 保留原有错误弹窗逻辑
                    val errorResId = when (response.code()) {
                        400 -> R.string.dialog_error_400
                        404 -> R.string.dialog_error_404
                        500 -> R.string.dialog_error_500
                        else -> R.string.dialog_error_generic_error
                    }
                    errorDialogHandler(context, errorResId)
                }
            }

            override fun onFailure(call: Call<RandomComputerApiResponse>, t: Throwable) {
                val errorMsg = t.message ?: "Unknown Error"
                onError(errorMsg)
                Log.e("Response error", errorMsg)
            }
        })
    } else {
        val errorMsg = context.getString(R.string.dialog_error_no_internet_connection)
        onError(errorMsg)
        errorDialogHandler(context, R.string.dialog_error_no_internet_connection)
    }
}

Fragment中的调用示例

// 在Fragment的onViewCreated等生命周期方法中调用
getRandomComputerDetails(requireContext(),
    onSuccess = { computerList ->
        // 这里拿到数据后即可更新UI,比如给RecyclerView设置数据源
        Log.i("Data received", "$computerList")
        // updateUI(computerList)
    },
    onError = { errorMsg ->
        // 可添加额外错误处理逻辑,弹窗已经在封装函数中触发
        Log.e("API Error", errorMsg)
    }
)

方案二:使用Kotlin协程(挂起函数,更简洁)

Kotlin协程可以让异步代码看起来像同步代码,是现代Kotlin开发的首选方式。我们需要把API函数改成挂起函数,用await()(或直接返回响应体)替代enqueue

准备工作

确保你的项目依赖了Retrofit的协程支持(较新版本的Retrofit已内置),同时将API接口方法改为挂起函数:

interface RandomComputerApiService {
    @GET("your-api-endpoint")
    suspend fun getRandomComputer(): Response<RandomComputerApiResponse>
    // 也可以直接返回数据类,此时请求失败会自动抛出异常,需自行捕获
    // suspend fun getRandomComputer(): RandomComputerApiResponse
}

修改后的API封装函数(挂起函数)

suspend fun getRandomComputerDetails(context: Context): Result<RandomComputerApiResponse> {
    return try {
        if (!isNetworkAvailable(context)) {
            errorDialogHandler(context, R.string.dialog_error_no_internet_connection)
            return Result.failure(Exception(context.getString(R.string.dialog_error_no_internet_connection)))
        }

        val retrofit: Retrofit = Retrofit.Builder()
            .baseUrl(BuildConfig.API_BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
        val service: RandomComputerApiService = retrofit.create(RandomComputerApiService::class.java)
        
        // 挂起协程直到请求完成
        val response = service.getRandomComputer()
        
        if (response.isSuccessful) {
            val computerList = response.body()
            computerList?.let { Result.success(it) } 
                ?: Result.failure(Exception("Response body is null"))
        } else {
            val errorMsg = when (response.code()) {
                400 -> "Bad Connection"
                404 -> "Not Found"
                500 -> "Server Error"
                else -> "Generic Error"
            }
            val errorResId = when (response.code()) {
                400 -> R.string.dialog_error_400
                404 -> R.string.dialog_error_404
                500 -> R.string.dialog_error_500
                else -> R.string.dialog_error_generic_error
            }
            errorDialogHandler(context, errorResId)
            Result.failure(Exception(errorMsg))
        }
    } catch (t: Throwable) {
        Log.e("Response error", t.message.toString())
        Result.failure(t)
    }
}

Fragment中的调用示例(协程作用域内)

// 在Fragment的生命周期协程中调用,确保跟随Fragment生命周期
viewLifecycleOwner.lifecycleScope.launch {
    val result = getRandomComputerDetails(requireContext())
    result.onSuccess { computerList ->
        // 拿到数据后更新UI
        Log.i("Data received", "$computerList")
        // updateUI(computerList)
    }.onFailure { error ->
        // 处理错误逻辑
        Log.e("API Error", error.message ?: "Unknown error")
    }
}

方案选择

  • 如果你需要快速适配现有代码,不需要引入协程相关知识,高阶函数方案更适合你;
  • 如果你想写出更简洁、易维护的现代Kotlin代码,协程方案是更好的选择,它能避免回调嵌套的问题。

内容的提问来源于stack exchange,提问作者Melom

火山引擎 最新活动