reactive-mvvm-android 架构全景图:响应式 MVVM 的 Clean Architecture 分层设计一次看懂
reactive-mvvm-android 架构全景图响应式 MVVM 的 Clean Architecture 分层设计一次看懂【免费下载链接】reactive-mvvm-androidMy way to MVVM using KotlinX Coroutines and Android>项目地址: https://gitcode.com/gh_mirrors/re/reactive-mvvm-androidreactive-mvvm-android 是一个用MVVM Clean Architecture实现的 Android 漫画搜索应用核心亮点在于用 KotlinX Coroutines 的 Flow 构建完整的响应式数据流并借助 Android DataBinding 把 ViewModel 状态零胶水地渲染到界面。本文将从一张全景图出发带你逐层拆解这套响应式 MVVM 的分层设计适合刚接触 Android 架构的新手对照学习。一、项目定位一个小 App 撑起完整架构 别看它只是个搜索漫画封面的小应用五脏俱全业务搜索 ComicVine 开放 API展示漫画卷Volume列表点击查看详情缓存搜索过的关键词和结果存进 Room 数据库二次搜索直接走本地架构MVVM 负责 UI 与业务解耦Clean Architecture 负责业务与框架解耦技术组件承担职责KotlinX Coroutines Flow响应式异步通信的血液Android DataBinding LiveData状态驱动 UI 自动刷新Room本地缓存DAO 直接返回 FlowRetrofit OkHttp网络层接口方法直接suspend化Koin轻量依赖注入Arrow (Either)函数式错误处理Kotest Turbine单元测试见app/src/test/二、架构全景图一次看懂四层结构 ️整个app/src/main/java/es/ffgiraldez/comicsearch/目录按 Clean Architecture 划分为四个同心层依赖方向永远指向内层┌─────────────────────────────────────────────────────┐ │ UI 层 (query/ui/、detail/、navigation/) │ │ Activity · 自定义 BindingAdapter · 布局 XML │ │ 只做展示不写任何业务逻辑 │ └──────────────────┬──────────────────────────────────┘ │ DataBinding 自动观察 ┌──────────────────▼──────────────────────────────────┐ │ Presentation 层 (query/*/presentation/) │ │ SuspendQueryStateViewModel基类 │ │ SuspendSearchViewModel / SuspendSuggestionViewModel │ │ 产出 QueryViewState 状态机Idle/Loading/Result/Error│ └──────────────────┬──────────────────────────────────┘ │ 只依赖仓库接口不关心实现 ┌──────────────────▼──────────────────────────────────┐ │ Domain 层 (comics/domain/) │ │ 纯 Kotlin 实体Volume / Query / ComicError │ │ 零 Android 依赖是架构的心脏 │ └──────────────────┬──────────────────────────────────┘ │ 仓库实现位于 Data 层反向满足 Domain 契约 ┌──────────────────▼──────────────────────────────────┐ │ Data 层 (comics/data/) │ │ SuspendComicRepository本地优先策略 │ │ ├── network/ Retrofit 接口ComicVineApi.kt │ │ └── storage/ Room DAO 数据库ComicDao.kt 等 │ └─────────────────────────────────────────────────────┘各层对应的核心目录层目录关键文件UIquery/ui/、query/search/ui/QueryActivity.kt、SearchBindingAdapters.ktPresentationquery/base/presentation/SuspendQueryStateViewModel.kt、QueryViewState.ktDomaincomics/domain/Entities.ktDatacomics/data/SuspendComicRepository.kt、ComicDataSources.kt基础设施comics/di/、platform/、navigation/comicModule.kt、Navigator.kt三、响应式数据流从输入框到界面的单向流动 这是整个项目的灵魂。用户在搜索框输入时数据沿一条纯响应式链路流动全程没有回调、没有 Handler输入捕获基类SuspendQueryStateViewModel持有一个MutableLiveDataString作为查询输入通过query.asFlow()把它变成 Flow 流见SuspendQueryStateViewModel.kt状态转换子类如SuspendSearchViewModel.kt用flatMapLatest把词流行切换为结果流——输入一变旧请求自动取消天然防抖防竞态错误建模仓库返回EitherComicError, ListT用 Arrow 把网络失败/结果为空建模为类型而不是异常QueryViewState.kt中的toViewState()完成转换状态外发onStart先发出Idle/Loading再流式发出Result/Error最终asLiveData()交还给 LiveDataUI 自动刷新DataBinding 观察到state变化通过自定义 BindingAdapter 自动更新列表、进度条、错误文案——Activity 里几乎零逻辑代码一句话总结Flow 负责怎么算ViewState 负责算什么状态DataBinding 负责怎么显示。三者各司其职这就是响应式 MVVM的完整含义。四、Domain 层只有 3 个类的纯 Kotlin 核心 ❤️打开 Entities.kt整个领域模型只有三个定义Volume漫画卷标题、作者、封面Query一次搜索词ComicErrorsealed classNetworkError/EmptyResultsError为什么这么重要Domain 层不 import 任何 Android、Retrofit、Room 包。这意味着它可以在纯 JVM 上单测不需要 RobolectricUI 和数据层都围着这几个纯数据类转边界清晰换掉整个网络或存储实现Domain 一行不用改这就是 Clean Architecture 的依赖倒置精髓内层定义契约外层实现契约。五、Data 层Repository 的本地优先缓存策略 SuspendComicRepository.kt 是 Data 层的中枢它把 ComicDataSources.kt 中定义的本地/远程两个数据源接口组合起来策略非常经典findByTerm(term) ├─ 先问本地Room DAO 返回 Flow查这个关键词搜过没有 ├─ 搜过 → 直接回放本地结果命中空结果也算错误态提示无结果 └─ 没搜过 → suspend 请求远端 → 成功后写回本地 → 再流式发出几个值得抄的设计细节整个仓库就是一条 Flow 管道flatMapConcat把查本地 → 拉远端 → 存本地串成一条链消费方只管订阅DAO 直接返回 FlowComicDao.kt 里的Query方法签名是Flow...数据一变UI 自动跟着变无需手动刷新错误在源头就被类型化Either.catch包裹网络调用异常在进入上层之前就已变成ComicError.network()UI 层永远不会收到未捕获异常一个抽象基类两个具体仓库SuspendComicRepositoryT被搜索Volume和搜索建议Suggestion两个场景复用避免重复逻辑六、Presentation 层ViewModel 基类 状态机 这一层有两个模板方法设计是项目最可复用的部分① 基类模板SuspendQueryStateViewModel接收一个转换器函数(FlowString) - FlowQueryViewStateT子类只需注入转换器即可获得完整的query输入与state输出。搜索和建议两个功能因此共享 80% 代码。② 状态机模板QueryViewState是 sealed class恰好四个状态状态触发时机UI 表现Idle初始什么都不显示Loading请求发出进度条可见Result拿到数据列表更新Error网络失败/空结果错误文案sealed class 逼着 UI 层穷尽所有分支不存在未处理状态——这是比回调地狱可靠得多的状态管理方式。七、UI 层BindingAdapter 如何做到无胶水渲染 ️以 SearchBindingAdapters.kt 为例作者用BindingAdapter把状态 → 控件的映射封装成四个小函数RecyclerView.bindStateData(...)列表随Result数据自动提交FrameLayout.bindStateVisibility(...)错误容器随错误态显隐TextView.bindErrorText(...)错误文案人性化转换ProgressBar.bindProgress(...)加载动画随Loading态开关配合 ViewBinding.kt 里的gone(Boolean)扩展布局文件app/src/main/res/layout/query_activity.xml里只剩声明式绑定QueryActivity.kt几乎没有任何状态处理代码。界面变成了状态的投影——这正是响应式 UI 的终极形态。八、依赖注入与可测性Koin Kotest 双保险 ✅注入comicModule.kt 用 Koin 装配 Retrofit 客户端与 Room 数据库搜索/建议各自还有独立 DI 模块query/search/di/SearchModule.kt替换实现只需改模块不动业务代码导航navigation/目录把 Activity 跳转抽象成Screen密封类 NavigatorUI 层不直接startActivity测试app/src/test/下用 Kotest Turbine 写了完整的规格测试——测试文件验证对象SuspendComicRepositoryShould.kt仓库的本地优先策略配 fake 数据源SearchViewModelSpec.kt/SuggestionViewModelSpec.ktViewModel 状态流用 Turbine 断言 Flow 事件SearchBindingAdapterSpec.kt/SuggestionBindingAdapterSpec.kt绑定适配器的 UI 映射测试能写得这么顺正是因为前面每一层都遵守了依赖内层契约的约定。九、新手学习路线按这个顺序读代码最省力 先读 Entities.kt——3 个数据类看懂领域边界再读 QueryViewState.kt——理解四态状态机接着读 SuspendSearchViewModel.kt——18 行代码看完整条响应式链路然后读 SuspendComicRepository.kt——Flow 组合 缓存策略最后读 SearchBindingAdapters.kt——状态如何变成界面总结这套架构教会我们的 5 件事#设计点可迁移的实践1Domain 层纯 Kotlin业务核心永远不依赖框架可测性来自边界2数据源接口定义在数据层、注入仓库依赖倒置让先本地后远端可插拔3全链路 Flow flatMapLatest用流编排异步输入变化自动取消旧任务4sealed class 状态机穷举状态比 try-catch 更可控5BindingAdapter 封装渲染Activity 越薄架构越纯粹一句话带走reactive-mvvm-android 证明了——响应式 MVVM 的本质就是用 Flow 把输入 → 状态 → 界面焊成一条自动传送带再用 Clean Architecture 的四层结构保证传送带上每一环都可独立测试、独立替换。【免费下载链接】reactive-mvvm-androidMy way to MVVM using KotlinX Coroutines and Android>项目地址: https://gitcode.com/gh_mirrors/re/reactive-mvvm-android创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考