# BaseProject **Repository Path**: lhc542/base-project ## Basic Information - **Project Name**: BaseProject - **Description**: kotlin项目中的一些经验分享 - **Primary Language**: Kotlin - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2023-04-22 - **Last Updated**: 2023-07-13 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 介绍 kotlin项目中的一些经验分享 ## RxHttp扩展 ```kotlin fun loadGameInfo() { //开启一个协程 viewModelScope.launch { // 简化网络请求,请求失败result == null callGet(Url.GAME_CURRENT)?.let { result -> //具体逻辑 } callGet(Url.GAME_CURRENT){ //可以在这里处理异常,默认是toast显示错误信息 Toaster.show(it.msg) showError() }?.let {any -> //具体逻辑 } } } ``` callGet是顶层函数可在任意kotlin类中调用,具体实现如下[地址](https://gitee.com/lhc542/base-project/blob/master/common/src/main/java/com/orange/common/net/ktx/RxHttp.kt) ```kotlin // 参数默认值也减少了重载方法 suspend inline fun callGet( path: String, params: Map = mapOf(), cacheMode: CacheMode = CacheMode.ONLY_NETWORK, converter: IConverter = GsonConverter.create(), noinline onCatch: ((Throwable) -> Unit) = { Toaster.show(it.msg) } ): T? { return RxHttp.get(path) .setConverter(converter) .addAll(params) .setCacheMode(cacheMode) .toAwaitResponse() .tryAwait{ if (!rxHttpGlobalCatch(it)) { onCatch.invoke(it) } } } /** * 全局异常处理 */ var rxHttpGlobalCatch :(t: Throwable) -> Boolean = {false} ``` 全局异常 ```kotlin rxHttpGlobalCatch = { t: Throwable -> t.run { LogUtils.e("rxhttp 请求异常:$message") when (code) { //账号已在别处登录 2003 -> { LiveEventBus.get(Constants.Events.LOGIN_ERROR).post(null) true } else -> { // 没有处理的异常 false } } } } ``` 处理单个请求异常 ```kotlin val result1 = callGet(Url.GAME_CURRENT){ throwable -> //可以在这里处理异常,默认是toast显示错误信息 } ``` 在协程中处理串行和并行任务变得更简单,逻辑更清晰,简单举个列子: ```kotlin //串行任务 viewModelScope.launch { //先执行登录 //<-- main线程 val loginResult = deviceLogin() //<-- io线程 //....这里可以一些其他处理 //<-- 回到main线程 //再获取用户信息 val userInfo = loadUserInfo() //<-- io线程 } ``` ```kotlin //并行任务 //↓ main val userInfoAsync = async { loadUserInfo() } //↓ io线程 val settingAsync = async { loadSetting() } //↓ io线程 ↓ io线程 //当执行到async代码块的时候就已经开始执行了 //↓ main //等待两个任务的执行结果 val user = userInfoAsync.await() val setting = settingAsync.await() ``` > **备注**: 没有依赖RxJava,如需要操作符可以使用kotlin自带的flow > [DeepLinksHelper](https://gitee.com/lhc542/base-project/blob/master/app/src/main/java/com/base/project/deeplinks/DeepLinksHelper.kt)有回调函数转挂起函数的实践 ## MMKV扩展 简单演示一下效果[查看文件](https://gitee.com/lhc542/base-project/blob/master/common/src/main/java/com/orange/common/ktx/MMKV.kt) ```kotlin var inviteId by mmkvString() var userName by mmkvString("默认值") fun test(){ inviteId = "设置值" //这里等同于 MMKVUtils.put("inviteId", "设置值") val string = inviteId //这里等同于 MMKVUtils.getString("inviteId", "") inviteId.isEmpty() } ``` 通过by委托属性,可以在属性执行set,get方法时增加mmkv读写操作 ```kotlin fun mmkvInt(default: Int = 0) = MMKVProperty({ MMKVUtils.getInt(it, default) }, { MMKVUtils.put(first, second) }) fun mmkvString(default: String = "") = MMKVProperty({ MMKVUtils.getString(it, default) }, { MMKVUtils.put(first, second) }) ......略 class MMKVProperty( private val decode: (String) -> V, private val encode: Pair.() -> Boolean ) : ReadWriteProperty { private var cache: V? = null override fun getValue(thisRef: Any, property: KProperty<*>): V { cache ?: decode(property.name).also { cache = it } LogUtils.d("读取mmkv ${property.name} = $cache") return cache!! } override fun setValue(thisRef: Any, property: KProperty<*>, value: V) { LogUtils.d("保存mmkv ${property.name} = $value") if (encode(property.name to value)) { cache = value } } } ``` 还有一个很常用的属性委托用途,获取ViewModel ```kotlin val viewModel: MainViewModel by viewModels() ``` viewModels是activity和fragment的扩展函数 ## 获取Lifecycle 有一个官方的扩展函数可以从view中向上查找到最近的Lifecycle ```kotlin View.findViewTreeLifecycleOwner()?.lifecycle ``` > **备注**:官方的扩展函数[地址](https://developer.android.com/kotlin/ktx#groovy) 再对[Lifecycle扩展](https://gitee.com/lhc542/base-project/blob/master/common/src/main/java/com/orange/common/ktx/Lifecycle.kt)一下就能很简单的在onDestroy释放资源了 ```kotlin view.findViewTreeLifecycleOwner()?.lifecycle?.doOnDestroy { view.clearAnimation() } ``` ## dp转px 以往都要用一个工具类或者方法来将dp转化成px值,现在可通过扩展属性实现具体可[查看文件](https://gitee.com/lhc542/base-project/blob/master/common/src/main/java/com/orange/common/ktx/Dimensions.kt) ```kotlin //dp转px var width:Number = 10.dp //px转dp width = 10.pxToDp() ``` ## 点击(防抖动/防重复点击) 通过扩展函数增加view支持防止重复点击的方法[查看文件](https://gitee.com/lhc542/base-project/blob/master/common/src/main/java/com/orange/common/ktx/View.kt) ```kotlin // 设置全局间隔 2000ms intervalTimeDefault = 2000 // 防止连续点击 默认间隔500ms binding.space.onClick { //具体操作 } // 设置间隔1000ms binding.space.onClick(1000) { } ``` 这里推荐两个扩展库,可以挑选觉得好用的复制到自己项目中 [Longan](https://github.com/DylanCaiCoding/Longan) [AndroidKTX](https://github.com/li-xiaojun/AndroidKTX) ## 第三方库推荐 首推RecyclerView扩展库[BRV](https://github.com/liangjingkanji/BRV/blob/master/README.md),支持databinding代码示例和说明都很齐全 弹窗库[XPopup](https://github.com/li-xiaojun/XPopup),简单封装下支持连续弹窗,简化了调用流程可以参考下[查看文件](https://gitee.com/lhc542/base-project/blob/master/common/src/main/java/com/orange/common/popup/BasePopup.kt): ```kotlin //单个弹窗 LoadingPopup().show() //连续弹窗,dismiss 后自动显示下一个 LoadingPopup().addShow() LoadingPopup().addShow() //额外设置 这一步也可以放在内部initView时执行 LoadingPopup().build { dismissOnBackPressed(false) // 按返回键是否关闭弹窗 dismissOnTouchOutside(false) // 点击外部是否关闭弹窗 hasShadowBg(false) setPopupCallback(object :SimpleCallback(){ override fun onDismiss(popupView: BasePopupView?) { } }) }.addShow() class LoadingPopup : BasePopup() { override fun getLayoutId() = R.layout.common_loading_popup override fun initView() { build { hasShadowBg(false) isDestroyOnDismiss(false) } } } ```