大家好。日常写客户端业务需求,大家一定对下面这种场景深恶痛绝:
每当我们要做一个表单提交、点赞、取消订单之类的操作,为了处理网络请求,ViewModel 里通常会堆满这样的样板代码:
kotlin
fun submitData() {
_uiState.update { it.copy(isLoading = true) }
viewModelScope.launch {
try {
val response = apiService.submit(...)
_uiState.update { it.copy(isLoading = false, isSuccess = true) }
} catch (e: Exception) {
// 弱网、断网、超时全在这里...
_uiState.update { it.copy(isLoading = false) }
showToast("网络连接失败,请稍后重试!")
}
}
}
这种"前台阻塞等待网络响应"的范式有几个致命痛点:
- 体验极差:用户在弱网下只能看着 Loading 圈发呆,最后等来一个报错。
- 状态爆炸 :你需要维护繁琐的
Loading、Success、Error状态机。 - 容易丢数据:如果还没请求成功,App 就被系统杀了,用户的操作就彻底丢失了。
微信发消息、或者一些大厂的高频交互,为什么在地铁里没网的时候也感觉那么流畅?因为它们在底层使用了类似后端的 发件箱模式 (Outbox Pattern) 。
核心思路就是:不要让 UI 等网络!数据直接在本地数据库落盘,UI 瞬间返回成功。剩下的事,交给后台守护进程去重试。
为了在团队的 KMP 和 Android 项目里优雅地实现这个模式,并且不污染现有的网络代码,我手搓了一个极其轻量级的离线同步引擎开源库 ------ Syncox。
什么是 Syncox?
Syncox 是一个专为 Kotlin Multiplatform (KMP) 和 Android 打造的客户端离线同步引擎。
它的核心设计理念是 "零侵入" 和 "发后即忘 (Fire-and-Forget)" 。你不需要重构你现有的 Ktor 或 Retrofit 接口,只需要加上一个注解,就能让你的 App 拥有大厂级别的断网容灾能力。
看看接入有多简单
不用学什么复杂的架构概念,只需要三步,你就能体会到干掉 try-catch 的爽快感:
第一步:把数据包一层 (Action)
定义一个数据类实现 SyncoxAction 接口,这一步是为了告诉引擎:"我要存什么数据"以及"这个数据该找哪个接口发"。
kotlin
@Serializable
data class CreatePostRequest(val title: String, val body: String, val userId: Int)
class CreatePostAction(request: CreatePostRequest) : SyncoxAction {
override val actionType: String = "CREATE_POST" // 路由寻址 Key
override val payloadJson: String = Json.encodeToString(request) // 自动转 JSON 落盘
}
第二步:给原有的网络接口加个注解
这就是 Syncox 最爽的地方。得益于 KSP 编译期代码生成技术 ,你只需要在真实的请求函数上贴个 @OfflineSync。业务代码里再也不用手动调用这个函数了,KSP 会自动生成一张零反射的安全路由表,让底层的后台进程自己去找它!
kotlin
// 这个方法交由底层引擎自动调度,报错了也会自动重试
@OfflineSync(action = "CREATE_POST")
suspend fun executeCreatePost(payloadJson: String): NetworkResult {
return try {
val request = Json.decodeFromString<CreatePostRequest>(payloadJson)
val response = httpClient.post("posts", request)
if (response.status.isSuccess()) {
NetworkResult.Success // 成功:引擎会自动从 SQLite 擦除记录
} else {
NetworkResult.Failure(isFatal = false) // 服务端临时错:引擎自动触发退避重试
}
} catch (e: Exception) {
NetworkResult.Failure(isFatal = false, error = e) // 断网:引擎进入静默等待
}
}
第三步:在 ViewModel 里一键发送
现在,你的 ViewModel 干净得只剩下一行代码。没有 Loading,没有 try-catch。
kotlin
// 瞬间落盘本地数据库,耗时 < 1ms。UI 线程完全不会卡顿!
Syncox.enqueue(CreatePostAction(request))
// UI 层如果想展示"正在后台同步中",直接拿这个 Flow 绑到 Compose 上就行
val pendingCountFlow: Flow<Int> = Syncox.observePendingCount()
总结与开源地址
如果你也厌倦了在 ViewModel 里处理无穷无尽的网络异常,或者你想让你的 App 在弱网环境下表现得更"高级"、更丝滑,非常建议你试试这套发件箱模式。
目前项目已经完全开源,支持 KMP 的跨端项目(Android、iOS、Desktop)和纯 Android 项目:
👉 GitHub 地址 : github.com/SamiuZhong/...
项目刚刚起步,还在积极迭代中。欢迎各位大佬前来审阅、提 Issue 或者 PR,如果觉得思路对你有启发,求个 Star ⭐ 支持一下!