Kotlin Network Request Wrappers with LiveData: Two Approaches, Code Samples, and Comparison
This article explains two Kotlin‑based network request wrappers for Android that reduce boilerplate by using LiveData, Retrofit, OkHttp and coroutines, compares their design trade‑offs, provides complete Activity, ViewModel, Repository and BaseRepository code samples, and shows how to handle loading, success, error and multiple data sources in a clean MVVM architecture.
Purpose
Provide a simple way to invoke network requests with minimal repetitive code, without relying on third‑party libraries (only Retrofit + OkHttp + coroutines), and make it usable even for developers unfamiliar with coroutines.
Implementation 1
Less code, request automatically shows a loading indicator, and does not require manual loading handling.
However, it tightly couples the LiveData to the whole request chain, making it harder to decouple UI and data sources.
Activity Code Example
mViewModel.wxArticleLiveData.observe(this, object : IStateObserver
>() {
override fun onSuccess(data: List
?) { }
override fun onError() { }
})
mViewModel.wxArticleLiveData.observeState(this) {
onSuccess { data -> Log.i("wutao", "网络请求的结果是:$data") }
onError { }
}ViewModel Code Example
class MainViewModel {
private val repository by lazy { WxArticleRepository() }
val wxArticleLiveData = StateLiveData
>()
fun requestNet() {
viewModelScope.launch {
repository.fetchWxArticle(wxArticleLiveData)
}
}
}Repository Code Example
class WxArticleRepository : BaseRepository() {
private val mService by lazy { RetrofitClient.service }
suspend fun fetchWxArticle(stateLiveData: StateLiveData
>) {
executeResp(stateLiveData, mService::getWxArticle)
}
interface ApiService {
@GET("wxarticle/chapters/json")
suspend fun getWxArticle(): BaseResponse
>
}
}Implementation 2
Eliminates the LiveData‑driven request chain, separates loading from the request, and returns results directly from the repository, making the architecture more decoupled and suitable for multiple data sources.
Activity Code Example
mViewModel.login("username", "password")
mViewModel.userLiveData.observeState(this) {
onSuccess { data -> mBinding.tvContent.text = data.toString() }
onComplete { dismissLoading() }
}ViewModel Code Example
class MainViewModel {
val userLiveData = StateLiveData
()
fun login(username: String, password: String) {
viewModelScope.launch {
userLiveData.value = repository.login(username, password)
}
}
}Repository Code Example
suspend fun login(username: String, password: String): ApiResponse
{
return executeHttp { mService.login(username, password) }
}BaseRepository – Unified HTTP Handling
open class BaseRepository {
suspend fun
executeHttp(block: suspend () -> ApiResponse
): ApiResponse
{
runCatching { block.invoke() }
.onSuccess { data -> return handleHttpOk(data) }
.onFailure { e -> return handleHttpError(e) }
return ApiEmptyResponse()
}
private fun
handleHttpError(e: Throwable): ApiErrorResponse
{ /* ... */ }
private fun
handleHttpOk(data: ApiResponse
): ApiResponse
{ /* ... */ }
private fun
getHttpSuccessResponse(response: ApiResponse
): ApiResponse
{ /* ... */ }
}LiveData and Observer Extensions
abstract class IStateObserver
: Observer
> {
override fun onChanged(apiResponse: ApiResponse
) {
when (apiResponse) {
is ApiSuccessResponse -> onSuccess(apiResponse.response)
is ApiEmptyResponse -> onDataEmpty()
is ApiFailedResponse -> onFailed(apiResponse.errorCode, apiResponse.errorMsg)
is ApiErrorResponse -> onError(apiResponse.throwable)
}
onComplete()
}
abstract fun onSuccess(data: T)
abstract fun onError(e: Throwable)
abstract fun onDataEmpty()
abstract fun onFailed(errorCode: Int?, errorMsg: String?)
abstract fun onComplete()
}
class StateLiveData
: MutableLiveData
>() {
fun observeState(owner: LifecycleOwner, listenerBuilder: ListenerBuilder.() -> Unit) {
val listener = ListenerBuilder().also(listenerBuilder)
val observer = object : IStateObserver
() {
override fun onSuccess(data: T) { listener.mSuccessListenerAction?.invoke(data) }
override fun onError(e: Throwable) { listener.mErrorListenerAction?.invoke(e) ?: toast("Http Error") }
override fun onDataEmpty() { listener.mEmptyListenerAction?.invoke() }
override fun onFailed(errorCode: Int?, errorMsg: String?) { listener.mFailedListenerAction?.invoke(errorCode, errorMsg) }
override fun onComplete() { listener.mCompleteListenerAction?.invoke() }
}
super.observe(owner, observer)
}
}Conclusion
Implementation 1 reduces code size and provides built‑in loading, but its tight coupling to UI limits flexibility and makes handling multiple data sources cumbersome.
Implementation 2 achieves better decoupling, works independently of UI modules, and handles multiple data sources more cleanly, at the cost of requiring explicit loading management.
Choose the approach that best fits your project’s needs; both aim to simplify network requests in Android MVVM architecture.
Sohu Tech Products
A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.