Kotlin使用Coroutine+ViewModel+retrofit构建一个网络请求框架
公司里的老代码用的网络请求框架技术都比较老,为了快速搭建一个网络请求框架,提高工作效率,记录一下用jetpack相关内容搭建一个网络请求框架。
dependencies {
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
def roomVersion = "2.4.1"
implementation "androidx.room:room-runtime:$roomVersion"
annotationProcessor "androidx.room:room-compiler:$roomVersion"
kapt("androidx.room:room-compiler:$roomVersion")
implementation "androidx.room:room-ktx:$roomVersion"
implementation "androidx.multidex:multidex:2.0.1"
// =============第三方库========================
//Retrofit网络请求
implementation "com.squareup.retrofit2:retrofit:2.7.0"
implementation "com.squareup.retrofit2:converter-gson:2.7.0"
// OkHttp
implementation "com.squareup.okhttp3:okhttp:4.2.2"
//retrofit的log日志
implementation 'com.squareup.okhttp3:logging-interceptor:3.5.0'
//Kotlin Coroutines 协程
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2"
}
该工程是随便新建的工程,工程中还有Room的使用,所以贴出了所有添加的依赖,重点依然是记录网络框架的搭建。
ApiService
interface ApiService {
@GET("wxarticle/chapters/json")
suspend fun getWXArticle() : ArticleData
}
ApiFactory
object ApiFactory {
private val mClient : OkHttpClient by lazy { newClient() }
fun <T> createService(baseUrl:String, clazz: Class<T>): T =
Retrofit.Builder().baseUrl(baseUrl).client(mClient)
.addConverterFactory(GsonConverterFactory.create(GsonBuilder().serializeNulls().create()))
.build().create(clazz)
private fun newClient() = OkHttpClient.Builder().apply{
connectTimeout(30,TimeUnit.SECONDS)
readTimeout(10,TimeUnit.SECONDS)
writeTimeout(10,TimeUnit.SECONDS)
addInterceptor(HttpLoggingInterceptor(HttpLog()).setLevel(HttpLoggingInterceptor.Level.BODY))
}.build()
class HttpLog : HttpLoggingInterceptor.Logger {
override fun log(message: String) {
Log.d("HttpLogInfo", message)
}
}
}
NetworkService
object NetworkService {
private const val BASE_URL = "https://www.wanandroid.com/"
val api by lazy { ApiFactory.createService(BASE_URL, ApiService::class.java) }
}
Repository
object Repository {
private suspend fun <T : BaseBean> preprocessData(baseBean: T, context: Context? = null): T =
if (baseBean.errorCode == 0) {// 成功
// 返回数据
baseBean
} else {// 失败
// 抛出接口异常
throw ApiException(baseBean.errorCode, baseBean.errorMsg)
}
suspend fun getWXArticle(): ArticleData =
NetworkService.api.getWXArticle().let {
preprocessData(it)
}
}
common文件下工具类
ApiException
class ApiException(val errorCode: Int,val msg: String):Throwable(msg)
ExceptionUtil
object ExceptionUtil {
/**
* 处理异常,toast提示错误信息
*/
fun catchException(e: Throwable) {
e.printStackTrace()
when (e) {
is HttpException -> {
catchHttpException(e.code())
return
}
is SocketTimeoutException -> showToast(
R.string.common_error_net_time_out
)
is UnknownHostException, is NetworkErrorException -> showToast(
R.string.common_error_net
)
is MalformedJsonException, is JsonSyntaxException -> showToast(
R.string.common_error_server_json
)
// 接口异常
is ApiException -> showToast(
e.msg,
e.errorCode
)
else -> showToast(
"${
MyApplication.instance.getString(
R.string.common_error_do_something_fail
)}:${e::class.java.name}"
)
}
}
/**
* 处理网络异常
*/
fun catchHttpException(errorCode: Int) {
if (errorCode in 200 until 300) return// 成功code则不处理
showToast(
catchHttpExceptionCode(
errorCode
), errorCode
)
}
/**
* toast提示
*/
private fun showToast(@StringRes errorMsg: Int, errorCode: Int = -1) {
showToast(
MyApplication.instance.getString(
errorMsg
), errorCode
)
}
/**
* toast提示
*/
private fun showToast(errorMsg: String, errorCode: Int = -1) {
if (errorCode == -1) {
ToastUtils.showShort(errorMsg)
} else {
ToastUtils.showShort("$errorCode:$errorMsg")
}
}
/**
* 处理网络异常
*/
private fun catchHttpExceptionCode(errorCode: Int): Int = when (errorCode) {
in 500..600 -> R.string.common_error_server
in 400 until 500 -> R.string.common_error_request
else -> R.string.common_error_request
}
}
string内容
<string name="common_error_net">网络异常,请检查网络连接!</string>
<string name="common_error_net_time_out">网络超时</string>
<string name="common_error_do_something_fail">操作异常</string>
<string name="common_error_request">请求错误</string>
<string name="common_error_server">服务器错误</string>
<string name="common_error_server_json">服务器错误:Json格式错误</string>
ToastUtils
object ToastUtils {
fun showShort(msg: String){
Toast.makeText(MyApplication.instance,msg,Toast.LENGTH_SHORT).show()
}
}
扩展viewmodel方法
fun ViewModel.launch(
block : suspend CoroutineScope.() -> Unit,
onError: (e:Throwable) -> Unit,
onComplete : () -> Unit = {}
){
viewModelScope.launch(
CoroutineExceptionHandler { _, throwable ->
run{
ExceptionUtil.catchException(throwable)
onError(throwable)
}
}
) {
try {
block.invoke(this)
}finally {
onComplete()
}
}
}
BaseViewModel
abstract class BaseViewModel : ViewModel(){
val loadState = MutableLiveData<LoadState>()
}
新建bean文件下,包含三个文件
ArticleData非固定,根据需求新建,此处只为展示例子
class ArticleData : BaseBean() {
var data = arrayListOf<Chapters>()
}
class Chapters{
// children: []
// courseId: 13
// id: 408
// name: "鸿洋"
// order: 190000
// parentChapterId: 407
// userControlSetTop: false
// visible: 1
var courseId = ""
var id = ""
var name = ""
var order = 0
}
BaseBean
该类格式根据平台返回的结构改变
open class BaseBean{
var errorCode:Int = -1
var errorMsg:String = ""
}
LoadState
/**
* 加载状态
* @author ssq
* sealed 关键字表示此类仅内部继承
*/
sealed class LoadState(val msg: String) {
/**
* 加载中
*/
class Loading(msg: String = ""): LoadState(msg)
/**
* 成功
*/
class Success(msg: String = ""): LoadState(msg)
/**
* 失败
*/
class Fail(msg: String = ""): LoadState(msg)
}
总体结构如图所示
具体使用
MainViewModel
class MainViewModel : BaseViewModel() {
val data = MutableLiveData<ArticleData>()
fun getData() = launch({
loadState.value = LoadState.Loading()
data.value = Repository.getWXArticle()
loadState.value = LoadState.Success()
},{
loadState.value = LoadState.Fail()
})
}
MainFragment
class MainFragment : Fragment() {
companion object {
fun newInstance() = MainFragment()
}
private lateinit var viewModel: MainViewModel
private var binding: MainFragmentBinding? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = MainFragmentBinding.inflate(inflater, container, false)
return binding!!.root
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
binding?.btGetData?.setOnClickListener {
viewModel.getData()
}
activity?.let { it ->
viewModel.data.observe(it, Observer { showdata(it) })
viewModel.loadState.observe(it, Observer { changeLoadState(it) })
}
}
private fun changeLoadState(loadState: LoadState){
binding?.pbProgress?.visibility = when(loadState){
is LoadState.Loading -> {
binding?.tvData?.text = ""
View.VISIBLE
}
else -> View.GONE
}
}
private fun showdata(data: ArticleData){
binding?.tvData?.text= "id: ${data.data[0].id} name: ${data.data[0].name}"
}
}
补充MyApplication,根据需求决定是否需要
class MyApplication : MultiDexApplication(){
companion object{
lateinit var instance:MyApplication
var isDebugMode = false
}
override fun onCreate() {
super.onCreate()
instance = this
if("com.example.com.viewmodeltest" == getProcessName(this, android.os.Process.myPid())){
initDebug()
}
}
private fun initDebug() {
isDebugMode = instance.applicationInfo != null && instance.applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE != 0
}
private fun getProcessName(cxt: Context, pid: Int): String {
//获取ActivityManager对象
val am = cxt.getSystemService(Context.ACTIVITY_SERVICE)
if (am is ActivityManager) {
//在运行的进程
val runningApps = am.runningAppProcesses
for (processInfo in runningApps) {
if (processInfo.pid == pid) {
return processInfo.processName
}
}
}
return ""
}
}
xml很简单,一个button,一个progress,一个TextView
参考资料网址:https://blog.csdn.net/NJP_NJP/article/details/103524778
https://github.com/sange93/MVVMDemo