3 Star 33 Fork 16

抓猪 / kmvvm

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
Apache-2.0

技术要点

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

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

  • 全局配置网络加载错误页面,并支持重新加载数据

  • 全局配置列表空页面

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

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

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

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

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

架构图

最低兼容:21

release版本

snapshot版本

Gitee

Github

CHANGE LOG

Gradle

1. 在根目录的build.gradle中添加

plugins {
    id 'org.jetbrains.kotlin.jvm' version "1.8.0" apply false
    id 'org.jetbrains.kotlin.multiplatform' version '1.8.0' apply false
    id 'org.jetbrains.kotlin.plugin.serialization' version '1.8.0' apply false
    id 'com.google.devtools.ksp' version '1.8.21-1.0.11' apply false
}

2. 在app的build.gradle中添加

plugins {
    id 'kotlinx-serialization'

    id 'com.google.devtools.ksp'
}

3. 在app的gradle.properties中添加

  • 停用ksp增量编译
ksp.incremental=false

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

buildFeatures {
    viewBinding = true
}

5. 添加依赖

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

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

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

6. 使用快照版本的,需要添加如下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 Int
     */
    @TextStyle
    fun getTitleTextStyle(): 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

    /**
     * 网络请求失败的显示页面
     * @param layoutInflater LayoutInflater
     * @param any Any BaseActivity or BaseFragment
     * @return ViewBinding
     */
    fun getFailedBinding(layoutInflater: LayoutInflater, any: Any): ViewBinding?

    /**
     * 失败页面,需要重新加载的点击事件的id
     * @return Int
     */
    @IdRes
    fun onFailedReloadClickId(): Int
    
    /**
     * 刷新每页加载个数
     * @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 getTitleTextStyle(): Int {
      return TextStyle.BOLD
    }

    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 getFailedBinding(layoutInflater: LayoutInflater, any: Any): ViewBinding? {
        return when (any) {
            is BaseActivity<*> -> {
                LayoutActivityErrorBinding.inflate(layoutInflater)
            }
            is BaseFragment<*> -> {
                LayoutFragmentErrorBinding.inflate(layoutInflater)
            }
            else -> {
                null
            }
        }
    }

    override fun onFailedReloadClickId(): Int {
        return R.id.failed_reload
    }

    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("第二个图标按钮点击生效")
}

2.5 加载失败页面

  • 网络请求失败可展示失败页面,并有刷新按钮可以重新加载数据
  • 在lifecycleLoadingView扩展函数中将showFailedView设置为true,数据请求失败了,就会显示失败页面
  • onFailedReload的闭包中再次调用网络请求的接口,就可以重新再加载数据了
/**
 * 加载失败后展示失败页面,点击自定义失败页面的刷新按钮,重新请求数据
 * @param autoFirstLoad Boolean 第一次是否自动加载
 * @param block [@kotlin.ExtensionFunctionType] Function1<View, Unit>
 */
fun onFailedReload(autoFirstLoad: Boolean = true, block: View.() -> Unit){
    .....
}
override fun initFlow() {
    onFailedReload {
        loadingViewError(bodyBinding.root)
    }
}

fun loadingViewError(v: View) {
    viewModel.loadingViewError().lifecycleLoadingView(this, showFailedView = true) {
        snackBar(this)
    }
}

3. Fragment

  • 使用MVVM的继承BaseVMFragment
  • 不使用MVVM的继承BaseFragment

3.1 提示框

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

使用示例

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

3.2 加载失败页面

  • 网络请求失败可展示失败页面,并有刷新按钮可以重新加载数据
  • 在lifecycleLoadingView扩展函数中将showFailedView设置为true,数据请求失败了,就会显示失败页面
  • onFailedReload的闭包中再次调用网络请求的接口,就可以重新再加载数据了
/**
 * 加载失败后展示失败页面,点击自定义失败页面的刷新按钮,重新请求数据
 * @param autoFirstLoad Boolean 第一次是否自动加载
 * @param block [@kotlin.ExtensionFunctionType] Function1<View, Unit>
 */
fun onFailedReload(autoFirstLoad: Boolean = true, block: View.() -> Unit){
    .....
}
override fun initFlow() {
    onFailedReload {
        loadBanners()
    }
}

private fun loadBanners(){
    viewModel.queryBanners().lifecycleLoadingDialog(this, true) {
        val images = mutableListOf<String>()
        this.forEach {
            images.add(it.imagePath)
        }
        bodyBinding.banner.run {
            setImages(images)
            start()
        }
    }
}

4. RecycleView

  • Adapter可以继承RecycleAdapter来使用,RecycleAdapter使用了ViewBanding,只需要实现以下两个个方法

使用示例

class UserAdapter(iPageControl: IPageControl) :
    RecyclerAdapter<User, ItemUserBinding>(iPageControl) {
    
    override fun viewBinding(parent: ViewGroup): ItemUserBinding {
        return ItemUserBinding.inflate(LayoutInflater.from(parent.context), parent, false)
    }

    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: BaseView, refreshLayoutWrapper: RefreshRecyclerView )
  • 不带loading的网络请求封装
    • lifecycle( base: BaseView, showFailedView: Boolean = false, errorCallback: ((t: Throwable) -> Unit)? = null, callback: T.() -> Unit )
    • lifecycle( baseViewModel: BaseViewModel, showFailedView: Boolean = false, errorCallback: ((t: Throwable) -> Unit)? = null, callback: T.() -> Unit )
  • 带loadingView的网络请求封装
    • lifecycleLoadingDialog( base: BaseView, showFailedView: Boolean = false, errorCallback: ((t: Throwable) -> Unit)? = null, callback: T.() -> Unit )
    • lifecycleLoadingDialog( baseViewModel: BaseViewModel, showFailedView: Boolean = false, errorCallback: ((t: Throwable) -> Unit)? = null, callback: T.() -> Unit )
  • 带loadingDialog的网络请求封装
    • lifecycleLoadingView( base: BaseView, showFailedView: Boolean = false, errorCallback: ((t: Throwable) -> Unit)? = null, callback: T.() -> Unit )
    • lifecycleLoadingView( baseViewModel: BaseViewModel, showFailedView: Boolean = false, 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.ServiceApi class * {*;}
-keep public class **.databinding.*Binding {*;}
-keep class **.*_Compiler {*;}
#序列化混淆
-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 - 协程

serialization-序列化

其他

QQ交流群(228014675)

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

Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

简介

Kotlin+Flow+Retrofit+OKHttp+ViewBanding+ViewModel+LiveData封装的MVVM框架,支持协程方式访问网络请求,kotlin最新的编译时框架ksp,可定义全局加载失败页面,并支持全局刷新数据的点击时间,还可定义全局列表的空页面 展开 收起
Android
Apache-2.0
取消

发行版 (61)

全部

贡献者

全部

近期动态

加载更多
不能加载更多了
Android
1
https://gitee.com/catchpig/kmvvm.git
git@gitee.com:catchpig/kmvvm.git
catchpig
kmvvm
kmvvm
master

搜索帮助

14c37bed 8189591 565d56ea 8189591