背景
在上篇文章介绍了Mvvm基础使用,本篇文章在原来的ViewBinding基础上增加了ViewModel层的封装,使代码变得非常简洁。本篇文章仅作为记录知识使用,如有不对之处请指教!
MVVM基础架构封装
1.基础类BaseActivity
package com.example.studymvvmproject01.base
import android.content.pm.ActivityInfo
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.viewbinding.ViewBinding
import com.example.studymvvmproject01.R
import com.gyf.immersionbar.ImmersionBar
abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
protected open var mBinding: VB? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mBinding = getViewBinding()
setContentView(mBinding?.root)
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT//竖屏
initialize()
}
open fun initialize() {
}
abstract fun getViewBinding(): VB?
override fun onDestroy() {
super.onDestroy()
mBinding = null
}
}
2.BaseFragment 封装
abstract class BaseFragment<VB:ViewBinding>:Fragment() {
protected open var binding:VB?=null
protected open val mBinding get()= binding!!
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = getViewBinding()
return mBinding?.root
}
abstract fun getViewBinding(): VB?
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initialize()
}
open fun initialize() {
}
override fun onDestroy() {
super.onDestroy()
binding=null
}
}
3.BaseViewModel 层封装
typealias Block<T> = suspend (CoroutineScope) -> T
typealias Error = suspend (Exception) -> Unit
typealias Cancel = suspend (Exception) -> Unit
open class BaseViewModel : ViewModel() {
var TAG="BaseViewModel"
val needLogin = MutableLiveData<Boolean>().apply { value = false }
protected fun launch(
block: Block<Unit>,
error: Error? = null,
cancel: Cancel? = null,
showErrorToast: Boolean = true,
): Job {
return viewModelScope.launch {
try {
block.invoke(this)
} catch (e: Exception) {
when (e) {
is CancellationException -> {
cancel?.invoke(e)
}
else -> {
onError(e, showErrorToast)
error?.invoke(e)
}
}
}
}
}
/**
* 统一处理错误
* @param e 异常
* @param showErrorToast 是否显示错误吐司
*/
@SuppressLint("WrongConstant")
private fun onError(e: Exception, showErrorToast: Boolean) {
when (e) {
is ApiException -> {
when (e.code) {
-1001 -> {
if (showErrorToast) {
Toast.makeText(AppHelper.mContext,e.message,1000).show()
}
needLogin.value = true
}
// 其他错误
else -> {
if (showErrorToast) Toast.makeText(AppHelper.mContext,e.message,1000).show()
}
}
Log.e(TAG,e.toString())
}
// 网络请求失败
is ConnectException, is SocketTimeoutException, is UnknownHostException, is HttpException -> {
if (showErrorToast) Toast.makeText(AppHelper.mContext,"网络请求失败",1000).show()
Log.e(TAG,"网络请求失败"+e.toString())
}
// 数据解析错误
is JsonParseException -> {
Log.e(TAG,"数据解析错误"+e.toString())
}
// 其他错误
else -> {
Log.e(TAG,"其他错误"+e.toString())
}
}
}
}
trip:关于CoroutineScope的介绍可以参考zhuanlan.zhihu.com/p/297543508
4.BaseVmActivity封装
abstract class BaseVmActivity<VB : ViewBinding, VM : BaseViewModel> : BaseActivity<VB>() {
protected open lateinit var mViewModel: VM
//加载数量
protected open val mTotalCount = 20
protected open var mCurrentSize = 0//当前加载数量
protected open var mCurrentPage = 0//当前加载页数
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
initViewModel()
observer()
initView()
initData()
setListener()
}
open fun setListener() {
}
open fun initData() {
}
open fun initView() {
}
/**
* 订阅退出登录逻辑
*/
private fun observer() {
mViewModel.needLogin.observe(this, {
//如果未登录,跳转到登录页面
if (it) {
SpUtil.setBoolean(MyConfig.IS_LOGIN, false)
//跳转登录页面
}
})
}
private fun initViewModel() {
mViewModel = ViewModelProvider(this).get(viewModelClass())
}
abstract fun viewModelClass(): Class<VM>
override fun onDestroy() {
super.onDestroy()
mCurrentSize = 0
mCurrentPage = 0
}
}
5.BaseVMFragment封装
abstract class BaseVMFragment<VB : ViewBinding, VM : BaseViewModel> : BaseFragment<VB>() {
protected lateinit var mViewModel: VM
private var lazyLoaded = false
//分页参数
protected open val mTotalCount = 20//每次加载数量
protected open var mCurrentSize = 0//当前加载数量
protected open var mCurrentPage = 0//当前加载页数
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initViewModel()
observe()
initView()
initData()
setListener()
}
open fun setListener() {
}
open fun initData() {
}
open fun initView() {
}
open fun observe() {
mViewModel.needLogin.observe(viewLifecycleOwner, {
if (it) {
SpUtil.setBoolean(MyConfig.IS_LOGIN, false)
}
})
}
private fun initViewModel() {
mViewModel = ViewModelProvider(this).get(viewModelClass())
}
abstract fun viewModelClass(): Class<VM>
override fun onResume() {
super.onResume()
if(!lazyLoaded){
lazyLoadData()
lazyLoaded=true
}
}
open fun lazyLoadData() {
}
}
6.BaseRepository封装
open class BaseRepository{
protected fun apiService(): Api {
return RetrofitClient.create(Api::class.java)
}
}
7.网络架构封装
(1)RetrofitClient类
object RetrofitClient{
private const val CALL_TIMEOUT = 10L
private const val CONNECT_TIMEOUT = 20L
private const val IO_TIMEOUT = 20L
private val mRetrofit:Retrofit
init {
val loggingInterceptor = HttpLoggingInterceptor { Log.d("httpLog", it) }
loggingInterceptor.level=HttpLoggingInterceptor.Level.BODY
/**
* OkHttpClient
*/
val okHttpClient=OkHttpClient.Builder()
.callTimeout(CALL_TIMEOUT, TimeUnit.SECONDS)
.connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS)
.readTimeout(IO_TIMEOUT, TimeUnit.SECONDS)
.writeTimeout(IO_TIMEOUT, TimeUnit.SECONDS)
//添加头部信息
.addInterceptor(AddCookiesInterceptor())
//拦截接口头部信息
// .addInterceptor(ReceivedCookiesInterceptor())
//日志拦截
.addInterceptor(loggingInterceptor)
.retryOnConnectionFailure(true)
.build()
mRetrofit= Retrofit.Builder().client(okHttpClient)
.baseUrl(Api.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
fun <T> create(tClass: Class<T>?): T {
return mRetrofit.create(tClass)
}
}
(2)添加头部信息拦截
class AddCookiesInterceptor:Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val builder:Request.Builder=chain.request().newBuilder();
val stringSet = SpUtil.getString(MyConfig.COOKIE)
builder.addHeader("Authorization","Bearer "+stringSet)
return chain.proceed(builder.build())
}
}
提示 此处Authorization为后台jwt认证服务接口认证
以上部分为mvvm基础框架封装,下面部分主要讲解如何使用。
实例
接口Api
interface Api {
companion object{
// const val BASE_URL="https://www.wanandroid.com/"
const val BASE_URL="http://172.16.7.3:8066/"
}
//体系数据
@GET("tree/json")
suspend fun getTree(): MutableList<Unit>
/**
* 获取登录token
* 静态Header
* @param requestBody body
* @return
*/
@Headers("Authorization:Basic dmlkZW9hbmFseXNpczp2aWRlb2FuYWx5c2lz")
@POST("/safeMobileServer/smc/auth/oauth/token")
suspend fun toke(@Body requestBody: RequestBody?):LoginBean
/**
* 按单位获取电厂作业数据
* @param headers 动态header
* @param siteId 站点Id
* @return
*/
@GET("/safeMobileServer/smc/psmgpersonloccur/personCntByType")
suspend fun workIndex(
@Query("siteId") siteId: Int,
):HomeProadBean
}
2.LoginBean 实体类
class LoginBean {
/**
* access_token : fd770c91-83ab-414e-b204-1112c26aed55
* token_type : bearer
* refresh_token : f5705a7a-914b-4371-ac56-f05f5c22406b
* expires_in : 40755
* scope : server
* tenant_id : 3
* license : made by kyny
* user_id : 300
* site_id : 1
* active : true
* dept_id : null
* username : 66ADMIN
*/
private var access_token: String? = null
private var token_type: String? = null
private var refresh_token: String? = null
private var expires_in = 0
private var scope: String? = null
private var tenant_id = 0
private var license: String? = null
private var user_id = 0
private var site_id = 0
private var active = false
private var dept_id: Any? = null
private var username: String? = null
fun getAccess_token(): String? {
return access_token
}
fun setAccess_token(access_token: String?) {
this.access_token = access_token
}
fun getToken_type(): String? {
return token_type
}
fun setToken_type(token_type: String?) {
this.token_type = token_type
}
fun getRefresh_token(): String? {
return refresh_token
}
fun setRefresh_token(refresh_token: String?) {
this.refresh_token = refresh_token
}
fun getExpires_in(): Int {
return expires_in
}
fun setExpires_in(expires_in: Int) {
this.expires_in = expires_in
}
fun getScope(): String? {
return scope
}
fun setScope(scope: String?) {
this.scope = scope
}
fun getTenant_id(): Int {
return tenant_id
}
fun setTenant_id(tenant_id: Int) {
this.tenant_id = tenant_id
}
fun getLicense(): String? {
return license
}
fun setLicense(license: String?) {
this.license = license
}
fun getUser_id(): Int {
return user_id
}
fun setUser_id(user_id: Int) {
this.user_id = user_id
}
fun getSite_id(): Int {
return site_id
}
fun setSite_id(site_id: Int) {
this.site_id = site_id
}
fun isActive(): Boolean {
return active
}
fun setActive(active: Boolean) {
this.active = active
}
fun getDept_id(): Any? {
return dept_id
}
fun setDept_id(dept_id: Any?) {
this.dept_id = dept_id
}
fun getUsername(): String? {
return username
}
fun setUsername(username: String?) {
this.username = username
}
}
3.HomeProadBean实体类
class HomeProadBean {
/**
* code : 0
* msg : 成功获取电厂现场人数分类统计
* data : [
* {"personCnt":11,"typeCode":"OVERALL"}
* ,{"personCnt":1,"typeCode":"OVERALL"}
* ,{"personCnt":0,"typeCode":"STAFF"},
* {"personCnt":0,"typeCode":"OUTSRC"},
* {"personCnt":6,"typeCode":"OTHERS"}]
*/
private var code = 0
private var msg: String? = null
private var data: List<DataBean?>? = null
fun getCode(): Int {
return code
}
fun setCode(code: Int) {
this.code = code
}
fun getMsg(): String? {
return msg
}
fun setMsg(msg: String?) {
this.msg = msg
}
fun getData(): List<DataBean?>? {
return data
}
fun setData(data: List<DataBean?>?) {
this.data = data
}
class DataBean {
/**
* personCnt : 11
* typeCode : OVERALL
*/
var personCnt = 0
var typeCode: String? = null
var name: String? = null
}
}
3.LoginRepository
class LoginRepository:BaseRepository() {
suspend fun login()=apiService().getTree()
suspend fun token(requestBody: RequestBody? ):LoginBean{
return apiService().toke(requestBody)
}
suspend fun workIndex(siteId:Int):HomeProadBean{
return apiService().workIndex(siteId)
}
}
4.LoginViewModel
class LoginViewModel : BaseViewModel() {
val repository by lazy {
LoginRepository()
}
val loginInfo=MutableLiveData<LoginBean>()
val homeProadBean=MutableLiveData<HomeProadBean>()
fun login(requestBody: RequestBody?) {
launch(
block = {
val token = repository.token(requestBody)
loginInfo.value=token
}
)
}
fun workIndex(siteId:Int) {
launch(
block = {
homeProadBean.value= repository.workIndex(siteId)
}
)
}
}
5.LoginActivity 实现
class LoginActivity : BaseVmActivity<ActivityMain1Binding, LoginViewModel>() {
override fun viewModelClass(): Class<LoginViewModel> {
return LoginViewModel::class.java
}
override fun getViewBinding(): ActivityMain1Binding {
return ActivityMain1Binding.inflate(layoutInflater)
}
override fun initData() {
super.initData()
//登录
val hashMap = HashMap<String, String>()
hashMap["scope"] = "server"
hashMap["username"] = "66admin"
hashMap["password"] = "Kyny@2021"
hashMap["grant_type"] = "password"
val requestBody = RequestUtil.getRequestBody(hashMap);
mViewModel.login(requestBody)
//登录返回结果数据订阅
mViewModel.workIndex(1)
}
override fun initView() {
super.initView()
mViewModel.loginInfo.observe(this, {
mBinding?.tvContent?.text = it.getAccess_token() + "," + it.getUsername()
//此处为登录接口,获取用户token
SpUtil.setString(MyConfig.COOKIE, it.getAccess_token())
})
//此接口为获取正常数据接口,并需要带头部参数认证
mViewModel.homeProadBean.observe(this,{
it.getMsg()?.let { it1 -> Log.e("TAG", it1) }
for(data in it.getData()!!){
Log.e("data", data?.personCnt.toString())
}
})
}
}
6.RequestUtil
此方法是将map集合转换为RequestBody实体类
object RequestUtil {
fun getRequestBody(hashMap: HashMap<String, String>): RequestBody? {
val data = StringBuffer()
if (hashMap != null && hashMap.size > 0) {
val iter: Iterator<*> = hashMap.entries.iterator()
while (iter.hasNext()) {
val entry =
iter.next() as Map.Entry<*, *>
val key = entry.key!!
val `val` = entry.value!!
data.append(key).append("=").append(`val`).append("&")
}
}
val jso = data.substring(0, data.length - 1)
return RequestBody.create("application/x-www-form-urlencoded; charset=utf-8".toMediaTypeOrNull(),
jso)
}
}
end:以上为mvvm架构基础封装,如有不对之处请指教。 源码地址:gitee.com/gxx123/andr…