Android项目实战搭建 MVVM架构

news2025/3/24 22:18:15

View层

具体代码:
 activity:
/**
 * @description:
 * 普通Activity基类,不带ViewModel,显示基本加载状态
 * 需要获取到子类的布局id用于databinding的绑定
 * @author YL Chen
 * @date 2024/9/4 21:34
 * @version 1.0
 */
abstract class BaseActivity<VB : ViewDataBinding>(@LayoutRes layoutID: Int) :
    AppCompatActivity() { //此处不能将layoutId传递进去,否则会导致fragment加载但不显示

    open lateinit var mRefreshLayout: SmartRefreshLayout
    //仅供直接继承本类的子类调用,继承于BaseVMActivity的调用此对象的方法无效,
    // 因为setSuccessView方法先于getLoadView方法执行,导致加载状态无法被移除,继承于BaseVMActivity的想改变状态需使用mViewModel调用changeStateView方法
    open lateinit var mMultiplyStateView: MultiplyStateView
    //子view的布局id
    private var mLayoutId: Int = layoutID

    //子View的dataBinding
    lateinit var mBinding: VB

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //设置基础布局
        setContentView(R.layout.base_load_more_layout)
        //初始化界面
        initView()
        //初始化数据
        initData()
    }

    //初始化界面
    open fun initView() {
        //获取加载成功DataViewBinding
        mBinding = DataBindingUtil.inflate(
            layoutInflater,
            mLayoutId,
            null,
            false
        )
        //刷新框架
        mRefreshLayout = findViewById(R.id.refreshLayout)
        //找到基础布局中的自定义多状态View控件
        mMultiplyStateView = findViewById(R.id.multiply_state_view)
        // 将加载成功View布局添加到自定义多状态View中
        mMultiplyStateView.setSuccessView(mBinding.root)
    }

    //初始化数据
    abstract fun initData()
}



/**
 * @description: 携带ViewModel的Activity基类,继承自BaseActivity
 * @author YL Chen
 * @date 2024/9/6 14:16
 * @version 1.0
 */
abstract class BaseVMActivity<VB : ViewDataBinding, VM : BaseViewModel>(@LayoutRes layoutId: Int) :
    BaseActivity<VB>(layoutId) {
    //子类ViewModel实例
    lateinit var mViewModel: VM

    /**
     * 获取对应的ViewModel,并初始化数据
     */
    override fun initData() {
        //dataLoading()
        mViewModel = getViewModel()!!
        //将子类的ViewModel和dataBinding联系起来,实现界面数据的自动更新
        //将xml布局对应的viewModel对象赋值到xml布局中声明的viewModel变量 即实现如:mBinding.viewModel = ViewModel() 的效果
        val variableId = getVariableId()
        if (variableId != -1) {
            mBinding.setVariable(variableId, mViewModel)
            //立即执行 Data Binding 中的挂起绑定
            //即Data Binding 会立即将 ViewModel 的属性和方法更新到布局文件中
            mBinding.executePendingBindings()
        }
        //初始化视图状态
        initViewState()
        //初始化ViewModel数据
        initVMData()
        //监听liveData
        observeLiveData()
        //设置状态页点击重新加载监听
        mMultiplyStateView.setOnReLodListener(mViewModel)
    }

    /**
     * 监听ViewModel中的LiveData
     */
    open fun observeLiveData() {
    }

    /**
     * 初始化状态
     */
    private fun initViewState() {
        mViewModel.mStateViewLiveData.observe(this) {
            when (it) {
                ViewStateEnum.VIEW_LOADING -> {
                    LogUtils.d(this, "StateLayoutEnum.DATA_LOADING")
                    dataLoading()
                }

                ViewStateEnum.VIEW_EMPTY -> {
                    LogUtils.d(this, "StateLayoutEnum.DATA_ERROR")
                    dataEmpty()
                }

                ViewStateEnum.VIEW_NET_ERROR -> {
                    LogUtils.d(this, "StateLayoutEnum.NET_ERROR")
                    netError()
                }

                ViewStateEnum.VIEW_LOAD_SUCCESS -> {
                    LogUtils.d(this, "StateLayoutEnum.LOAD_SUCCESS")
                    loadSuccess()
                }

                ViewStateEnum.VIEW_NONE -> {
                    LogUtils.d(this, "StateLayoutEnum.NONE")
                }
            }
        }
    }

    /**
     * 数据加载成功
     */
    open fun loadSuccess() {
        mMultiplyStateView.showSuccess()
    }

    /**
     * 网络加载失败
     */
    open fun netError() {
        mMultiplyStateView.showNetError()
    }


    /**
     * 数据加载为空
     */
    open fun dataEmpty() {
        mMultiplyStateView.showEmpty()
    }

    /**
     * 数据加载中
     */
    open fun dataLoading() {
        mMultiplyStateView.showLoading()
        val loadingView = mMultiplyStateView.getLoadingView()
        val myLoadingView = loadingView.findViewById<MyLoadingView>(R.id.my_loading_view)
        myLoadingView.startRotate()
    }

    /**
     * 初始化ViewModel数据
     */
    abstract fun initVMData()

    /**
     * 获取xml绑定的variable
     * @return Int
     */
    //子类通过重写此方法返回子类对应xml文件中绑定的viewModel变量的id
    open fun getVariableId(): Int {
        return -1
    }

    /**
     * 通过反射获取子类的ViewModel
     * @return VM?
     */
    private fun getViewModel(): VM? {
        //这里获得到的是类的泛型的类型
        val type = javaClass.genericSuperclass
        if (type != null && type is ParameterizedType) {
            val actualTypeArguments = type.actualTypeArguments
            val tClass = actualTypeArguments[1]
            return ViewModelProvider(
                this,
                ViewModelProvider.AndroidViewModelFactory.getInstance(application)
            )
                .get(tClass as Class<VM>)
        }
        return null
    }
}
fragment:
/**
 * @description: 普通Fragment基类,不带ViewModel
 * @author YL Chen
 * @date 2024/9/6 16:19
 * @version 1.0
 */
abstract class BaseFragment<VB : ViewDataBinding>(@LayoutRes layoutId: Int = 0) : Fragment() {

    open lateinit var mRefreshLayout: SmartRefreshLayout
    open lateinit var mMultiplyStateView: MultiplyStateView

    //子类的布局id
    private val mLayoutId: Int = layoutId

    //子View的dataBinding
    lateinit var mBinding: VB

    lateinit var mRootView: View

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        //设置根布局
        mRootView = layoutInflater.inflate(R.layout.base_load_more_layout, container, false)
        return mRootView
    }


    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        //初始化视图
        initView()
        //初始化数据
        initData()
    }

    //初始化视图
    open fun initView() {
        //获取加载成功View的DataViewBinding
        mBinding = DataBindingUtil.inflate(layoutInflater, mLayoutId, null, false)
        //找到根布局的baseFrameLayout
        mMultiplyStateView = mRootView.findViewById(R.id.multiply_state_view)
        //将子类加载成功View布局添加进去
        mMultiplyStateView.setSuccessView(mBinding.root)
        //获取到刷新框架
        mRefreshLayout = mRootView.findViewById(R.id.refreshLayout)
    }

    //初始化数据
    abstract fun initData()
}





/**
 * @description: 携带ViewModel的fragment
 * @author YL Chen
 * @date 2024/9/6 21:52
 * @version 1.0
 */
abstract class BaseVMFragment<VB : ViewDataBinding, VM : BaseViewModel>(@LayoutRes layoutId: Int) :
    BaseFragment<VB>(layoutId) {


    lateinit var mViewModel: VM

    override fun initData() {
        mViewModel = getViewModel()!!
        val variableId = getVariableId()
        if (variableId != -1) {
            mBinding.setVariable(getVariableId(), mViewModel)
            mBinding.executePendingBindings()
        }
        initState()
        initVMData()
        observeLiveData()
        //设置状态页点击重新加载监听
        mMultiplyStateView.setOnReLodListener(mViewModel)
    }

    /**
     * 获取子类xml 的Variable
     * @return Int
     */
    open fun getVariableId(): Int {
        return -1
    }

    /**
     * 初始化状态
     */
    private fun initState() {
        mViewModel.mStateViewLiveData.observe(this) {
            when (it) {
                ViewStateEnum.VIEW_LOADING -> {
                    LogUtils.d(this, "StateLayoutEnum.DATA_LOADING")
                    dataLoading()
                }

                ViewStateEnum.VIEW_EMPTY -> {
                    LogUtils.d(this, "StateLayoutEnum.DATA_ERROR")
                    dataEmpty()
                }


                ViewStateEnum.VIEW_NET_ERROR -> {
                    LogUtils.d(this, "StateLayoutEnum.NET_ERROR")
                    netError()
                }

                ViewStateEnum.VIEW_LOAD_SUCCESS -> {
                    LogUtils.d(this, "StateLayoutEnum.LOAD_SUCCESS")
                    loadSuccess()
                }

                ViewStateEnum.VIEW_NONE -> {
                    LogUtils.d(this, "StateLayoutEnum.NONE")
                }
            }
        }
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        LogUtils.d(this, "onViewCreated")
    }

    /**
     * 数据加载成功
     */
    open fun loadSuccess() {
        mMultiplyStateView.showSuccess()
    }

    /**
     * 网络加载失败
     */
    open fun netError() {
        mMultiplyStateView.showNetError()
    }


    /**
     * 数据加载错误
     */
    open fun dataEmpty() {
        mMultiplyStateView.showEmpty()
    }

    /**
     * 数据加载中
     */
    open fun dataLoading() {
        mMultiplyStateView.showLoading()
        val loadingView = mMultiplyStateView.getLoadingView()
        val myLoadingView = loadingView.findViewById<MyLoadingView>(R.id.my_loading_view)
        myLoadingView.startRotate()
    }

    /**
     * 通过反射获取子类的ViewModel
     * @return VM?
     */
    private fun getViewModel(): VM? {//这里获得到的是类的泛型的类型
        val type = javaClass.genericSuperclass
        if (type != null && type is ParameterizedType) {
            val actualTypeArguments = type.actualTypeArguments
            val tClass = actualTypeArguments[1]
            return ViewModelProvider(
                this,
                ViewModelProvider.AndroidViewModelFactory.getInstance(requireActivity().application)
            )
                .get(tClass as Class<VM>)
        }
        return null
    }

    /**
     * 初始化ViewModel数据
     */
    abstract fun initVMData()

    /**
     * 监听ViewModel中的LiveData
     */
    open fun observeLiveData() {
    }

}
思路:
BaseXXX:

通过泛型接收子类视图的ViewDataBInding类型,并通过构造方法获取子类的视图ID。

onCreate()方法中,设置基础视图base_load_more_layout.xml,base_load_more_layout.xml中携带SmartRefreshLayout刷新框架和自定义的MultiplyStateView状态视图切换View,以实现刷新、加载更多视图和基础的状态视图切换功能;调用initView(),initData()方法

initView(): 初始化ViewDataBinding,使用构造方法中传递过来的子类视图ID进行初始化,并将其声明为公开的成员变量(mBinding),子类可直接调用,此ViewDataBinding为成功视图,并初始化SmartRefreshLayout和MultiplyStateView,声明为成员变量(mRefreshLayout和mMultiplyStateView),设置初始化的ViewDataBinding.root为MultiplyStateView的成功视图。

initData抽象方法强制子类重写,用于初始化数据。

BaseVMXXX:

通过泛型接受子类的ViewDataBinding和ViewModel,并通过构造方法获取子类视图ID,继承自BaseXXX,将泛型ViewDataBinding和视图ID传递给BaseXXX。

重写BaseXXX的initData()方法:

  1. 通过反射获取通过泛型传递进来的子类ViewModel的对象,并将其声明为公开的成员变量,子类可直接调用(mViewModel)

  2. 调用getVariableId()方法获取子类传递过来的在XML布局中绑定的ViewModel变量,并通过mBinding调用setsetVariable()方法将其与mViewModel绑定到一起,使得可以在子类的视图的一些事件可以绑定对应的ViewModel中。

  3. 调用initViewState()初始化当前View的视图状态,默认是Loding(加载中)状态。

  4. 调用initVMData()初始化ViewModel数据

  5. 调用observeLiveData()监听ViewMode中的LiveData

  6. lifecycle.addObserver(mViewModel),使ViewModel监听对应View类的生命周期,相当于将ViewModel与View类进行绑定,使其只在View生命周期内响应,避免内存泄漏或者资源的错误释放。

  7. mMultiplyStateView.setOnReLodListener(mViewModel):为状态视图设置重新加载监听,监听者为View类对应的ViewModel。

getVariableId():公开的方法,默认返回-1,供子类重写返回XML视图中绑定的ViewModel变量

initViewState():观察对应ViewModel中的切换视图LiveData变量(mStateViewLiveData),实现状态视图的初始化和切换。

observeLiveData():公开的方法,供子类重写监听观察对应ViewMode中的LiveData变量。

补充:
自定义多状态视图View代码:
package com.yl.wanandroid.ui.custom

import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.widget.FrameLayout
import androidx.annotation.LayoutRes
import com.yl.wanandroid.R
import com.yl.wanandroid.model.ViewStateEnum
import com.yl.wanandroid.utils.LogUtils


/**
 * @description: 自定义多状态View
 * @author YL Chen
 * @date 2024/9/29 10:47
 * @version 1.0
 */

open class MultiplyStateView : FrameLayout {

    private var mOnReLodListener: OnReLodListener? = null
    private lateinit var params: LayoutParams
    private lateinit var mInflater: LayoutInflater
    private var mSuccessViewId: Int = 0
    private var mEmptyViewId: Int = 0
    private var mNetErrorViewId: Int = 0
    private var mLoadingViewId: Int = 0

    //四种展示的view
    private var mLoadingView: View? = null
    private var mSuccessView: View? = null
    private var mNetErrorView: View? = null
    private var mEmptyView: View? = null

    //当前视图状态
    private var currentState: ViewStateEnum = ViewStateEnum.VIEW_NONE

    constructor(context: Context) : this(context, null)

    constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)

    constructor(context: Context, attrs: AttributeSet?, defy: Int) : super(context, attrs, defy) {
        initView(context, attrs)
    }

    private fun initView(context: Context, attrs: AttributeSet?) {
        //获取自定义属性
        val a =
            context.obtainStyledAttributes(attrs, R.styleable.MultiplyStateView)
        mLoadingViewId =
            a.getResourceId(R.styleable.MultiplyStateView_msv_loadingView, R.layout.view_loading)
        mNetErrorViewId =
            a.getResourceId(R.styleable.MultiplyStateView_msv_netErrorView, R.layout.view_net_error)
        mEmptyViewId =
            a.getResourceId(R.styleable.MultiplyStateView_msv_emptyView, R.layout.view_empty)
        mSuccessViewId = a.getResourceId(R.styleable.MultiplyStateView_msv_successView, 0)

        a.recycle()
        mInflater = LayoutInflater.from(context)
        params = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
        setLoadingView(mLoadingViewId)
        setEmptyView(mEmptyViewId)
        setNetErrorView(mNetErrorViewId)
    }

    //在 XML 布局文件中的视图被加载并且所有的子视图都被添加到父视图中之后执行
/*    override fun onFinishInflate() {
        super.onFinishInflate()
        //展示加载页面
        LogUtils.d(this,"onFinishInflate-->showLoading")
        showLoading()
        val loadingView = getLoadingView()
        val myLoadingView = loadingView.findViewById<MyLoadingView>(R.id.my_loading_view)
        myLoadingView.startRotate()
    }*/

    //++++++++++++++++++++++++++++++++加载页面++++++++++++++++++++

    //动态加载并展示加载页面
    fun showLoading() {
        if (mLoadingView == null) {
            mLoadingView = mInflater.inflate(mLoadingViewId, null)
        }
        if (mLoadingView != null ) {
            removeAllViews()
            addView(mLoadingView, 0, params)
        } else {
            throw NullPointerException("you have to set loading view before that")
        }
    }

    /**
     * 提供方法给外界设置自定义加载页面
     * @param layoutId Int 布局Id
     */
    open fun setLoadingView(@LayoutRes layoutId: Int) {
        setLoadingView(mInflater.inflate(layoutId, null))
    }

    open fun setLoadingView(view: View) {
        mLoadingView = view
    }

    /**
     * 获取加载页面
     */
    fun getLoadingView(): View {
        if (null == mLoadingView) {
            mLoadingView = mInflater.inflate(mLoadingViewId, null)
        }
        return mLoadingView!!
    }

    //++++++++++++++++++++++++++++++++成功页面++++++++++++++++++++
    /**
     * 显示成功状态
     */
    fun showSuccess() {
        if (null == mSuccessView) {
            mSuccessView = mInflater.inflate(mSuccessViewId, null)
        }
        if (mSuccessView != null) {
            LogUtils.d(this,"childCount-->${childCount}")
            val loadingView = getLoadingView()
            val myLoadingView = loadingView.findViewById<MyLoadingView>(R.id.my_loading_view)
            myLoadingView.stopRotate()
            removeAllViews()
            LogUtils.d(this,"childCount-->${childCount}")
            addView(mSuccessView, 0, params)
            LogUtils.d(this,"childCount-->${childCount}")
            currentState = ViewStateEnum.VIEW_LOAD_SUCCESS
        } else {
            throw NullPointerException("you have to set success view before that")
        }
    }

    /**
     * 设置自定义的成功页面
     *
     * @param layoutResID
     */
    fun setSuccessView(@LayoutRes layoutResID: Int) {
        setSuccessView(mInflater.inflate(layoutResID, null))
    }

    /**
     * 设置自定义的成功页面
     *
     * @param view
     */
    fun setSuccessView(view: View) {
        mSuccessView = view
        LogUtils.d(this,"setSuccessView-->${view}")
    }


    /**
     * 获取成功页面
     */
    fun getSuccessView(): View {
        if (null == mSuccessView) {
            mSuccessView = mInflater.inflate(mSuccessViewId, null)
        }
        return mSuccessView!!
    }


    //++++++++++++++++++++++++++++++++网络错误页面++++++++++++++++++++
    /**
     * 显示加载失败(网络错误)状态 带监听器的
     */
    fun showNetError() {
        if (null == mNetErrorView) {
            mNetErrorView = mInflater.inflate(mNetErrorViewId, null)
        }

        if (mNetErrorView != null) {
            removeAllViews()
            addView(mNetErrorView, 0, params)
            currentState = ViewStateEnum.VIEW_NET_ERROR
            mNetErrorView!!.setOnClickListener { showReLoading() }
        } else {
            throw java.lang.NullPointerException("you have to set unknown view before that")
        }
    }


    /**
     * 设置自定义的网络异常
     *
     * @param layoutResID
     */
    fun setNetErrorView(@LayoutRes layoutResID: Int) {
        setNetErrorView(mInflater.inflate(layoutResID, null))
    }

    /**
     * 设置自定义的网络异常
     *
     * @param view
     */
    fun setNetErrorView(view: View) {
        mNetErrorView = view
    }


    /**
     * 设置获取网络错误页面
     */
    fun getNetErrorView(): View {
        if (null == mNetErrorView) {
            mNetErrorView = mInflater.inflate(mNetErrorViewId, null)
        }
        return mNetErrorView!!
    }


    //++++++++++++++++++++++++++++++++空页面页面++++++++++++++++++++
    /**
     * 显示无数据状态
     */
    fun showEmpty() {
        if (null == mEmptyView) {
            mEmptyView = mInflater.inflate(mEmptyViewId, null)
        }

        if (mEmptyView != null) {
            removeAllViews()
            addView(mEmptyView, 0, params)
            currentState = ViewStateEnum.VIEW_EMPTY
        } else {
            throw java.lang.NullPointerException("you have to set empty view before that")
        }
    }


    /**
     * 设置自定义的空页面
     *
     * @param layoutResID
     */
    fun setEmptyView(@LayoutRes layoutResID: Int) {
        setEmptyView(mInflater.inflate(layoutResID, null))
    }

    /**
     * 设置自定义的空页面
     *
     * @param view
     */
    fun setEmptyView(view: View) {
        mEmptyView = view
    }


    /**
     * 设置获取空页面
     */
    fun getEmptyView(): View {
        if (null == mEmptyView) {
            mEmptyView = mInflater.inflate(mEmptyViewId, null)
        }
        return mEmptyView!!
    }


    /**
     * 再次加载数据
     */
    private fun showReLoading() {
        //第一步重新loading
        if (mOnReLodListener != null) {
            showLoading()
            mOnReLodListener!!.onReLoad()
        } else {
            //未设置重新加载回调
            LogUtils.e(this, "请设置重新加载监听")
        }
    }

    /**
     * 外部回调
     *
     * @param onReLodListener
     */
    fun setOnReLodListener(onReLodListener: OnReLodListener) {
        this.mOnReLodListener = onReLodListener
    }

    /**
     * 重新加载页面的回调接口
     */
    interface OnReLodListener {
        fun onReLoad()
    }

    override fun onAttachedToWindow() {
        super.onAttachedToWindow()
        updateVisibility()
    }

    private fun updateVisibility() {
        // 获取父布局的可见性
        val parentVisibility = (parent as? View)?.visibility ?: View.VISIBLE
        visibility = parentVisibility
    }


    // 如果需要监听父布局的可见性变化,可以重写这个方法
    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
        super.onLayout(changed, left, top, right, bottom)
        updateVisibility()
    }

    override fun onDetachedFromWindow() {
        super.onDetachedFromWindow()
        mOnReLodListener = null // 清除监听器引用
    }

}

ViewModel层

BaseViewModel具体代码:
/**
 * @description: ViewModel基类
 * @author YL Chen
 * @date 2024/9/6 14:17
 * @version 1.0
 */
abstract class BaseViewModel : ViewModel(),MultiplyStateView.OnReLodListener {

    /**
     * 控制状态视图的LiveData
     */
    val mStateViewLiveData = MutableLiveData(ViewStateEnum.VIEW_LOADING)

    /**
     * 切换到UI线程
     * @param errorCallback SuspendFunction0<Unit> 错误回调
     * @param requestCall SuspendFunction0<Unit> 网络请求函数
     */
    fun launchUI(
        errorCallback: suspend (Int?, String?) -> Unit,
        requestCall: suspend () -> Unit
    ) {
        viewModelScope.launch(Dispatchers.Main) {
            //统一进行异常捕获
            safeApiCall(errorCallback, requestCall)
        }
    }

    /**
     * 对网络请求进行统一异常捕获
     * @param errorCallback SuspendFunction2<Int?, String?, Unit> 错误回调
     * @param requestCall SuspendFunction0<Unit> 网络请求函数
     * @return T? 网络请求成功数据
     */
    private suspend fun<T> safeApiCall(
        errorCallback: suspend (Int?, String?) -> Unit,
        requestCall: suspend () -> T?
    ): T? {
        try {
            //返回网络请求结果
            return requestCall()
        }catch (e: Exception){
            LogUtils.e(this@BaseViewModel,e.message.toString())
            e.printStackTrace()
            //统一异常处理
            //将异常转为ApiException
            val apiException = ExceptionHandler.handleException(e)
            if (apiException.errCode == ERROR.UNKNOW_HOST.code || apiException.errCode == ERROR.NETWORD_ERROR.code){
                changeStateView(ViewStateEnum.VIEW_NET_ERROR)
            }
            errorCallback(apiException.errCode,apiException.errMsg)
        }
        return null
    }

    /**
     * 更改状态视图的状态
     */
    fun changeStateView(
        state: ViewStateEnum
    ) {
        // 对参数进行校验
        when (state) {
            ViewStateEnum.VIEW_LOADING -> {
                mStateViewLiveData.postValue(ViewStateEnum.VIEW_LOADING)
            }

            ViewStateEnum.VIEW_EMPTY -> {
                mStateViewLiveData.postValue(ViewStateEnum.VIEW_EMPTY)
            }

            ViewStateEnum.VIEW_NET_ERROR -> {
                mStateViewLiveData.postValue(ViewStateEnum.VIEW_NET_ERROR)
            }

            ViewStateEnum.VIEW_LOAD_SUCCESS -> {
                mStateViewLiveData.postValue(ViewStateEnum.VIEW_LOAD_SUCCESS)
            }
            ViewStateEnum.VIEW_NONE -> {
                mStateViewLiveData.postValue(ViewStateEnum.VIEW_NONE)
            }
        }
    }

    //错误视图点击回调函数
    override fun onReLoad() {
        //调用方法由子类实现
        onReload()
    }

    //子类可实现此方法实现界面重新加载
    open fun onReload(){}
}
思路:

继承自ViewModel,实现MultiplyStateView.OnReLodListener接口,重写onReload()方法,当用户点击重新加载状态视图时,回调此方法

声明mStateViewLiveData成员变量,给View层观察监听

封装changeStateView()方法,用于在网络请求数据相关操作后进行手动调用,改变mStateViewLiveData变量的值,View基层对此变量进行观察监听,实现状态视图的实时切换。

onReload()方法:公开的方法,用户自己选择是否重新,不是MultiplyStateView.OnReLodListener强制重写的,而是自定义的,在强制重写的onReload中调用。

封装safeApiCall()方法,传入网络请求函数和错误回调函数,用于调用Modle层中的网络请求方法,并对返回结果进行统一的异常处理,如当网络异常时,调用ViewModel.changStateView(ViewStateEnum.VIEW_NET_ERROR)改变状态视图。

封装launchUI()方法,用于切换到UI线程,在其中调用safeApiCall()方法。

Model层

BaseBean具体代码:网络请求返回的基础数据Bean类(与接口返回的数据结构有关)
data class BaseBean<T>(val data: T,val errorCode: Int, val errorMsg: String)/**
 * 网络返回数据类
 * @param T
 * @property errorCode String 0:正常,非0异常
 * @property errorMsg String
 * @property data T
 * @constructor
 */

data class BaseResult<T>(val data: T,val errorCode: Int, val errorMsg: String, ) {
    fun isFailed(): Boolean {
        return errorCode != 0
    }
}
BaseRepository具体代码:网络请求基础仓库
/**
 * @description: 网络请求基础仓库
 * @author YL Chen
 * @date 2024/9/10 17:06
 * @version 1.0
 */
open class BaseRepository {
    /**
     * IO中处理请求,请求错误抛出自定义异常
     * @param requestCall SuspendFunction0<BaseResult<T>?>
     * @return T?
     */
    suspend fun <T> requestResponse(requestCall: suspend () -> BaseResult<T>?): T? {
        val result = withContext(Dispatchers.IO) {
            withTimeout(Constant.CONNECT_TIME_OUT * 1000) {
                requestCall()
            }
        } ?: return null
        LogUtils.e(this@BaseRepository,"result-->$result")

        if (result.isFailed()) {
            throw ApiException(result.errorCode, result.errorMsg)
        }
        return result.data
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2320388.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Mybatis的基础操作——03

写mybatis代码的方法有两种&#xff1a; 注解xml方式 本篇就介绍XML的方式 使用XML来配置映射语句能够实现复杂的SQL功能&#xff0c;也就是将sql语句写到XML配置文件中。 目录 一、配置XML文件的路径&#xff0c;在resources/mapper 的目录下 二、写持久层代码 1.添加mappe…

React:React主流组件库对比

1、Material-UI | 官网 | GitHub | GitHub Star: 94.8k Material-UI 是一个实现了 Google Material Design 规范的 React 组件库。 Material UI 包含了大量预构建的 Material Design 组件&#xff0c;覆盖导航、滑块、下拉菜单等各种常用组件&#xff0c;并都提供了高度的可定制…

python每日十题(6)

】函数定义&#xff1a;函数是指一组语句的集合通过一个名字&#xff08;函数名&#xff09;封装起来&#xff0c;要想执行这个函数&#xff0c;只需要调用其函数名即可。函数能提高应用的模块性和代码的重复利用率 在Python语言中&#xff0c;用关键字class来定义类 在Python语…

1.Go - Hello World

1.安装Go依赖 https://go.dev/dl/ 根据操作系统选择适合的依赖&#xff0c;比如windows&#xff1a; 2.配置环境变量 右键此电脑 - 属性 - 环境变量 PS&#xff1a; GOROOT&#xff1a;Go依赖路径&#xff1b; GOPATH&#xff1a;Go项目路径&#xff1b; …

优先队列 priority_queue详解

说到&#xff0c;priority_queue优先队列。必须先要了解啥是堆与运算符重载(我在下方有解释)。 否则只知皮毛&#xff0c;极易忘记寸步难行。 但在开头&#xff0c;还是简单的说下怎么用 首先&#xff0c;你需要调用 #include <queue> 在main函数中&#xff0c;声明…

《信息系统安全》(第一次上机实验报告)

实验一 &#xff1a;网络协议分析工具Wireshark 一 实验目的 学习使用网络协议分析工具Wireshark的方法&#xff0c;并用它来分析一些协议。 二实验原理 TCP/IP协议族中网络层、传输层、应用层相关重要协议原理。网络协议分析工具Wireshark的工作原理和基本使用规则。 三 实…

简要分析IPPROTO_TCP参数

IPPROTO_TCP是操作系统或网络编程中定义的一个 协议号常量&#xff0c;用于标识 传输控制协议&#xff08;TCP&#xff09;。其核心作用是 在传输层指定使用TCP协议&#xff0c;确保数据通过TCP的可靠传输机制进行通信。 一、定义与值 头文件&#xff1a;定义在<netinet/in.…

JavaScript与客户端开发

1、简介 简单的讲&#xff0c;JavaScript是一种脚本语言&#xff0c;为网站提供了一种在客户端运行程序的手段&#xff0c;通过它可以实现客户端数据验证、网页特效等功能。 JavaScript是一种基于对象和事件驱动&#xff08;不懂啥意思&#xff0c;暂不管它&#xff09;&…

基于CNN的FashionMNIST数据集识别5——GoogleNet模型

源码 import torch from torch import nn from torchsummary import summaryclass Inception(nn.Module):def __init__(self, in_channels, c1, c2, c3, c4):super().__init__()self.ReLu nn.ReLU()#路径1self.p1_1 nn.Conv2d(in_channelsin_channels, out_channelsc1, kern…

JVM垃圾回收笔记01-垃圾回收算法

文章目录 前言1. 如何判断对象可以回收1.1 引用计数法1.2 可达性分析算法查看根对象哪些对象可以作为 GC Root ?对象可以被回收&#xff0c;就代表一定会被回收吗&#xff1f; 1.3 引用类型1.强引用&#xff08;StrongReference&#xff09;2.软引用&#xff08;SoftReference…

【初探数据结构】树与二叉树

&#x1f4ac; 欢迎讨论&#xff1a;在阅读过程中有任何疑问&#xff0c;欢迎在评论区留言&#xff0c;我们一起交流学习&#xff01; &#x1f44d; 点赞、收藏与分享&#xff1a;如果你觉得这篇文章对你有帮助&#xff0c;记得点赞、收藏&#xff0c;并分享给更多对数据结构感…

蓝桥杯备考:二分答案之路标设置

最大距离&#xff0c;找最小空旷指数值&#xff0c;我们是很容易想到用二分的&#xff0c;我们再看看这个答案有没有二段性 是有这么个二段性的&#xff0c;我们只要二分就行了&#xff0c;但是二分的check函数是有点不好想的&#xff0c;我们枚举空旷值的时候&#xff0c;为了…

回调方法传参汇总

文章目录 0. 引入问题1. 父子组件传值1.1 父传子&#xff1a;props1.2 子传父&#xff1a;$emit1.3 双向绑定&#xff1a;v-model 2. 多个参数传递3. 父组件监听方法传递其他值3.1 $event3.2 箭头方法 4. 子组件传递多个参数&#xff0c;父组件传递本地参数4.1 箭头函数 … 扩…

XSS基础靶场练习

目录 1. 准备靶场 2. PASS 1. Level 1&#xff1a;无过滤 源码&#xff1a; 2. level2&#xff1a;转HTML实体 htmlspecialchars简介&#xff1a; 源码 PASS 3. level3:转HTML深入 源码&#xff1a; PASS 4. level4:过滤<> 源码&#xff1a; PASS: 5. level5:过滤on 源码…

Redis核心机制(一)

目录 Redis的特性 1.速度快 2.以键值对方式进行存储 3.丰富的功能 4.客户端语言多 5.持久化 6.主从复制 7.高可用和分布式 Redis使用场景 Redis核心机制——持久化 RDB bgsave执行流程 ​编辑 AOF AOF重写流程 3.混合持久化&#xff08;RDBAOF&#xff09; Red…

QGroupBox取消勾选时不禁用子控件

默认情况下&#xff0c;QGroupBox取消勾选会自动禁用子控件&#xff0c;如下图所示 那么如何实现取消勾选时不禁用子控件呢&#xff1f; 实现很简单&#xff0c;直接上代码了 connect(ui->groupBox, &QGroupBox::toggled, this, [](bool checked){if (checked false){…

MyBatis-Plus 自动填充:优雅实现创建/更新时间自动更新!

目录 一、什么是 MyBatis-Plus 自动填充&#xff1f; &#x1f914;二、自动填充的原理 ⚙️三、实际例子&#xff1a;创建时间和更新时间字段自动填充 ⏰四、注意事项 ⚠️五、总结 &#x1f389; &#x1f31f;我的其他文章也讲解的比较有趣&#x1f601;&#xff0c;如果喜欢…

canvas数据标注功能简单实现:矩形、圆形

背景说明 基于UI同学的设计&#xff0c;在市面上找不到刚刚好的数据标注工具&#xff0c;遂决定自行开发。目前需求是实现图片的矩形、圆形标注&#xff0c;并获取标注的坐标信息&#xff0c;使用canvas可以比较方便的实现该功能。 主要功能 选中图形&#xff0c;进行拖动 使…

【UI设计】一些好用的免费图标素材网站

阿里巴巴矢量图标库https://www.iconfont.cn/国内最大的矢量图标库之一&#xff0c;拥有 800 万 图标资源。特色功能包括团队协作、多端适配、定制化编辑等&#xff0c;适合企业级项目、电商设计、中文产品开发等场景。IconParkhttps://iconpark.oceanengine.com/home字节跳动…

ubuntu 解挂载时提示 “umount: /home/xx/Applications/yy: target is busy.”

问题如题所示&#xff0c;我挂载一个squanfs文件系统到指定目录&#xff0c;当我使用完后&#xff0c;准备解挂载时&#xff0c;提示umount: /home/xx/Applications/yy: target is busy.&#xff0c;具体的如图所示&#xff0c; 这种提示通常是表明这个路径的内容正在被某些进…