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");
}