MVP_Project
一个基于 Android + Kotlin + Jetpack Compose 的示例工程,本文档用于说明如何在当前项目中采用 MVP 架构进行开发,并约束后续代码组织方式。
项目说明
当前工程已经完成基础 Android 应用初始化,主要技术栈如下:
- Kotlin
- Android SDK 34
- Jetpack Compose
- Material 3
- Gradle Kotlin DSL
当前入口页面位于 app/src/main/java/com/hk/word/mvp/MainActivity.kt,后续业务页面建议在此基础上逐步迁移为标准的 MVP 分层结构。
为什么采用 MVP
MVP 是一种经典的表现层架构模式,适合在页面逻辑逐渐变复杂时保持代码清晰。
采用 MVP 后可以得到这些收益:
View负责界面展示,减少页面类中混杂的业务逻辑Presenter负责流程编排与状态控制,便于测试Model负责数据获取与业务处理,便于复用- 页面结构清晰,后续多人协作时更容易维护
MVP 架构说明
在本项目中,推荐将 MVP 理解为以下三层:
1. View
View 负责:
- 展示 UI
- 接收用户操作
- 将用户行为转交给 Presenter
- 接收 Presenter 回传的数据并刷新界面
在当前项目里,Activity、Fragment,以及承载界面展示的 Compose 页面都可以视为 View 层的一部分。
注意:
- View 不直接处理复杂业务逻辑
- View 不直接操作网络、数据库等数据源
- View 只关心"显示什么"和"把事件交给谁"
2. Presenter
Presenter 负责:
- 接收 View 的事件
- 调用 Model 获取或处理数据
- 对结果进行业务编排
- 将最终结果回传给 View
Presenter 是 MVP 的核心,建议做到:
- 不依赖 Android 具体控件实现
- 不编写 UI 细节代码
- 尽量保持单一职责,一个页面对应一个 Presenter
3. Model
Model 负责:
- 网络请求
- 本地数据库访问
- Repository 封装
- 数据转换与业务数据处理
Model 不关心界面如何展示,只提供 Presenter 所需的数据能力。
推荐目录结构
建议后续按"功能模块 + MVP 分层"组织代码,例如:
text
app/src/main/java/com/hk/word/mvp
├── common/ # 通用工具、基类、扩展函数
├── data/ # 数据层
│ ├── local/ # 本地数据源
│ ├── remote/ # 远程数据源
│ ├── repository/ # Repository 实现
│ └── model/ # 数据实体
├── ui/
│ ├── home/ # 首页模块
│ │ ├── contract/ # HomeContract
│ │ ├── presenter/ # HomePresenter
│ │ ├── view/ # Activity/Fragment/Compose Screen
│ │ └── model/ # Home 模块相关业务模型
│ └── theme/ # Compose 主题
└── MainActivity.kt
如果后续模块变多,也可以进一步改为:
text
ui/
├── login/
├── home/
├── profile/
└── ...
每个模块内部都保持自己的 Contract、Presenter、View 和必要的数据封装,避免所有 Presenter 混在同一个目录。
推荐开发规范
Contract 先行
建议每个页面先定义一个 Contract,明确 View 与 Presenter 的职责边界。
示例:
kotlin
interface HomeContract {
interface View {
fun showLoading()
fun showContent(text: String)
fun showError(message: String)
}
interface Presenter {
fun loadData()
fun onDestroy()
}
}
这样做的好处是:
- 页面职责明确
- Presenter 与 View 的交互清晰
- 更容易进行单元测试和后续重构
Presenter 持有 View 接口
Presenter 不应该依赖具体 Activity 或 Composable 实现,而应依赖 View 接口。
示例:
kotlin
class HomePresenter(
private var view: HomeContract.View?,
private val repository: HomeRepository
) : HomeContract.Presenter {
override fun loadData() {
view?.showLoading()
val result = repository.getWelcomeText()
view?.showContent(result)
}
override fun onDestroy() {
view = null
}
}
Model 负责数据来源统一
不论数据来自本地还是网络,建议统一通过 Repository 暴露给 Presenter。
示例职责:
RemoteDataSource负责接口调用LocalDataSource负责本地缓存Repository负责决定取缓存还是远端
在 Compose 中如何落地 MVP
当前项目使用 Jetpack Compose,因此 View 层可以这样理解:
Activity或Fragment负责生命周期管理Composable负责 UI 渲染- Presenter 继续负责业务流程和页面逻辑
一个推荐的实现方式是:
Activity创建 PresenterActivity实现Contract.ViewActivity将 UI State 传给 Compose 页面- Compose 页面只负责展示和事件回调
- 用户点击事件再由 View 转交给 Presenter
例如:
kotlin
data class HomeUiState(
val loading: Boolean = false,
val content: String = "",
val error: String? = null
)
kotlin
@Composable
fun HomeScreen(
state: HomeUiState,
onReload: () -> Unit
) {
// 这里只负责渲染,不直接处理业务
}
这种方式的核心思想是:
- Compose 只关心状态驱动的界面渲染
- Presenter 只关心业务逻辑和状态更新
- 两者通过 View 层进行衔接
推荐页面交互流程
一个标准的页面调用链建议如下:
text
用户操作
-> View 接收事件
-> Presenter 处理逻辑
-> Model / Repository 获取数据
-> Presenter 整理结果
-> View 刷新 UI
例如用户点击"加载数据":
- Compose 按钮点击后通知 View
- View 调用
presenter.loadData() - Presenter 调用 Repository 获取数据
- Presenter 根据结果调用
showContent()或showError() - View 更新
UiState - Compose 根据最新状态重组界面
当前项目的落地建议
结合当前工程现状,建议按以下顺序演进:
- 保留
MainActivity作为应用入口 - 将示例
Greeting()页面替换为业务页面HomeScreen() - 新增
home/contract/HomeContract.kt - 新增
home/presenter/HomePresenter.kt - 新增
data/repository/HomeRepository.kt - 在
MainActivity中实现HomeContract.View - 使用
UiState驱动 Compose 页面更新
编码约束建议
为保证 MVP 架构长期稳定,建议遵循以下规范:
- 不在 View 中直接写网络请求
- 不在 Presenter 中直接依赖 Android UI 控件
- 不在 Model 中写界面逻辑
- 一个页面对应一个 Contract 和一个 Presenter
- 复杂页面统一使用
UiState管理显示状态 - Presenter 在页面销毁时及时释放 View 引用,避免内存泄漏
后续可扩展方向
当项目继续扩展时,可以在 MVP 基础上补充以下能力:
- Repository + DataSource 的完整数据层抽象
- 协程与异步任务封装
- 单元测试覆盖 Presenter
- 错误码与统一异常处理
- 页面级状态管理规范
总结
本项目建议采用 MVP + Compose 的方式组织表现层:
View负责展示和事件转发Presenter负责业务逻辑与流程编排Model负责数据处理与获取
这样既保留了 MVP 易维护、职责清晰的优点,也能兼容当前基于 Compose 的 Android 项目结构,适合作为后续业务开发的基础架构。