Kotlin使用Coroutine+ViewModel+retrofit构建一个网络请求框架

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值