协程 baseViewModel和 BaseRepository 封装

baseViewModel 类 代码

package com.sum.network.viewmodel

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.sum.framework.log.LogUtil
import com.sum.network.error.ApiException
import com.sum.network.response.BaseResponse
import com.sum.network.error.ExceptionHandler
import com.sum.network.callback.IApiErrorCallback
import com.sum.network.error.ERROR
import com.sum.network.flow.requestFlow
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeout

/**
 * @date   2023/2/24 13:03
 * @desc   viewModel基类
 */
open class BaseViewModel : ViewModel() {
    /**
     * 运行在主线程中,可直接调用
     * @param errorBlock 错误回调
     * @param responseBlock 请求函数
     */
    fun launchUI(errorBlock: (Int?, String?) -> Unit, responseBlock: suspend () -> Unit) {
        viewModelScope.launch(Dispatchers.Main) {
            safeApiCall(errorBlock = errorBlock, responseBlock)
        }
    }

    /**
     * 需要运行在协程作用域中
     * @param errorBlock 错误回调
     * @param responseBlock 请求函数
     */
    suspend fun <T> safeApiCall(
        errorBlock: suspend (Int?, String?) -> Unit,
        responseBlock: suspend () -> T?
    ): T? {
        try {
            return responseBlock()
        } catch (e: Exception) {
            e.printStackTrace()
            LogUtil.e(e)
            val exception = ExceptionHandler.handleException(e)
            errorBlock(exception.errCode, exception.errMsg)
        }
        return null
    }

    /**
     * 不依赖BaseRepository,运行在主线程中,可直接调用
     * @param errorCall 错误回调
     * @param responseBlock 请求函数
     * @param successBlock 请求回调
     */
    fun <T> launchUIWithResult(
        responseBlock: suspend () -> BaseResponse<T>?,
        errorCall: IApiErrorCallback?,
        successBlock: (T?) -> Unit
    ) {
        viewModelScope.launch(Dispatchers.Main) {
            val result = safeApiCallWithResult(errorCall = errorCall, responseBlock)
            successBlock(result)
        }
    }

    /**
     * 不依赖BaseRepository,需要在作用域中运行
     * @param errorCall 错误回调
     * @param responseBlock 请求函数
     */
    suspend fun <T> safeApiCallWithResult(
        errorCall: IApiErrorCallback?,
        responseBlock: suspend () -> BaseResponse<T>?
    ): T? {
        try {
            val response = withContext(Dispatchers.IO) {
                withTimeout(10 * 1000) {
                    responseBlock()
                }
            } ?: return null

            if (response.isFailed()) {
                throw ApiException(response.errorCode, response.errorMsg)
            }
            return response.data
        } catch (e: Exception) {
            e.printStackTrace()
            LogUtil.e(e)
            val exception = ExceptionHandler.handleException(e)
            if (ERROR.UNLOGIN.code == exception.errCode) {
                errorCall?.onLoginFail(exception.errCode, exception.errMsg)
            } else {
                errorCall?.onError(exception.errCode, exception.errMsg)
            }
        }
        return null
    }

    /**
     * flow 运行在主线程中,可直接调用
     * @param errorCall 错误回调
     * @param requestCall 请求函数
     * @param showLoading 是否展示加载框
     * @param successBlock 请求结果
     */
    fun <T> launchFlow(
        errorCall: IApiErrorCallback? = null,
        requestCall: suspend () -> BaseResponse<T>?,
        showLoading: ((Boolean) -> Unit)? = null,
        successBlock: (T?) -> Unit
    ) {
        viewModelScope.launch(Dispatchers.Main) {
            val data = requestFlow(errorBlock = { code, error ->
                if (ERROR.UNLOGIN.code == code) {
                    errorCall?.onLoginFail(code, error)
                } else {
                    errorCall?.onError(code, error)
                }
            }, requestCall, showLoading)
            successBlock(data)
        }
    }
}

BaseRepository类代码 

package com.sum.network.repository

import com.sum.network.error.ApiException
import com.sum.network.response.BaseResponse
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeout

/**
 * @date   2023/2/23 23:31
 * @desc   基础仓库
 */
open class BaseRepository {

    /**
     * IO中处理请求
     */
    suspend fun <T> requestResponse(requestCall: suspend () -> BaseResponse<T>?): T? {
        val response = withContext(Dispatchers.IO) {
            withTimeout(10 * 1000) {
                requestCall()
            }
        } ?: return null

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

BaseResponse 类代码 
package com.sum.network.response

/**
 * @date   2023/2/24 13:10
 * @desc   通用数据类
 */
data class BaseResponse<out T>(
    val data: T?,
    val errorCode: Int = 0,//服务器状态码 这里0表示请求成功
    val errorMsg: String = ""//错误信息
) {

    /**
     * 判定接口返回是否正常
     */
    fun isFailed(): Boolean {
        return errorCode != 0
    }
}

在用到flow的封装,需要用到flow的扩展函数

FlowExtend.kt
package com.sum.network.flow

import com.sum.framework.log.LogUtil
import com.sum.network.error.ApiException
import com.sum.network.error.ExceptionHandler
import com.sum.network.response.BaseResponse
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.withTimeout

/**
 * @date   2023/4/12 22:47
 * @desc   flow请求拓展
 */

/**
 * 通过flow执行请求,需要在协程作用域中执行
 * @param errorBlock 错误回调
 * @param requestCall 执行的请求
 * @param showLoading 开启和关闭加载框
 * @return 请求结果
 */
suspend fun <T> requestFlow(
    errorBlock: ((Int?, String?) -> Unit)? = null,
    requestCall: suspend () -> BaseResponse<T>?,
    showLoading: ((Boolean) -> Unit)? = null
): T? {
    var data: T? = null
    val flow = requestFlowResponse(errorBlock, requestCall, showLoading)
    //7.调用collect获取emit()回调的结果,就是请求最后的结果
    flow.collect {
        data = it?.data
    }
    return data
}

/**
 * 通过flow执行请求,需要在协程作用域中执行
 * @param errorBlock 错误回调
 * @param requestCall 执行的请求
 * @param showLoading 开启和关闭加载框
 * @return Flow<BaseResponse<T>>
 */
suspend fun <T> requestFlowResponse(
    errorBlock: ((Int?, String?) -> Unit)? = null,
    requestCall: suspend () -> BaseResponse<T>?,
    showLoading: ((Boolean) -> Unit)? = null
): Flow<BaseResponse<T>?> {
    //1.执行请求
    val flow = flow {
        //设置超时时间
        val response = withTimeout(10 * 1000) {
            requestCall()
        }

        if (response?.isFailed() == true) {
            throw ApiException(response.errorCode, response.errorMsg)
        }
        //2.发送网络请求结果回调
        emit(response)
        //3.指定运行的线程,flow {}执行的线程
    }.flowOn(Dispatchers.IO)
            .onStart {
                //4.请求开始,展示加载框
                showLoading?.invoke(true)
            }
            //5.捕获异常
            .catch { e ->
                e.printStackTrace()
                LogUtil.e(e)
                val exception = ExceptionHandler.handleException(e)
                errorBlock?.invoke(exception.errCode, exception.errMsg)
            }
            //6.请求完成,包括成功和失败
            .onCompletion {
                showLoading?.invoke(false)
            }
    return flow
}

调用示例:

   运行在主线程中直接调用

HomeViewModel.kt
package com.sum.main.ui.home.viewmodel

import android.content.res.AssetManager
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.liveData
import androidx.lifecycle.viewModelScope
import com.sum.common.constant.FILE_VIDEO_LIST
import com.sum.common.model.ArticleList
import com.sum.common.model.Banner
import com.sum.common.model.ProjectSubInfo
import com.sum.framework.toast.TipsToast
import com.sum.main.repository.HomeRepository
import com.sum.common.model.ProjectTabItem
import com.sum.main.utils.ParseFileUtils
import com.sum.network.flow.requestFlow
import com.sum.network.manager.ApiManager
import com.sum.network.viewmodel.BaseViewModel
import com.sum.room.entity.VideoInfo
import com.sum.room.manager.VideoCacheManager
import kotlinx.coroutines.launch

/**
 * @date   2023/3/3 8:15
 * @desc   首页ViewModel
 */
class HomeViewModel : BaseViewModel() {
    val projectItemLiveData = MutableLiveData<MutableList<ProjectSubInfo>?>()
    val bannersLiveData = MutableLiveData<MutableList<Banner>?>()

    val homeRepository by lazy { HomeRepository() }

    /**
     * 首页banner
     */
    fun getBannerList(): LiveData<MutableList<Banner>?> {
//        launchUI(errorBlock = { code, errorMsg ->
//            TipsToast.showTips(errorMsg)
//            bannersLiveData.value = null
//        }) {
//            val data = homeRepository.getHomeBanner()
//            bannersLiveData.value = data
//        }
//        return bannersLiveData
        //通过flow来请求
        viewModelScope.launch {
            val data = requestFlow(requestCall = {
                ApiManager.api.getHomeBanner()
            }, errorBlock = { code, error ->
                TipsToast.showTips(error)
                bannersLiveData.value = null
            })
            bannersLiveData.value = data
        }
        return bannersLiveData
    }

    /**
     * 首页列表
     * @param page 页码
     */
    fun getHomeInfoList(page: Int): LiveData<ArticleList> {
        return liveData {
            val response = safeApiCall(errorBlock = { code, errorMsg ->
                TipsToast.showTips(errorMsg)
            }) {
                homeRepository.getHomeInfoList(page)
            }
            response?.let {
                emit(it)
            }
        }
    }

    /**
     * 首页Project tab
     */
    fun getProjectTab(): LiveData<MutableList<ProjectTabItem>?> {
        return liveData {
            val response = safeApiCall(errorBlock = { code, errorMsg ->
                TipsToast.showTips(errorMsg)
            }) {
                homeRepository.getProjectTab()
            }
            emit(response)
        }
    }

    /**
     * 获取项目列表数据
     * @param page
     * @param cid
     */
    fun getProjectList(page: Int, cid: Int): LiveData<MutableList<ProjectSubInfo>?> {
        launchUI(errorBlock = { code, errorMsg ->
            TipsToast.showTips(errorMsg)
            projectItemLiveData.value = null
        }) {
            val data = homeRepository.getProjectList(page, cid)
            projectItemLiveData.value = data?.datas
        }
        return projectItemLiveData
    }

    /**
     * 首页视频列表
     */
    fun getVideoList(assetManager: AssetManager): LiveData<MutableList<VideoInfo>?> {
        return liveData {
            val response = safeApiCall(errorBlock = { code, errorMsg ->
                TipsToast.showTips(errorMsg)
            }) {
                var list = homeRepository.getVideoListCache()
                //缓存为空则创建视频数据
                if (list.isNullOrEmpty()) {
                    list = ParseFileUtils.parseAssetsFile(assetManager, FILE_VIDEO_LIST)
                    VideoCacheManager.saveVideoList(list)
                }
                list
            }

            emit(response)
        }
    }

}
HomeRepository.kt
package com.sum.main.repository

import com.sum.common.model.ArticleList
import com.sum.common.model.Banner
import com.sum.common.model.ProjectSubList
import com.sum.network.manager.ApiManager
import com.sum.common.model.ProjectTabItem
import com.sum.network.repository.BaseRepository
import com.sum.room.entity.VideoInfo
import com.sum.room.manager.VideoCacheManager

/**
 * @date   2023/2/27 18:58
 * @desc   首页请求仓库
 */
class HomeRepository : BaseRepository() {
    /**
     * 首页Banner
     */
    suspend fun getHomeBanner(): MutableList<Banner>? {
        return requestResponse {
            ApiManager.api.getHomeBanner()
        }
    }

    /**
     * 首页列表
     * @param page 页码
     * @param pageSize 每页数量
     */
    suspend fun getHomeInfoList(page: Int): ArticleList? {
        return requestResponse {
            ApiManager.api.getHomeList(page, 20)
        }
    }

    /**
     * 项目tab
     */
    suspend fun getProjectTab(): MutableList<ProjectTabItem>? {
        return requestResponse {
            ApiManager.api.getProjectTab()
        }
    }

    /**
     * 项目列表
     * @param page
     * @param cid
     */
    suspend fun getProjectList(page: Int, cid: Int): ProjectSubList? {
        return requestResponse {
            ApiManager.api.getProjectList(page, cid)
        }
    }

    /**
     * 获取视频列表数据
     */
    suspend fun getVideoListCache(): MutableList<VideoInfo>? {
        return VideoCacheManager.getVideoList()
    }
}
HomeFragment.kt
package com.sum.main.ui.home

import android.graphics.Typeface
import android.os.Bundle
import android.util.SparseArray
import android.util.TypedValue
import android.view.View
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
import com.scwang.smart.refresh.layout.api.RefreshLayout
import com.scwang.smart.refresh.layout.listener.OnRefreshListener
import com.sum.common.model.ProjectTabItem
import com.sum.common.provider.SearchServiceProvider
import com.sum.framework.adapter.ViewPage2FragmentAdapter
import com.sum.framework.base.BaseMvvmFragment
import com.sum.framework.ext.gone
import com.sum.framework.ext.onClick
import com.sum.framework.ext.visible
import com.sum.framework.utils.getStringFromResource
import com.sum.main.R
import com.sum.main.databinding.FragmentHomeBinding
import com.sum.main.ui.home.viewmodel.HomeViewModel

/**
 * @date   2023/3/3 8:16
 * @desc   首页
 */
class HomeFragment : BaseMvvmFragment<FragmentHomeBinding, HomeViewModel>(), OnRefreshListener {

    private val mArrayTabFragments = SparseArray<Fragment>()

    private var mTabLayoutMediator: TabLayoutMediator? = null
    private var mFragmentAdapter: ViewPage2FragmentAdapter? = null
    private var mProjectTabs: MutableList<ProjectTabItem> = mutableListOf()

    override fun initView(view: View, savedInstanceState: Bundle?) {
        mBinding?.refreshLayout?.apply {
            autoRefresh()
            setEnableRefresh(true)
            setEnableLoadMore(false)
            setOnRefreshListener(this@HomeFragment)
        }
        mBinding?.ivSearch?.onClick {
            SearchServiceProvider.toSearch(requireContext())
        }
        initTab()
    }

    override fun onRefresh(refreshLayout: RefreshLayout) {
        refresh()
    }

    private fun refresh() {
        mViewModel.getBannerList().observe(this) { banners ->
            banners?.let {
                mBinding?.bannerHome?.visible()
                mBinding?.bannerHome?.setData(it)
            } ?: kotlin.run {
                mBinding?.bannerHome?.gone()
            }
            mBinding?.refreshLayout?.finishRefresh()
        }

        mViewModel.getProjectTab().observe(this) { tabs ->
            mProjectTabs =
                mProjectTabs.filter { it.name == getStringFromResource(R.string.home_tab_video_title) }.toMutableList()
            tabs?.forEachIndexed { index, item ->
                mProjectTabs.add(item)
                mArrayTabFragments.append(index + 1, HomeTabFragment.newInstance(tabs[index].id))
            }
            mFragmentAdapter?.setData(mArrayTabFragments)
            mFragmentAdapter?.notifyItemRangeChanged(1, mArrayTabFragments.size())

            // 解决 TabLayout 刷新数据后滚动到错误位置
            mBinding?.tabHome?.let {
                it.post { it.getTabAt(0)?.select() }
            }
        }
    }

    private fun initTab() {
        mArrayTabFragments.append(0, HomeVideoFragment())
        mProjectTabs.add(0, ProjectTabItem(id = 0, getStringFromResource(R.string.home_tab_video_title)))
        activity?.let {
            mFragmentAdapter = ViewPage2FragmentAdapter(childFragmentManager, lifecycle, mArrayTabFragments)
//            mFragmentAdapter = ViewPage2FragmentAdapter(it, mArrayTabFragments)
        }
        mBinding?.let {
            it.viewPager.adapter = mFragmentAdapter
            //可左右滑动
            it.viewPager.isUserInputEnabled = true
            //禁用预加载
            //需要注意是FragmentStateAdapter不会一直保持Fragment实例,在被destroy后,需要做好Fragment重建后回复数据的准备,这点可以结合ViewModel来进行配合使用。
            it.viewPager.offscreenPageLimit = mArrayTabFragments.size()

            mTabLayoutMediator = TabLayoutMediator(it.tabHome, it.viewPager) { tab: TabLayout.Tab, position: Int ->
                tab.text = mProjectTabs[position].name
            }
            //tabLayout和viewPager2关联起来
            mTabLayoutMediator?.attach()

            //增加tab选择监听
            it.tabHome.addOnTabSelectedListener(tabSelectedCall)
            //设置第一个tab效果
            val tabFirst = it.tabHome.getTabAt(0)
            setTabTextSize(tabFirst)
        }
    }

    /**
     * tab选择回调
     */
    private val tabSelectedCall = object : TabLayout.OnTabSelectedListener {
        override fun onTabSelected(tab: TabLayout.Tab?) {
            setTabTextSize(tab)
        }

        override fun onTabUnselected(tab: TabLayout.Tab?) {
            //非选中效果在xml中设置
            tab?.customView = null
        }

        override fun onTabReselected(tab: TabLayout.Tab?) {
        }
    }

    /**
     * 设置tab大小加粗效果
     */
    private fun setTabTextSize(tabFirst: TabLayout.Tab?) {
        TextView(requireContext()).apply {
            typeface = Typeface.DEFAULT_BOLD
            setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15f)
            setTextColor(ContextCompat.getColor(requireContext(), R.color.black))
        }.also {
            it.text = tabFirst?.text
            tabFirst?.customView = it
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        mTabLayoutMediator?.detach()
    }
}
SystemViewModel.kt
package com.sum.main.ui.system.viewmodel

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.liveData
import com.sum.common.model.SystemList
import com.sum.framework.toast.TipsToast
import com.sum.network.callback.IApiErrorCallback
import com.sum.network.manager.ApiManager
import com.sum.network.viewmodel.BaseViewModel

/**
 * @date   2023/3/3 8:19
 * @desc   体系ViewModel
 */
class SystemViewModel : BaseViewModel() {

    //错误无数据回调
    val errorListLiveData: MutableLiveData<String> = MutableLiveData()

    /**
     * 获取体系列表
     */
    fun getSystemList(): LiveData<MutableList<SystemList>> {
        return liveData {
            val data = safeApiCallWithResult(errorCall = object : IApiErrorCallback {
                override fun onError(code: Int?, error: String?) {
                    TipsToast.showTips(error)
                    errorListLiveData.value = error
                }
            }) {
                ApiManager.api.getSystemList()
            }
            data?.let {
                emit(it)
            } ?: kotlin.run {
                errorListLiveData.value = ""
            }
        }
    }

}
SystemFragment.kt
package com.sum.main.ui.system

import android.os.Bundle
import android.view.View
import androidx.recyclerview.widget.LinearLayoutManager
import com.sum.framework.base.BaseMvvmFragment
import com.sum.framework.decoration.NormalItemDecoration
import com.sum.framework.ext.toJson
import com.sum.framework.ext.visible
import com.sum.framework.utils.dpToPx
import com.sum.main.R
import com.sum.main.databinding.FragmentSystemBinding
import com.sum.main.ui.system.adapter.SystemAdapter
import com.sum.main.ui.system.viewmodel.SystemViewModel

/**
 * @date   2023/3/3 8:18
 * @desc   体系
 */
class SystemFragment : BaseMvvmFragment<FragmentSystemBinding, SystemViewModel>() {
    private lateinit var mAdapter: SystemAdapter

    override fun initView(view: View, savedInstanceState: Bundle?) {
        mAdapter = SystemAdapter()
        mBinding?.recyclerView?.apply {
            layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false)
            adapter = mAdapter
            addItemDecoration(NormalItemDecoration().apply {
                setBounds(left = dpToPx(8), top = dpToPx(10), right = dpToPx(8), bottom = dpToPx(10))
                setLastBottom(true)
            })
        }
        mAdapter.onItemClickListener = { view: View, position: Int ->
            val item = mAdapter.getItem(position)
            ArticleTabActivity.startIntent(requireContext(), item?.toJson(true))
        }
    }

    override fun initData() {
        showLoading()
        mViewModel.getSystemList().observe(this) {
            mAdapter.setData(it)
            dismissLoading()
        }
        mViewModel.errorListLiveData.observe(this) {
            //空数据视图
            mBinding?.viewEmptyData?.visible()
        }
    }
}
MyCollectViewModel.kt
package com.sum.user.collection

import android.content.Context
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.sum.common.model.ArticleInfo
import com.sum.framework.toast.TipsToast
import com.sum.network.callback.IApiErrorCallback
import com.sum.network.flow.requestFlow
import com.sum.network.manager.ApiManager
import com.sum.network.viewmodel.BaseViewModel
import kotlinx.coroutines.launch

/**
 * @date   2023/3/24 18:29
 * @desc   我的收藏
 */
class MyCollectViewModel : BaseViewModel() {
    var collectListLiveData = MutableLiveData<MutableList<ArticleInfo>?>()

    /**
     * 我的收藏列表
     * @param page  页码
     */
    fun getMyCollectList(page: Int) {
//        launchUIWithResult(errorCall = object : IApiErrorCallback {
//            override fun onError(code: Int?, error: String?) {
//                TipsToast.showTips(error)
//                collectListLiveData.value = null
//            }
//        }, responseBlock = {
//            ApiManager.api.getCollectList(page)
//        }) {
//            collectListLiveData.value = it?.datas
//        }

        viewModelScope.launch {
            val data = requestFlow(requestCall = {
                ApiManager.api.getCollectList(page)
            }, errorBlock = { code, error ->
                TipsToast.showTips(error)
                collectListLiveData.value = null
            })
            collectListLiveData.value = data?.datas
        }
    }

    /**
     * 收藏站内文章
     * @param id  文章id
     * @param originId 收藏之前的那篇文章本身的id
     */
    fun collectArticle(id: Int, originId: Int, showLoading: (Boolean) -> Unit): LiveData<Boolean?> {
//        launchUIWithResult(responseBlock = {
//            ApiManager.api.cancelMyCollect(id, originId)
//        }, errorCall = object : IApiErrorCallback {
//            override fun onError(code: Int?, error: String?) {
//                super.onError(code, error)
//                collectLiveData.value = null
//            }
//
//            override fun onLoginFail(code: Int?, error: String?) {
//                super.onLoginFail(code, error)
//                collectLiveData.value = null
//                LoginServiceProvider.login(context)
//            }
//        }) {
//            collectLiveData.value = true
//        }
//        return collectLiveData
        val collectLiveData: MutableLiveData<Boolean?> = MutableLiveData()

        launchFlow(errorCall = object : IApiErrorCallback {
            override fun onError(code: Int?, error: String?) {
                super.onError(code, error)
                collectLiveData.value = null
            }

            override fun onLoginFail(code: Int?, error: String?) {
                super.onLoginFail(code, error)
                collectLiveData.value = false
            }
        }, requestCall = {
            ApiManager.api.cancelMyCollect(id, originId)
        }, showLoading = showLoading) {
            collectLiveData.value = true
        }
        return collectLiveData
    }
}
MyCollectionActivity.kt
package com.sum.user.collection

import android.os.Bundle
import android.view.View
import androidx.recyclerview.widget.LinearLayoutManager
import com.alibaba.android.arouter.facade.annotation.Route
import com.scwang.smart.refresh.layout.api.RefreshLayout
import com.scwang.smart.refresh.layout.listener.OnLoadMoreListener
import com.scwang.smart.refresh.layout.listener.OnRefreshListener
import com.sum.common.R
import com.sum.common.constant.USER_ACTIVITY_COLLECTION
import com.sum.common.provider.LoginServiceProvider
import com.sum.common.provider.MainServiceProvider
import com.sum.framework.base.BaseMvvmActivity
import com.sum.framework.decoration.NormalItemDecoration
import com.sum.framework.ext.gone
import com.sum.framework.ext.visible
import com.sum.framework.toast.TipsToast
import com.sum.framework.utils.dpToPx
import com.sum.user.databinding.ActivityMyCollectListBinding

/**
 * @date   2023/3/24 18:26
 * @desc   我的收藏
 */
@Route(path = USER_ACTIVITY_COLLECTION)
class MyCollectionActivity : BaseMvvmActivity<ActivityMyCollectListBinding, MyCollectViewModel>(),
    OnRefreshListener, OnLoadMoreListener {
    private var mPage = 0
    private lateinit var mAdapter: MyCollectListAdapter
    override fun initView(savedInstanceState: Bundle?) {
        mBinding.refreshLayout.apply {
            setEnableRefresh(true)
            setEnableLoadMore(true)
            setOnRefreshListener(this@MyCollectionActivity)
            setOnLoadMoreListener(this@MyCollectionActivity)
            autoRefresh()
        }
        mAdapter = MyCollectListAdapter()
        val dp12 = dpToPx(12)
        mBinding.recyclerView.apply {
            layoutManager = LinearLayoutManager(this@MyCollectionActivity)
            adapter = mAdapter
            addItemDecoration(NormalItemDecoration().apply {
                setBounds(left = dp12, top = dp12, right = dp12, bottom = dp12)
                setLastBottom(true)
            })
        }
        mAdapter.onItemClickListener = { _: View, position: Int ->
            val item = mAdapter.getItem(position)
            if (item != null && !item.link.isNullOrEmpty()) {
                MainServiceProvider.toArticleDetail(
                    context = this,
                    url = item.link!!,
                    title = item.title ?: ""
                )
            }
        }

        mAdapter.onItemCancelCollectListener = { view: View, position: Int ->
            if (LoginServiceProvider.isLogin()) {
                cancelCollectArticle(position)
            } else {
                LoginServiceProvider.login(this)
            }
        }
    }

    /**
     * 取消收藏
     * @param position
     */
    private fun cancelCollectArticle(position: Int) {
        val item = mAdapter.getItem(position)
        item?.let {
            mViewModel.collectArticle(it.id, it.originId ?: -1) { showLoading ->
                if (showLoading) {
                    showLoading()
                } else {
                    dismissLoading()
                }
            }.observe(this) { result ->
                if (result == true) {
                    mAdapter.removeAt(position)
                    TipsToast.showTips(R.string.collect_cancel)
                } else if (result == false) {
                    LoginServiceProvider.login(this)
                }
            }
        }
    }

    override fun initData() {
        getMyCollectList()
        mViewModel.collectListLiveData.observe(this) {
            if (mPage == 0) {
                mAdapter.setData(it)
                if (it.isNullOrEmpty()) {
                    //空视图
                    mBinding.viewEmptyData.visible()
                } else {
                    mBinding.viewEmptyData.gone()
                }
                mBinding.refreshLayout.finishRefresh()
            } else {
                mAdapter.addAll(it)
                mBinding.refreshLayout.finishLoadMore()
            }
        }
    }

    private fun getMyCollectList() {
        mViewModel.getMyCollectList(mPage)
    }

    override fun onRefresh(refreshLayout: RefreshLayout) {
        mPage = 0
        getMyCollectList()
    }

    override fun onLoadMore(refreshLayout: RefreshLayout) {
        mPage++
        getMyCollectList()
    }

}
ApiException.kt
package com.sum.network.error

import java.io.IOException

/**
 * 结果异常类
 * 服务器非200状态,对应的异常
 */
open class ApiException : Exception {
    var errCode: Int
    var errMsg: String

    constructor(error: ERROR, e: Throwable? = null) : super(e) {
        errCode = error.code
        errMsg = error.errMsg
    }

    constructor(code: Int, msg: String, e: Throwable? = null) : super(e) {
        this.errCode = code
        this.errMsg = msg
    }
}

/**
 * 无网络连接异常
 */
class NoNetWorkException : IOException {
    var errCode: Int
    var errMsg: String

    constructor(error: ERROR, e: Throwable? = null) : super(e) {
        errCode = error.code
        errMsg = error.errMsg
    }
}
ExceptionHandler.kt
package com.sum.network.error

import android.net.ParseException
import com.google.gson.JsonParseException
import com.google.gson.stream.MalformedJsonException
import com.sum.framework.toast.TipsToast
import org.json.JSONException
import retrofit2.HttpException
import java.net.ConnectException

/**
 * 统一错误处理工具类
 */
object ExceptionHandler {

    fun handleException(e: Throwable): ApiException {

        val ex: ApiException
        if (e is ApiException) {
            ex = ApiException(e.errCode, e.errMsg, e)
            if (ex.errCode == ERROR.UNLOGIN.code){
                //登录失效
            }
        } else if (e is NoNetWorkException) {
            TipsToast.showTips("网络异常,请尝试刷新")
            ex = ApiException(ERROR.NETWORD_ERROR, e)
        } else if (e is HttpException) {
            ex = when (e.code()) {
                ERROR.UNAUTHORIZED.code -> ApiException(ERROR.UNAUTHORIZED, e)
                ERROR.FORBIDDEN.code -> ApiException(ERROR.FORBIDDEN, e)
                ERROR.NOT_FOUND.code -> ApiException(ERROR.NOT_FOUND, e)
                ERROR.REQUEST_TIMEOUT.code -> ApiException(ERROR.REQUEST_TIMEOUT, e)
                ERROR.GATEWAY_TIMEOUT.code -> ApiException(ERROR.GATEWAY_TIMEOUT, e)
                ERROR.INTERNAL_SERVER_ERROR.code -> ApiException(ERROR.INTERNAL_SERVER_ERROR, e)
                ERROR.BAD_GATEWAY.code -> ApiException(ERROR.BAD_GATEWAY, e)
                ERROR.SERVICE_UNAVAILABLE.code -> ApiException(ERROR.SERVICE_UNAVAILABLE, e)
                else -> ApiException(e.code(), e.message(), e)
            }
        } else if (e is JsonParseException
                || e is JSONException
                || e is ParseException
                || e is MalformedJsonException
        ) {
            ex = ApiException(ERROR.PARSE_ERROR, e)
        } else if (e is ConnectException) {
            ex = ApiException(ERROR.NETWORD_ERROR, e)
        } else if (e is javax.net.ssl.SSLException) {
            ex = ApiException(ERROR.SSL_ERROR, e)
        } else if (e is java.net.SocketException) {
            ex = ApiException(ERROR.TIMEOUT_ERROR, e)
        } else if (e is java.net.SocketTimeoutException) {
            ex = ApiException(ERROR.TIMEOUT_ERROR, e)
        } else if (e is java.net.UnknownHostException) {
            ex = ApiException(ERROR.UNKNOW_HOST, e)
        } else {
            ex = if (!e.message.isNullOrEmpty()) ApiException(1000, e.message!!, e)
            else ApiException(ERROR.UNKNOWN, e)
        }
        return ex
    }
}

ERROR.kt
package com.sum.network.error

enum class ERROR(val code: Int, val errMsg: String) {

    /**
     * 对应HTTP的状态码
     */
    /**
     * 当前请求需要用户验证
     */
    UNAUTHORIZED(401, "当前请求需要用户验证"),

    /**
     * 资源不可用。服务器理解客户的请求,但拒绝处理它
     */
    FORBIDDEN(403, "资源不可用"),

    /**
     * 无法找到指定位置的资源
     */
    NOT_FOUND(404, "无法找到指定位置的资源"),

    /**
     * 在服务器许可的等待时间内,客户一直没有发出任何请求
     */
    REQUEST_TIMEOUT(408, "请求超时"),

    /**
     * 服务器遇到了意料不到的情况,不能完成客户的请求
     */
    INTERNAL_SERVER_ERROR(500, "服务器错误"),

    /**
     * 服务器作为网关或者代理时,为了完成请求访问下一个服务器,但该服务器返回了非法的应答
     */
    BAD_GATEWAY(502, "非法应答"),

    /**
     * 服务器由于维护或者负载过重未能应答
     */
    SERVICE_UNAVAILABLE(503, "服务器未能应答"),

    /**
     * 由作为代理或网关的服务器使用,表示不能及时地从远程服务器获得应答
     */
    GATEWAY_TIMEOUT(504, "服务器未能应答"),

    /**
     * 未知错误
     */
    UNKNOWN(1000, "未知错误"),

    /**
     * 解析错误
     */
    PARSE_ERROR(1001, "解析错误"),

    /**
     * 网络错误
     */
    NETWORD_ERROR(1002, "网络异常,请尝试刷新"),

    /**
     * 协议出错
     */
    HTTP_ERROR(1003, "404 Not Found"),

    /**
     * 证书出错
     */
    SSL_ERROR(1004, "证书出错"),

    /**
     * 连接超时
     */
    TIMEOUT_ERROR(1006, "连接超时"),

    /**
     * 未登录
     */
    UNLOGIN(-1001, "未登录"),

    /**
     * 未知Host
     */
    UNKNOW_HOST(1007, "未知Host");
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值