阅读 1157

android MVVM 基础类深入封装及使用

背景

在上篇文章介绍了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…

文章分类
Android
文章标签