网上看了好多的android mvvm形式,大多数都很复杂,不够简洁,造成项目代码臃肿,逻辑难以梳理,因此分享下自己的mvvm实践(大多数来源于优秀项目思路的整合,可能不是最优的):
1.第一步,当然是接口了(kt代码)
interface ApiService {
companion object {
val instance by lazy { RetrofitFactory.create(ApiService::class.java) }
}
@Headers("$DOMAIN_NAME_HEADER$BASE_HTTP_URL_NAME")
@POST("user/login")
@FormUrlEncoded
suspend fun login(@FieldMap map: Map): ResponseBean
}
其中RetrofitFactory代码为:
object RetrofitFactory {
//初始化
//通用拦截
private val interceptor: Interceptor by lazy {
Interceptor { chain ->
val request = chain.request()
val builder = request.newBuilder()
builder.addHeader("X-Client-Platform", "Android")
.addHeader("X-Client-Version", BuildConfig.VERSION_NAME)
.addHeader("X-Client-Build", BuildConfig.VERSION_CODE.toString())
.build()
chain.proceed(request)
}
}
//Retrofit实例化
private val retrofit: Retrofit by lazy {
Retrofit.Builder()
.baseUrl(Constant.DEFAULT_URL)
.addConverterFactory(NullOnEmptyConverterFactory())
.addConverterFactory(CustomConverterFactory.create(ResponseBean::class.java))
.client(RetrofitUrlManager.getInstance().with(initClient()).build())
.build()
}
/*
OKHttp创建
*/
private fun initClient(): OkHttpClient.Builder {
val sslParams1 = HttpsUtils.getSslSocketFactory()
return OkHttpClient.Builder()
.sslSocketFactory(sslParams1.sSLSocketFactory, sslParams1.trustManager)
.addInterceptor(initLogInterceptor())
.addInterceptor(interceptor)
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
}
/*
日志拦截器
*/
private fun initLogInterceptor(): HttpLoggingInterceptor {
val interceptor = HttpLoggingInterceptor()
interceptor.setPrintLevel(HttpLoggingInterceptor.Level.BODY)
interceptor.setColorLevel(Level.INFO)
return interceptor
}
/*
具体服务实例化
*/
fun create(service: Class): T {
return retrofit.create(service)
}
}
其中header用到了me.jessyan:retrofit-url-manager:1.4.0这个库,主要作用是多域名配置
2.第二步
object Repository : BaseRepository() {
suspend fun login(map: Map): ResponseBean {
return apiCall { ApiService.instance.login(map) }
}
}
其中用到的BaseRepository代码为:
open class BaseRepository {
suspend fun apiCall(call: suspend () -> ResponseBean): ResponseBean {
return call.invoke()
}
suspend fun dbCall(call: suspend () -> T): T {
return call.invoke()
}
}
3.第三步,就是viewmodel了,代码如下
class LoginViewModel: BaseViewModel() {
val mLoginLiveData = StateLiveData()
fun login(userName:String,password:String){
if (userName.isEmpty()){
mLoginLiveData.postError(1,"用户名不能为空")
return
}
if (password.isEmpty()){
mLoginLiveData.postError(1,"密码不能为空")
return
}
val map = hashMapOf()
map["adminLoginName"] = userName
map["adminPassword"] = password
launch(mLoginLiveData){
await { Repository.login(map) }
}
}
}
其中用到的BaseViewModel,代码为:
open class BaseViewModel : ViewModel() {
fun launch(block: suspend CoroutineScope.() -> Unit) {
if (!isNetUsable)
return
viewModelScope.launch { block() }
}
fun launch(
liveData: StateLiveData,
tryBlock: suspend CoroutineScope.() -> T
) {
if (!isNetUsable) {
liveData.postStart()
liveData.postComplete()
liveData.postNetError()
return
}
launch {
tryCatch(liveData, tryBlock)
}
}
private suspend fun tryCatch(
liveData: StateLiveData,
tryBlock: suspend CoroutineScope.() -> T
) {
coroutineScope {
try {
d("start")
liveData.postStart()
d("start-end")
val response = tryBlock.invoke(this)
d("success")
liveData.value = response
d("parse")
} catch (e: OperatorException) {
e.printStackTrace()
d("fail")
liveData.postError(e.code, e.msg)
} catch (e: Exception) {
if (isDebug) {
throw e
} else {
liveData.postError(1, "网络连接失败,请稍候重试!")
e.message?.let { d(it) }
CrashReport.postCatchedException(e)
}
} finally {
liveData.postComplete()
d("complete")
}
}
}
}
StateLiveData代码如下:
class StateLiveData : MutableLiveData() {
val startState = MutableLiveData()
val otherState = MutableLiveData()
val completeState = MutableLiveData()
val error = MutableLiveData>()
fun postStart() {
if (startState.value != null)
startState.value = startState.value!! + 1
else
startState.value = 1
}
fun postComplete() {
if (completeState.value != null)
completeState.value = completeState.value!! + 1
else
completeState.value = 1
}
fun postEmpty() {
otherState.value = OtherState.EMPTY
}
fun postNetError() {
otherState.value = OtherState.NET_ERROR
}
fun postServerError() {
otherState.value = OtherState.SERVER_ERROR
}
fun postTokenError() {
otherState.value = OtherState.TOKEN_ERROR
}
fun postError(errorCode: Int, msg: String?) {
error.value = errorCode to msg
}
}
await的代码如下:
inline fun await(responseBean: () -> ResponseBean): T {
if (!"".isNetUsable)
throw OperatorException(-2, "网络连接失败,请打开网络连接!")
try {
val response = responseBean.invoke()
when (response.code) {
0 -> {
return response.data ?: T::class.java.newInstance()
}
2000 -> {
ActivityTask.clearTask()
Router.withApi(App::class.java).toLogin()
throw OperatorException(2000, "您的账号在其它地方登陆,请保管好账号密码!")
}
else -> {
if (response.msg?.contains(tokenError) == true) {
ActivityTask.clearTask()
Router.withApi(App::class.java).toLogin()
BaseApplication.instance.toast("您的账号在其它地方登陆,请保管好账号密码!")
throw OperatorException(2000, "您的账号在其它地方登陆,请保管好账号密码!")
} else {
throw OperatorException(response.code, response.msg ?: "数据解析异常,请联系技术人员解决!")
}
}
}
} catch (e: OperatorException) {
throw OperatorException(e.code, e.msg)
} catch (e: SocketTimeoutException) {
e.printStackTrace()
throw OperatorException(-6, "网络连接超时,请稍后重试...")
} catch (e: Exception) {
e.printStackTrace()
CrashReport.postCatchedException(e)
throw OperatorException(-6, "网络连接异常!")
}
}
4.第四步就是activity代码了,如下
mViewModel.mLoginLiveData.observes(this) {
onStart {
LoadingDialog.show(supportFragmentManager, "登录中")
}
onSuccess {
PreferenceManager.user = it
PreferenceManager.token = it.userToken!!
//记录用户登录密码
PreferenceManager.userLoginInfo = Pair(
username.text.toString(),
if (isRememberPassword) password.text.toString() else ""
)
startActivity()
finish()
}
onFailed { error, _ ->
showTipToast(error.toString())
}
onNetFail {
showTipToast("网络连接失败,请检查网络...")
}
onServerFail {
showTipToast("服务器错误,请稍候重试")
}
onComplete {
LoadingDialog.dismiss()
}
}
其中自定义扩展函数observes为:
inline fun StateLiveData.observes(
owner: LifecycleOwner,
dsl: RetrofitCoroutineDsl.() -> Unit
) {
val request = RetrofitCoroutineDsl()
request.dsl()
observe(owner, Observer {
//这块千万不要改,出错不负责
request.onSuccess?.invoke(it ?: T::class.java.newInstance())
})
startState.observe(owner, Observer {
request.onStart?.invoke()
})
otherState.observe(owner, Observer {
when (it) {
OtherState.NET_ERROR -> {
request.onNetFail?.invoke()
}
OtherState.SERVER_ERROR -> {
request.onServerFail?.invoke()
}
OtherState.EMPTY -> {
d("collect observe onempty ${request.onEmpty == null}")
request.onEmpty?.invoke()
}
OtherState.TOKEN_ERROR -> {
ActivityTask.clearTask()
Router.withApi(App::class.java).toLogin()
BaseApplication.instance.toast("您的账号在其它地方登陆,请保管好账号密码!")
}
else -> {
}
}
})
completeState.observe(owner, Observer {
request.onComplete?.invoke()
})
error.observe(owner, Observer {
if (it?.second?.contains(tokenError) == true) {
ActivityTask.clearTask()
Router.withApi(App::class.java).toLogin()
BaseApplication.instance.toast("您的账号在其它地方登陆,请保管好账号密码!")
} else {
request.onFailed?.invoke(it.second, it.first)
}
})
}
RetrofitCoroutineDsl代码为:
enum class OtherState {
EMPTY, NET_ERROR, SERVER_ERROR, TOKEN_ERROR
}
class RetrofitCoroutineDsl {
lateinit var api: (ResponseBean)
var onSuccess: ((T) -> Unit)? = null
var onEmpty: (() -> Unit)? = null
var onComplete: (() -> Unit)? = null
var onStart: (() -> Unit)? = null
var onNetFail: (() -> Unit)? = null
var onServerFail: (() -> Unit)? = null
var onFailed: ((msg: String?, code: Int) -> Unit)? = null
var showFailedMsg = false
internal fun clean() {
onSuccess = null
onComplete = null
onFailed = null
onEmpty = null
onStart = null
onNetFail = null
onServerFail = null
}
fun onSuccess(block: (T) -> Unit) {
this.onSuccess = block
}
fun onComplete(block: () -> Unit) {
this.onComplete = block
}
fun onEmpty(block: () -> Unit) {
d("collect dsl onempty")
this.onEmpty = block
}
fun onStart(block: () -> Unit) {
this.onStart = block
}
fun onNetFail(block: () -> Unit) {
this.onNetFail = block
}
fun onServerFail(block: () -> Unit) {
this.onServerFail = block
}
fun onFailed(block: (error: String?, code: Int) -> Unit) {
this.onFailed = block
}
}
其中,采用kt的dsl写法,按需求,写你需要的方法
因此你的网络请求就很简单了,就这四步了,这个实践支持串行请求,只需要这样写:
fun login(userName:String,password:String){
if (userName.isEmpty()){
mLoginLiveData.postError(1,"用户名不能为空")
return
}
if (password.isEmpty()){
mLoginLiveData.postError(1,"密码不能为空")
return
}
val map = hashMapOf()
map["adminLoginName"] = userName
map["adminPassword"] = password
launch(mLoginLiveData){
val name = await { Repository.getName(xxx) }
val password = await { Repository.getPassword(xxx) }
await { Repository.login(name,password) }
}
}
可以看到网络请求思路清晰,且书写简单!!!