purpose
-
Simple call, write less repetitive code
-
No dependence on third-party libraries (Retrofit+Okhttp+ coroutines only)
-
You don’t even know coroutines.
-
What does it mean to write Kotlin code in Kotlin’s way? Compare the following two codes:
mViewModel.wxArticleLiveData.observe(this.object : IStateObserver<List<WxArticleBean>>() {
override fun onSuccess(data: List<WxArticleBean>?{}override fun onError(a) {
}
})
mViewModel.wxArticleLiveData.observeState(this) {
onSuccess { data: List<WxArticleBean>? ->
}
onError {
}
}
Copy the code
Since we are using Kotlin, we should not write the interface back in the Java way. Is the DSL expression not sweet?
Provides two ways to implement:
- Mode 1 has less code and Loading is provided for network requests. Therefore, you do not need to manually call Loading
- Mode two decouples more thoroughly
There are differences between the two design ideas in decoupling. Depending on specific requirements, no one is better or worse. According to their own projects, which one is more convenient to use.
Encapsulation based on official architecture:
1. Encapsulation 1
Sample code in Activity
Click request network
mViewModel.getArticleData()
Copy the code
Set the listener to listen only for successful results, using the default exception handling
mViewModel.wxArticleLiveData.observeState(this) {
onSuccess { data ->
Log.i("wutao"."The result of the network request is:$data")}}Copy the code
If you need to handle each callback separately
These callbacks are optional and do not need to be optional
mViewModel.wxArticleLiveData.observeState(this) {
onSuccess { data ->
Log.i("wutao"."The result of the network request is:$data")
}
onEmpty{
Log.i("wutao"."Return null data, show null layout.")
}
onFailed {
Log.i("wutao".ErrorCode returned in the background:$it")
}
onException { e ->
Log.i("wutao"."This is an exception callback that is not returned in the background.")
}
onShowLoading {
Log.i("wutao"."Customizing Loading for a single request")
}
onComplete {
Log.i("wutao"."Network request terminated.")}}Copy the code
Request Loading
Many network requests require Loading. You do not want to write onShowLoading{} every time.
mViewModel.wxArticleLoadingLiveData.observeState(this.this) {
onSuccess { data ->
Log.i("wutao"."The result of the network request is:$data")}}Copy the code
The second observeState() method passes in a reference to the UI, so that Loading is automatically loaded before a single network request, and Loading is automatically cancelled on success or failure.
The above code is in the Activity, let’s look at the ViewModel.
Example code in ViewModel
class MainViewModel{
private val repository by lazy { WxArticleRepository() }
val wxArticleLiveData = StateLiveData<List<WxArticleBean>>()
fun requestNet(a) {
viewModelScope.launch {
repository.fetchWxArticle(wxArticleLiveData)
}
}
}
Copy the code
Simply introduce the corresponding data warehouse Repo and then use the coroutine to perform the network request method. Look at the code in the Repo.
Repository code examples
class WxArticleRepository : BaseRepository() {
private val mService by lazy { RetrofitClient.service }
suspend fun fetchWxArticle(stateLiveData: StateLiveData<List<WxArticleBean> >) {
executeResp(stateLiveData, mService::getWxArticle)
}
}
interface ApiService {
@GET("wxarticle/chapters/json")
suspend fun getWxArticle(a): BaseResponse<List<WxArticleBean>>
}
Copy the code
Get a Retrofit instance and then call the ApiService interface method.
Package one advantage
-
The code is clean, there is no hand-written thread switching code, and there are not many interface callbacks.
-
With Loading state configured, you do not need to manually enable or disable Loading.
-
Data-driven UI, with LiveData as the carrier, returns the page state and network results to the UI through LiveData.
Project address:
Github.com/ldlywt/Fast… (Branch name: withLoading)
The deficiency of encapsulation one
The core idea of encapsulation 1 is that one LiveData runs through the entire network request chain. This is both its strength and its weakness.
-
Decoupling is incomplete and goes against the idea of clearly defined boundaries of responsibility between modules of an application
-
If Loading is required for LiveData listening, BaseActivity interfaces with Loading methods must be implemented.
-
The obserState() method passes a UI reference in the second argument.
-
If you’re new to the method, you’ll have a lot of questions about why you need a LiveData parameter for the method. What happened to the return value of the network request?
-
One of the biggest drawbacks of encapsulation is that it presents a very unfriendly side to multiple data sources.
A Repository is a data warehouse where all the ways to get data in a project are agreed to manage, and network access to data is just one of them.
If you want to add one to retrieve data from the database or cache, the encapsulation is hard to change. If you want to force the change, it breaks the encapsulation and is very invasive.
In view of the deficiency of package 1, package 2 is optimized.
Two, packaging two
Train of thought
-
To solve the above shortcomings, LiveData cannot be used as a carrier throughout the network request.
-
Remove the UI reference from the Observe() method. Don’t overlook a UI reference that represents a specific Activity that is coupled to Observe and that implements the IUiView interface.
-
The network request is separated from the Loading state. Therefore, the Loading must be manually controlled.
-
Methods in Repository have return values that return results and do not need to use livedata as a method parameter.
-
LiveData exists only in the ViewModel and does not exist throughout the request chain. There is no need for a reference to LiveData in Repository. The Repository code simply retrives the data.
-
For multiple data sources, it is also very easy to handle.
-
It has nothing to do with the UI and can be used entirely as a separate Lib.
The Activity of the code
// Request the network
mViewModel.login("username"."password")
// Register the listener
mViewModel.userLiveData.observeState(this) {
onSuccess {data ->
mBinding.tvContent.text = data.toString()
}
onComplete {
dismissLoading()
}
}
Copy the code
ObserveState () no longer requires a UI reference.
In the ViewModel
class MainViewModel {
valuserLiveData = StateLiveData<User? > ()fun login(username: String, password: String) {
viewModelScope.launch {
userLiveData.value = repository.login(username, password)
}
}
}
Copy the code
Data is sent through the setValue or postValue method of LiveData.
In the Repository
suspend fun login(username: String, password: String): ApiResponse<User? > {return executeHttp {
mService.login(username, password)
}
}
Copy the code
Methods in Repository all return request results, and the method parameters do not require livedata. Repository can stand on its own.
For multiple data sources
// WxArticleRepository
class WxArticleRepository : BaseRepository() {
private val mService by lazy {
RetrofitClient.service
}
suspend fun fetchWxArticleFromNet(a): ApiResponse<List<WxArticleBean>> {
return executeHttp {
mService.getWxArticle()
}
}
suspend fun fetchWxArticleFromDb(a): ApiResponse<List<WxArticleBean>> {
return getWxArticleFromDatabase()
}
}
// MainViewModel.kt
private val dbLiveData = StateLiveData<List<WxArticleBean>>()
private val apiLiveData = StateLiveData<List<WxArticleBean>>()
val mediatorLiveDataLiveData = MediatorLiveData<ApiResponse<List<WxArticleBean>>>().apply {
this.addSource(apiLiveData) {
this.value = it
}
this.addSource(dbLiveData) {
this.value = it
}
}
Copy the code
As you can see, encapsulation 2 is more consistent with the principle of single responsibility, Repository purely to obtain data, ViewModel to process and send data.
Project Address:
Github.com/ldlywt/Fast… (Master branch)
Third, the realization principle
Data from Hongyang Dashen playing Android open API
Back data structure definition: {"data":... ."errorCode": 0."errorMsg": ""
}
Copy the code
Encapsulate one and encapsulate two code gap is very small, mainly look at encapsulate two.
Define the data return class
open class ApiResponse<T>(
open val data: T? = null.open val errorCode: Int? = null.open val errorMsg: String? = null.open val error: Throwable? = null,
) : Serializable {
val isSuccess: Boolean
get() = errorCode == 0
}
data class ApiSuccessResponse<T>(val response: T) : ApiResponse<T>(data = response)
class ApiEmptyResponse<T> : ApiResponse<T>(a)data class ApiFailedResponse<T>(override val errorCode: Int? .override val errorMsg: String?) : ApiResponse<T>(errorCode = errorCode, errorMsg = errorMsg)
data class ApiErrorResponse<T>(val throwable: Throwable) : ApiResponse<T>(error = throwable)
Copy the code
Based on the base class returned by the background, different state data classes are defined according to different results.
Unified processing of network requests: BaseRepository
open class BaseRepository {
suspend fun <T> executeHttp(block: suspend() - >ApiResponse<T>): ApiResponse<T> {
runCatching {
block.invoke()
}.onSuccess { data: ApiResponse<T> ->
return handleHttpOk(data)
}.onFailure { e ->
return handleHttpError(e)
}
return ApiEmptyResponse()
}
/** * non-background return error, caught exception */
private fun <T> handleHttpError(e: Throwable): ApiErrorResponse<T> {
if (BuildConfig.DEBUG) e.printStackTrace()
handlingExceptions(e)
return ApiErrorResponse(e)
}
/** * returns 200, but isSuccess */ is also judged
private fun <T> handleHttpOk(data: ApiResponse<T>): ApiResponse<T> {
return if (data.isSuccess) {
getHttpSuccessResponse(data)}else {
handlingApiExceptions(data.errorCode, data.errorMsg)
ApiFailedResponse(data.errorCode, data.errorMsg)
}
}
/** * successful and empty data processing */
private fun <T> getHttpSuccessResponse(response: ApiResponse<T>): ApiResponse<T> {
return if (response.data= =null || response.data is List<*> && (response.data as List<*>).isEmpty()) {
ApiEmptyResponse()
} else {
ApiSuccessResponse(response.data!!) }}}Copy the code
Retrofit coroutine error code handling is thrown by exception, so try… Catch to catch error codes other than 200. Returned wrapped as different data-class objects.
Extend LiveData and Observers
The LiveData Observer() determines which data class it is and performs the corresponding callback:
abstract class IStateObserver<T> : Observer<ApiResponse<T>> {
override fun onChanged(apiResponse: ApiResponse<T>) {
when (apiResponse) {
is ApiSuccessResponse -> onSuccess(apiResponse.response)
is ApiEmptyResponse -> onDataEmpty()
is ApiFailedResponse -> onFailed(apiResponse.errorCode, apiResponse.errorMsg)
is ApiErrorResponse -> onError(apiResponse.throwable)
}
onComplete()
}
Copy the code
Then extend LiveData and replace the Java callback with kotlin’s DSL expression, shorthand code.
class StateLiveData<T> : MutableLiveData<ApiResponse<T>>() {
fun observeState(owner: LifecycleOwner, listenerBuilder: ListenerBuilder. () - >Unit) {
val listener = ListenerBuilder().also(listenerBuilder)
val value = object : IStateObserver<T>() {
override fun onSuccess(data: T){ listener.mSuccessListenerAction? .invoke(data)}override fun onError(e: Throwable){ listener.mErrorListenerAction? .invoke(e) ? : toast("Http Error")}override fun onDataEmpty(a){ listener.mEmptyListenerAction? .invoke() }override fun onComplete(a){ listener.mCompleteListenerAction? .invoke() }override fun onFailed(errorCode: Int? , errorMsg:String?).{ listener.mFailedListenerAction? .invoke(errorCode, errorMsg) } }super.observe(owner, value)
}
}
Copy the code
Four,
Encapsulation 1: Less code, can be based on the project needs to encapsulate some specific UI related, faster development, more fun to use.
Package 2: Decoupled more thoroughly, can run independently of UI module.
Personally, I think framework design mainly serves the needs of my own projects (except open source projects). It is better to conform to design patterns and design principles, but it doesn’t matter if it doesn’t. It is good if it suits my project needs and saves my time.
In our own projects, how light, how fast, how to write cool.
Five, thanks
Thank you very much for hongyang god to provide stable and easy to use to play Android, spare time to play Android open API to learn a lot of things.
I feel that the selfless sharing of the Internet has provided me with a lot of good ideas.
This network framework has been changed for many times, and finally optimized to the point that I am satisfied with it. If there is any shortage, please point out and communicate with me, so that we can learn and make progress together.
Vi. Project address
Github.com/ldlywt/Fast…
I would appreciate it if you could touch your finger and hit Star.