Mobile Development 15 min read

Simplifying Android Network Requests with Kotlin Coroutines

By converting Android network calls from callback‑based listeners to Kotlin coroutines, the code becomes sequential, eliminates duplicated UI‑state checks, centralizes loading handling, supports parallel requests with async/await, and integrates with lifecycleScope for automatic cancellation, greatly improving readability and safety.

Ximalaya Technology Team
Ximalaya Technology Team
Ximalaya Technology Team
Simplifying Android Network Requests with Kotlin Coroutines

In the current Android app, network requests are performed using a callback pattern that invokes the main thread after completion. The typical request code includes loading state handling, URL and parameter definition, and a callback that checks canUpdateUi() before updating the UI.

private
fun requestData() {
onPageLoadingCompleted(LoadCompleteType.LOADING)
// 请求前显示loading状态
val
url
= MainUrlConstants.getInstanse().hotCommentUniversalUrl
// 定义请求地址
val
params
= mapOf(
"param1"
to
"hello"
,
"param2"
to
"world"
)
// 定义请求参数
CommonRequestM.getData(url, params, CommentModel::class.java, object : IDataCallBack<CommentModel> {
override fun onSuccess(data: CommentModel?) {
// 成功回调
if
(!canUpdateUi()) {
// 这一步判断页面是否已销毁,如果已销毁就不应该继续执行UI操作
return
}
onPageLoadingCompleted(LoadCompleteType.OK)
// 请求完成,停止loading状态
refreshUI(data)
// ..接下来执行刷新UI操作
}
override fun onError(code: Int, message: String?) {
// 失败回调
if
(!canUpdateUi()) {
// 同成功回调,做同样的页面状态判断
return
}
onPageLoadingCompleted(LoadCompleteType.OK)
// 同成功回调,也做停止loading操作
CustomToast.showFailToast(message)
}
})
}

The duplicated logic—checking canUpdateUi() and stopping the loading indicator—appears in both success and error callbacks, making the code verbose and error‑prone.

By converting the request to a coroutine, the flow becomes sequential, eliminating the need for repeated UI‑state checks and allowing a single place to stop the loading UI.

private
fun requestData() {
onPageLoadingCompleted(LoadCompleteType.LOADING)
mMainScope.launch {
// 创建一个协程
val
url
= MainUrlConstants.getInstanse().hotCommentUniversalUrl
// 定义请求地址
val
params
= mapOf(
"param1"
to
"hello"
,
"param2"
to
"world"
)
// 定义请求参数
val
reqResult
= CoroutineRequest.getData(url, params, CommentModel::class.java)
// 使用挂起函数切到IO线程执行
onPageLoadingCompleted(LoadCompleteType.OK)
// 挂起函数返回结果后不需要判断canUpdateUi,取消loading操作只要写一次
if
(reqResult.isSuccess()) {
// 成功回调
refreshUI(reqResult.model)
}
else
{
// 失败回调
CustomToast.showFailToast(reqResult.msg)
}
}
}

Key advantages of the coroutine version:

The loading UI is stopped only once, after the suspend function returns.

The canUpdateUi() check is unnecessary because the coroutine is cancelled automatically when the Activity/Fragment is destroyed.

The code reads like a straightforward sequential algorithm, improving readability.

When only the result model is needed, the code can be further simplified:

mMainScope.launch {
val url =
MainUrlConstants
.getInstanse().hotCommentUniversalUrl
val params = mapOf(
"param1"
to
"hello"
,
"param2"
to
"world"
)
val resultModel =
CoroutineRequest
.getData(url, params,
CommentModel
::
class
.java).model
refreshUI(resultModel)
}

For scenarios requiring multiple concurrent requests, coroutines provide async and await to run requests in parallel and combine their results once all have completed.

private
fun requestMainData() {
mLoadingView.visibility = View.VISIBLE
mMainScope.launch {
val
preVoteRequest
= async(Dispatchers.IO) { requestPreVoteInfoSync() }
// 请求1,创建子协程,立即发起
val
welfareRequest
= async(Dispatchers.IO) { requestWelfareInfoSync() }
// 请求2,创建另一个子协程,立即发起
val
preVoteInfo
= preVoteRequest.await().model
// 调用await会挂起外层launch,等待请求1完成
val
welfareInfo
= welfareRequest.await().model
// 两个请求同时进行,await会在对应请求完成后立即返回
mLoadingView.visibility = View.GONE
// 此时两个请求均已返回,可统一刷新 UI
initViewPager(welfareInfo !=
null
)
if
(preVoteInfo !=
null
) {
// 渲染 tab 内容
}
else
{
showNetworkError()
}
}
}
private
fun requestPreVoteInfoSync(): SyncRequestResult<PreVoteInfo> {
val
params
= mapOf(
"albumId"
to
"$mAlbumId"
)
return
CommonRequestM.getDataSync(
"${UrlConstants.getInstanse().monthlyVotePreInfo()}${System.currentTimeMillis()}"
,
params, PreVoteInfo::class.java
)
}
private
fun requestWelfareInfoSync(): SyncRequestResult<MonthlyTicketAlbumWelfareVo> {
val
url
= MainUrlConstants.getInstanse().queryListenWelfare(mAlbumId)
val params: MutableMap<String, String> = ArrayMap()
// ..省略设置请求参数代码
return
CommonRequestM.getDataSync(url, params, MonthlyTicketAlbumWelfareVo::class.java)
}

To avoid manually creating and cancelling a coroutine scope for each Activity/Fragment, the AndroidX Lifecycle library provides lifecycleScope , which is automatically bound to the component’s lifecycle.

implementation(
"androidx.lifecycle:lifecycle-runtime-ktx:2.3.1"
)

Using the lifecycle‑aware scope simplifies the code:

lifecycleScope.launch {
// Execute coroutine work here
}

Overall, adopting Kotlin coroutines eliminates repetitive callback boilerplate, centralizes UI‑state handling, enables concurrent network calls with clear composition, and integrates seamlessly with Android’s lifecycle management.

uiAndroidKotlinAsync Programmingcoroutinesnetwork requests
Ximalaya Technology Team
Written by

Ximalaya Technology Team

Official account of Ximalaya's technology team, sharing distilled technical experience and insights to grow together.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.