Kotlin+Flow+Retrofit+OKHttp+KAPT+ViewBanding+ViewModel的MVVM框架,支持协程

Gitee

Github

技术要点

  • 支持Flow+Retrofit+OkHttp实现链式http请求

  • 支持Rxjava+Retrofit+OkHttp实现链式http请求

  • 封装基类:BaseActivity、BaseVMActivity、BaseFragment、BaseVMFragment、RecycleAdapter、BaseViewModel

  • 引入LifeCycle,将ViewModel和Activity的生命周期绑定在一起

  • 使用startup库将在Application中初始化移至到KotlinMvvmInitializer中,从而不用封装BaseApplication

  • APT(编译时注解)封装注解:Title、OnClickFirstDrawable、OnClickFirstText、OnClickSecondDrawable、OnClickSecondText、Prefs、PrefsField、StatusBar、FlowError、Adapter、GlobalConfig、ServiceApi

  • 封装工具扩展类:CalendarExt、ContextExt、DateExt、EditTextExt、GsonExt、RxJavaExt、StringExt、SnackbarExt

最低兼容:21

Gradle

1. 在app的build.gradle的添加

apply plugin: 'kotlin-kapt' // 使用 kapt 注解处理工具
apply plugin: 'kotlinx-serialization' //序列化

2. 在app的build.gradle的android下添加

buildFeatures {
    viewBinding = true
}

3. 添加依赖

implementation "io.github.catchpig.kmvvm:mvvm:last_version"
kapt "io.github.catchpig.kmvvm:compiler:last_version"

需要使用下载功能,请单独添加如下依赖

implementation "io.github.catchpig.kmvvm:download:last_version"

4. 使用快照版本的,需要添加如下maven地址

maven {
    // mavenCentral的快照地址
    url 'https://s01.oss.sonatype.org/content/repositories/snapshots/'
}

使用

1. 配置全部参数

interface IGlobalConfig {
    /**
     * 标题栏高度
     * @return Int
     */
    @DimenRes
    fun getTitleHeight(): Int

    /**
     * 标题栏的返回按钮资源
     * @return Int
     */
    @DrawableRes
    fun getTitleBackIcon(): Int

    /**
     * 标题栏背景颜色
     * @return Int
     */
    @ColorRes
    fun getTitleBackground(): Int

    /**
     * 标题栏文本颜色
     * @return Int
     */
    @ColorRes
    fun getTitleTextColor(): Int

    /**
     * 标题栏下方是否需要横线
     * @return Boolean
     */
    fun isShowTitleLine(): Boolean

    /**
     * 标题栏下方横线颜色
     * @return Int
     */
    @ColorRes
    fun getTitleLineColor(): Int

    /**
     * loading的颜色
     * @return Int
     */
    @ColorRes
    fun getLoadingColor(): Int

    /**
     * loading的背景颜色
     * @return Int
     */
    @ColorRes
    fun getLoadingBackground(): Int

    /**
     * RecyclerView的空页面ViewBinding
     * @param parent ViewGroup
     * @return ViewBinding
     */
    fun getRecyclerEmptyBanding(parent: ViewGroup): ViewBinding

    /**
     * 刷新每页加载个数
     * @return Int
     */
    fun getPageSize(): Int

    /**
     * 刷新起始页的index(有些后台设置的0,有些后台设置1)
     */
    fun getStartPageIndex(): Int
}

使用示例:

@GlobalConfig
class MvvmGlobalConfig : IGlobalConfig {
    override fun getTitleHeight(): Int {
        return R.dimen.title_bar_height
    }

    override fun getTitleBackIcon(): Int {
        return R.drawable.back_black
    }

    override fun getTitleBackground(): Int {
        return R.color.colorPrimary
    }

    override fun getTitleTextColor(): Int {
        return R.color.white
    }

    override fun isShowTitleLine(): Boolean {
        return true
    }

    override fun getTitleLineColor(): Int {
        return R.color.color_black
    }

    override fun getLoadingColor(): Int {
        return R.color.color_black
    }

    override fun getLoadingBackground(): Int {
        return R.color.white
    }

    override fun getRecyclerEmptyBanding(parent: ViewGroup): ViewBinding {
        return LayoutEmptyBinding.inflate(LayoutInflater.from(parent.context), parent, false)
    }

    override fun getPageSize(): Int {
        return 16
    }

    override fun getStartPageIndex(): Int {
        return 1
    }
}

2. Activity

  • 使用MVVM的继承BaseVMActivity
  • 不使用MVVM的继承BaseActivity’
2.1 标题注解使用

使用示例

Title其他注解参数,请看下方注解详情

//设置标题的文字
@Title(R.string.child_title)
class ChildActivity : BaseVMActivity<ActivityChildBinding, ChildViewModel>() 

如果标题栏文字要根据接口显示不同的文字,也有接口设置

class ChildActivity : BaseVMActivity<ActivityChildBinding, ChildViewModel>() {
    @OnClickFirstDrawable(R.drawable.more)
    fun clickFirstDrawable(v: View) {
        updateTitle("更改标题")
    }
}
2.2 状态栏注解使用

使用示例

StatusBar其他注解参数,请看下方注解详情

//弃用注解
@StatusBar(hide = true)
class FullScreenActivity : BaseActivity<ActivityFullScreenBinding>()
2.3 标题右侧文字或图标按钮注解使用

使用示例

注解修饰的方法只能可以带View参数,也可以不带View参数,看自身的需求

@Title(R.string.child_title)
class ChildActivity : BaseVMActivity<ActivityChildBinding, ChildViewModel>() {
    @OnClickFirstDrawable(R.drawable.more)
    fun clickFirstDrawable(v: View) {
        SnackbarManager.show(bodyBinding.root, "第一个图标按钮点击生效")
        updateTitle("nihao")
    }

    @OnClickFirstText(R.string.more)
    fun clickFirstText() {
        SnackbarManager.show(bodyBinding.root, "第一个文字按钮点击生效")
        updateTitle("12354")
    }

    @OnClickSecondDrawable(R.drawable.more)
    fun clickSecondDrawable(v: View) {
        SnackbarManager.show(bodyBinding.root, "第二个图标按钮点击生效")
        updateTitle("nihao")
    }

    @OnClickSecondText(R.string.more)
    fun clickSecondText() {
        SnackbarManager.show(bodyBinding.root, "第二个文字按钮点击生效")
        updateTitle("12354")
    }
}
2.4 提示框
  • Android 11 之后,Toast已经不支持自定义Toast,原生的Toast是很难看的
  • 本框架使用SnackBar做提示框

使用示例

@OnClickSecondDrawable(R.drawable.more)
fun clickSecondDrawable(v: View) {
    snackBar("第二个图标按钮点击生效")
}

在这里插入图片描述

3. Fragment

  • 使用MVVM的继承BaseVMFragment
  • 不使用MVVM的继承BaseFragment
3.1 提示框
  • Android 11 之后,Toast已经不支持自定义Toast,原生的Toast是很难看的
  • 本框架使用SnackBar做提示框

使用示例

snackbar.setOnClickListener {
    snackBar("提示框")
}

在这里插入图片描述

4. RecycleView

  • Adapter可以继承RecycleAdapter来使用,并在类上添加注解Adapter
    ,RecycleAdapter使用了ViewBanding,只需要实现以下一个方法

使用示例

@Adapter
class UserAdapter(iPageControl: IPageControl) :
    RecyclerAdapter<User, ItemUserBinding>(iPageControl) {

    override fun bindViewHolder(holder: CommonViewHolder<ItemUserBinding>, m: User, position: Int) {
        holder.viewBanding {
            name.text = m.name
        }
    }
}

5.刷新分页控件(RefreshRecyclerView)

  • RefreshRecyclerView集成了RefreshLayoutWrapper+RecyclerView

不用关心分页的逻辑,分页的刷新逻辑实现都在RefreshLayoutWrapper

  • 只需要设置LayoutManager和RecyclerAdapter,提供了setLayoutManager和setAdapter方法
  • 在获取到数据的时候调用updateData方法
  • 获取数据失败的时候调用updateError方法
  • 如果使用了lifecycleRefresh方法,updateData方法和updateError方法都不用关心
  • 提供自定义属性recycler_background(设置RecyclerView的背景色)
<declare-styleable name="RefreshRecyclerView">
    <attr name="recycler_background" format="color" />
</declare-styleable>

使用示例


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent"
              android:layout_height="match_parent" android:orientation="vertical">

    <TextView android:layout_width="match_parent" android:layout_height="50dp"
              android:background="@color/colorPrimary" android:gravity="center" android:text="文章"
              android:textColor="@color/color_white"/>

    <com.catchpig.mvvm.widget.refresh.RefreshRecyclerView android:id="@+id/refresh"
                                                          android:layout_width="match_parent"
                                                          android:layout_height="match_parent"
                                                          app:recycler_background="#445467">

    </com.catchpig.mvvm.widget.refresh.RefreshRecyclerView>
</LinearLayout>
bodyBinding.refresh.run {
    setOnRefreshLoadMoreListener { nextPageIndex ->
        viewModel.queryArticles(nextPageIndex).lifecycleRefresh(this@ArticleFragment,this)
    }
}

6. 网络请求

6.1只需要是接口类上加上注解ServiceApi,并使用NetManager.getService()获取对应的接口类
  • 如果rxJava为true,必须要引入RxJava的依赖包和adapter-rxjava3依赖包

    implementation("com.squareup.retrofit2:adapter-rxjava3:$retrofit2_version")
    
        //rxjava3
    implementation "io.reactivex.rxjava3:rxjava:$rxjava_version"
    implementation "io.reactivex.rxjava3:rxandroid:$rxandroid_version"
    

使用示例

@ServiceApi(
    baseUrl = "https://www.wanandroid.com/",
    responseConverter = ResponseBodyConverter::class,
    interceptors = [RequestInterceptor::class],
    debugInterceptors = [OkHttpProfilerInterceptor::class],
    rxJava = true
)
interface WanAndroidService {
    @GET("banner/json")
    suspend fun banner(): List<Banner>
}
object WanAndroidRepository {
    private val wanAndroidService = NetManager.getService(WanAndroidService::class.java)
    fun getBanners(): Flow<MutableList<Banner>> {
        //这里如果用flowOf的话,方法上面必须加上suspend关键字
        return flow {
            emit(wanAndroidService.queryBanner())
        }
    }
}
class IndexViewModel : BaseViewModel() {
    fun queryBanners(): Flow<MutableList<Banner>> {
        return WanAndroidRepository.getBanners()
    }
}
//Activity或者Fragment
viewModel.queryBanners().lifecycle(this) {
    val images = mutableListOf<String>()
    this.forEach {
        images.add(it.imagePath)
    }
    bodyBinding.banner.run {
        setImages(images)
        start()
    }
}
6.2 Response转换器
6.2.1 一般Response发返回结果会是如下
{
  code: "SUCCESS",
  errorMsg: "成功",
  data: ...
}
6.2.2 在code返回SUCEESSD的时候, 我们在Retrofit的Api接口里面只想拿到data的数据做返回,我们想在Converter里面处理掉code返回错误码的逻辑,就可以继承BaseResponseBodyConverter,内部已经实现了将response转化为data的逻辑

代码示例

class ResponseBodyConverter :
    BaseResponseBodyConverter() {
    override fun getResultClass(): KClass<out BaseResponseData<JsonElement>> {
        return Result::class
    }

    override fun handlerErrorCode(errorCode: String, msg: String): Exception {
        return NullPointerException()
    }
}
6.2.3 再将实现了BaseResponseBodyConverter的类加到ServiceApi注解的responseConverter属性上
6.2.4 如果想直接拿response的结果作为网络请求的返回值,可以直接将SerializationResponseBodyConverter加到ServiceApi注解的responseConverter属性上
6.3 FlowExt中扩展了网络请求方法(带lifecycleScope)
  • 刷新+RecycleView的网络请求封装
    • lifecycleRefresh(
      base: BaseVMFragment<*, *>, refreshLayoutWrapper: RefreshRecyclerView
      )
    • lifecycleRefresh(
      base: BaseVMActivity<*, *>, refreshLayoutWrapper: RefreshRecyclerView
      )
  • 不带loading的网络请求封装
    • lifecycle(
      base: BaseVMFragment<*, *>,
      errorCallback: ((t: Throwable) -> Unit)? = null,
      callback: T.() -> Unit
      )
    • lifecycle(
      base: BaseVMActivity<*, *>,
      errorCallback: ((t: Throwable) -> Unit)? = null,
      callback: T.() -> Unit
      )
  • 带loadingView的网络请求封装
    • lifecycleLoadingDialog(
      base: BaseVMFragment<*, *>,
      errorCallback: ((t: Throwable) -> Unit)? = null,
      callback: T.() -> Unit
      )
    • lifecycleLoadingDialog(
      base: BaseVMActivity<*, *>,
      errorCallback: ((t: Throwable) -> Unit)? = null,
      callback: T.() -> Unit
      )
  • 带loadingDialog的网络请求封装
    • lifecycleLoadingView(
      base: BaseVMFragment<*, *>,
      errorCallback: ((t: Throwable) -> Unit)? = null,
      callback: T.() -> Unit
      )
      • lifecycleLoadingView(
        base: BaseVMActivity<*, *>,
        errorCallback: ((t: Throwable) -> Unit)? = null,
        callback: T.() -> Unit
        )

7. 日志

  • 不需要在Application中初始化,因为LogUtils已经在KotlinMvvmInitializer中初始化了
  • 可以使用LogUtils.getInstance().i,LogUtils.getInstance().d等打印日志(不建议)
  • 也可以使用LogExt的扩展方法打印日志(建议)

8. 注解使用

9. 文件下载器

10. 工具库

混淆

-keep class com.catchpig.annotation.enums.**
-keep class com.google.android.material.snackbar.Snackbar {*;}
-keep @com.catchpig.annotation.Adapter class * {*;}
-keep @com.catchpig.annotation.ServiceApi class * {*;}
-keep public class **.databinding.*Binding {*;}
-keep class **.*_Compiler {*;}
-keep class com.gyf.immersionbar.* {*;}
-dontwarn com.gyf.immersionbar.**
#序列化混淆
-if @kotlinx.serialization.Serializable class **
-keepclassmembers class <1> {
static <1>$Companion Companion;
}
# Keep `serializer()` on companion objects (both default and named) of serializable classes.
-if @kotlinx.serialization.Serializable class ** {
static **$* *;
}
-keepclassmembers class <1>$<3> {
kotlinx.serialization.KSerializer serializer(...);
}
# Keep `INSTANCE.serializer()` of serializable objects.
-if @kotlinx.serialization.Serializable class ** {
public static ** INSTANCE;
}
-keepclassmembers class <1> {
public static <1> INSTANCE;
kotlinx.serialization.KSerializer serializer(...);
}
# @Serializable and @Polymorphic are used at runtime for polymorphic serialization.
-keepattributes RuntimeVisibleAnnotations,AnnotationDefault

第三方库

SmartRefreshLayout-刷新控件

Immersionbar-状态栏

RxJava3

RxAndroid

OkHttp

Retrofit

Gson

kotlinpoet - kt代码生成工具

AndroidUtilKTX - 工具类

LoadingView - Loading动画

coroutines - 协程

欢迎大家在issue上提出问题,我这边会不定时的看issue,解决问题

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,我来回答你的问题。 首先,Kotlin 是一种基于 JVM 的静态类型编程语言,它的语法简洁易懂,支持函数式编程和面向对象编程。 协程Kotlin 中的一种轻量级线程,可以实现异步编程和并发执行。Retrofit 是一款网络请求库,它可以帮助我们轻松地实现网络请求和数据解析。MVVM 是一种软件架构模式,可以将应用程序分为三个部分:模型、视图和视图模型。 下面是一个基于 Kotlin + 协程 + Retrofit + MVVM 的网络请求的优雅实现: 1. 定义 API 接口 首先定义 API 接口,使用 Retrofit 注解来描述请求方法和参数。 ```kotlin interface ApiService { @GET("api/news") suspend fun getNews(@Query("category") category: String): NewsResponse } ``` 2. 创建数据模型 根据 API 接口的返回数据,我们可以创建一个数据模型。 ```kotlin data class News(val title: String, val content: String) data class NewsResponse(val code: Int, val message: String, val newsList: List<News>) ``` 3. 创建 ViewModel ViewModel 是连接数据模型和视图的中间层,它处理数据逻辑并提供可观察的数据。 ```kotlin class NewsViewModel : ViewModel() { private val _newsList = MutableLiveData<List<News>>() val newsList: LiveData<List<News>> = _newsList fun loadNews(category: String) { viewModelScope.launch { val response = retrofit.create(ApiService::class.java).getNews(category) if (response.code == 200) { _newsList.value = response.newsList } } } } ``` 4. 创建视图 视图负责展示数据,并与用户交互。 ```kotlin class NewsActivity : AppCompatActivity() { private val viewModel by viewModels<NewsViewModel>() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_news) viewModel.newsList.observe(this, Observer { newsList -> // 更新视图 }) viewModel.loadNews("tech") } } ``` 通过使用 Kotlin + 协程 + Retrofit + MVVM,我们可以实现优雅地网络请求,代码简洁易懂,逻辑清晰。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值