手把手教你用 Jetpack Compose 打造一款 Android 提醒应用(附源码)
📌开源地址:https://github.com/cwh1234/remember_app
如果觉得有帮助,欢迎 ⭐ Star 支持一下!
📝 前言
生活中总有太多需要记住的事情——亲友的生日、重要的纪念日、证件到期时间、每月账单缴费……稍不留神就容易错过。市面上的提醒类 App 要么广告太多,要么功能繁杂不够纯粹。于是我决定自己动手,用Jetpack Compose + MVVM架构,打造一款简洁实用的 Android 提醒应用——Remember App。
本文将完整分享这个项目从架构设计到核心实现的全过程,适合正在学习 Jetpack Compose 或想了解 Android 现代开发实践的同学参考。
✨ 功能概览
| 功能 | 说明 |
|---|---|
| 🎂生日提醒 | 支持每年重复,不再错过亲友生日 |
| 💝纪念日 | 纪念重要的日子,每年自动提醒 |
| ✈️出差准备 | 提前通知,做好出差准备 |
| 📋证件到期 | 身份证、护照等证件到期提醒 |
| 💰账单缴费 | 水电网费、贷款等账单提醒 |
| 📌自定义 | 灵活创建个性化提醒 |
| ⏰提前提醒 | 支持设置提前 0-30 天通知 |
| 🔄每年重复 | 生日、纪念日等支持每年自动重复 |
| 📅节假日管理 | 内置中国法定节假日,可自定义添加 |
| 🌓深色模式 | 自动跟随系统深色/浅色主题 |
🛠 技术栈
Kotlin + Jetpack Compose (Material 3) ├── Room Database → 本地数据持久化 ├── Navigation Compose → 页面导航 ├── ViewModel + StateFlow → MVVM 架构 ├── DataStore → 偏好设置存储 ├── KSP → Room 编译期注解处理 └── Coroutines → 异步任务管理版本选型:
- Kotlin 1.8.22
- Compose BOM 2023.06.00
- Room 2.5.2
- Navigation Compose 2.6.0
- compileSdk / targetSdk 33
📐 项目架构
整体采用 Android 官方推荐的MVVM + Repository架构,遵循单向数据流原则:
┌─────────────────────────────────────────────┐ │ UI Layer │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ Reminder │ │ Add │ │ Mine │ │ │ │ Screen │ │ Screen │ │ Screen │ │ │ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ │ │ │ │ │ │ ┌────┴──────────────┴──────────────┴────┐ │ │ │ ViewModels (StateFlow) │ │ │ └────────────────┬──────────────────────┘ │ ├───────────────────┼─────────────────────────┤ │ Data Layer │ │ ┌────────────────┴──────────────────────┐ │ │ │ Repository │ │ │ └────────┬──────────────────┬───────────┘ │ │ │ │ │ │ ┌────────┴──────┐ ┌───────┴──────────┐ │ │ │ ReminderDao │ │ HolidayDao │ │ │ └───────┬───────┘ └───────┬──────────┘ │ │ │ │ │ │ ┌───────┴──────────────────┴──────────┐ │ │ │ Room Database │ │ │ └──────────────────────────────────────┘ │ └─────────────────────────────────────────────┘项目目录结构:
app/src/main/java/com/remember/app/ ├── RememberApp.kt # Application,初始化数据库和仓库 ├── MainActivity.kt # 唯一 Activity,入口 ├── model/ │ └── ReminderType.kt # 提醒类型枚举 ├── data/ │ ├── database/ │ │ ├── AppDatabase.kt # Room 数据库定义 + 预填充 │ │ ├── dao/ │ │ │ ├── ReminderDao.kt # 提醒数据访问 │ │ │ └── HolidayDao.kt # 节假日数据访问 │ │ └── entity/ │ │ ├── Reminder.kt # 提醒实体 │ │ └── Holiday.kt # 节假日实体 │ └── repository/ │ ├── ReminderRepository.kt │ └── HolidayRepository.kt ├── navigation/ │ └── AppNavigation.kt # 底部导航 + 页面路由 └── ui/ ├── theme/ # Material 3 主题配置 ├── add/ # "添加提醒"页面 + ViewModel ├── reminder/ # "提醒列表"页面 + ViewModel └── mine/ # "我的"页面 + ViewModel🔑 核心代码详解
1. 数据实体设计
提醒实体Reminder是整个应用的核心:
@Entity(tableName="reminders")dataclassReminder(@PrimaryKey(autoGenerate=true)valid:Long=0,valtitle:String,// 提醒标题valtype:String,// 提醒类型枚举名valtargetDate:Long,// 目标日期时间戳valadvanceDays:Int=3,// 提前提醒天数(0-30)valnotes:String="",// 备注valisActive:Boolean=true,// 是否启用valisRepeatYearly:Boolean=false,// 是否每年重复valcreatedAt:Long=System.currentTimeMillis())设计要点:
targetDate使用Long时间戳存储,方便做日期比较和排序advanceDays控制提前多少天开始提醒(支持 0-30 天滑块调节)isRepeatYearly标记生日、纪念日等每年重复的事项isActive支持软删除,用户可随时暂停/恢复提醒
2. Room DAO — Flow 响应式数据
Room 与 Kotlin Flow 的结合是本文亮点之一:
@DaointerfaceReminderDao{// 获取从今天起的所有活跃提醒,按日期升序@Query("SELECT * FROM reminders WHERE is_active = 1 AND target_date >= :today ORDER BY target_date ASC")fungetUpcomingReminders(today:Long):Flow<List<Reminder>>// 获取所有活跃提醒@Query("SELECT * FROM reminders WHERE is_active = 1 ORDER BY target_date ASC")fungetAllActiveReminders():Flow<List<Reminder>>// 插入提醒@Insert(onConflict=OnConflictStrategy.REPLACE)suspendfuninsert(reminder:Reminder):Long// 切换启用状态@Query("UPDATE reminders SET is_active = :isActive WHERE id = :id")suspendfunsetActive(id:Long,isActive:Boolean)}为什么要用Flow?— DAO 返回Flow<List<Reminder>>,数据变化时 UI 自动更新,无需手动刷新。这就是响应式编程(Reactive Programming)的核心优势。
3. ViewModel — 状态管理核心
以AddViewModel为例,展示标准的 MVVM 状态管理:
dataclassAddUiState(valselectedType:ReminderType=ReminderType.BIRTHDAY,valtitle:String="",valtargetDate:Long=System.currentTimeMillis(),valadvanceDays:Int=3,valnotes:String="",valisRepeatYearly:Boolean=false,valisSaving:Boolean=false,valsaveSuccess:Boolean=false,valerrorMessage:String?=null)classAddViewModel(application:Application):AndroidViewModel(application){privateval_uiState=MutableStateFlow(AddUiState())valuiState:StateFlow<AddUiState>=_uiState.asStateFlow()funupdateAdvanceDays(days:Int){_uiState.value=_uiState.value.copy(advanceDays=days.coerceIn(0,90))}funsaveReminder(){valstate=_uiState.valueif(state.title.isBlank()){_uiState.value=state.copy(errorMessage="请输入提醒标题")return}viewModelScope.launch{_uiState.value=_uiState.value.copy(isSaving=true)valreminder=Reminder(title=state.title.trim(),type=state.selectedType.name,targetDate=state.targetDate,advanceDays=state.advanceDays,notes=state.notes.trim(),isRepeatYearly=state.isRepeatYearly)reminderRepository.insert(reminder)_uiState.value=AddUiState(saveSuccess=true)// 保存成功,重置表单}}}核心思路:
- 使用
data class封装所有 UI 状态,单一数据源(Single Source of Truth) MutableStateFlow+asStateFlow()保证外部只读,内部可写data class.copy()实现不可变状态更新- 所有异步操作通过
viewModelScope.launch管理,避免内存泄漏
4. 提醒列表 — 智能分组显示
ReminderViewModel将提醒分为**“最近提醒"和"接下来”**两组:
privatefunloadReminders(){valtoday=getTodayStart()valdayInMillis=86_400_000LvaltodayDays=today/dayInMillis viewModelScope.launch(Dispatchers.IO){reminderRepository.getUpcomingReminders(today).map{allUpcoming->// 在 Kotlin 层分离"即将到来"和"最近提醒(已进入窗口期)"valdue=allUpcoming.filter{r->valtargetDays=r.targetDate/dayInMillis(targetDays-r.advanceDays)<=todayDays}ReminderUiState(upcomingReminders=allUpcoming,dueReminders=due,isLoading=false)}.catch{e->/* 错误处理 */}.collect{state->_uiState.value=state}}}分组逻辑:如果目标日期 - 提前天数 ≤ 今天,则该提醒进入"最近提醒"组(即已进入提醒窗口期),其余放入"接下来"组。每个提醒卡片显示剩余天数,进入窗口期的显示高亮色,一目了然。
5. 底部导航 — Material 3 NavigationBar
使用 Material 3 的NavigationBar实现三 Tab 底部导航:
@ComposablefunAppNavigation(){valnavItems=listOf(BottomNavItem.Reminder,// 提醒列表BottomNavItem.Add,// 添加提醒BottomNavItem.Mine// 个人中心)varselectedIndexbyremember{mutableStateOf(0)}Scaffold(bottomBar={NavigationBar(tonalElevation=8.dp){navItems.forEachIndexed{index,item->NavigationBarItem(selected=selectedIndex==index,onClick={selectedIndex=index},icon={Icon(/* selected/unselected icon */)},label={Text(item.title)},colors=NavigationBarItemDefaults.colors(/* 自定义颜色 */))}}}){innerPadding->when(selectedIndex){0->ReminderScreen()1->AddScreen()2->MineScreen()}}}设计思路:采用简单的selectedIndex状态切换,而非 Navigation Compose 的路由机制,适合这种"同级页面切换"场景,代码更简洁,无需传递路由参数。
6. Material 3 主题 — 支持深色模式
privatevalLightColorScheme=lightColorScheme(primary=Primary,// 温暖的橙色 #FF6B35background=Background,// #FAFAFAsurface=Surface,// White// ...)@ComposablefunRememberAppTheme(darkTheme:Boolean=isSystemInDarkTheme(),content:@Composable()->Unit){valcolorScheme=if(darkTheme)DarkColorSchemeelseLightColorScheme// 动态设置状态栏颜色valview=LocalView.current SideEffect{valwindow=(view.contextasActivity).window window.statusBarColor=colorScheme.primary.toArgb()}MaterialTheme(colorScheme=colorScheme,typography=AppTypography,content=content)}主题特点:
- 主色调采用温暖橙色(
#FF6B35),给人温馨、积极的感受 - 每种提醒类型有独立配色(生日粉色、出差蓝色、账单绿色等)
- 完美适配系统深色模式,自动切换
- 状态栏颜色跟随主题主色
7. 添加提醒界面 — 分类卡片 + 表单
添加页面采用3 列网格卡片选择提醒类型 +表单区域填写详情:
// 6 种提醒类型的分类网格LazyVerticalGrid(columns=GridCells.Fixed(3)){items(reminderCategories){category->CategoryCard(category=category,isSelected=uiState.selectedType==category.type,onClick={viewModel.updateType(category.type)})}}每个分类卡片有独立图标(emoji)、名称、颜色,选中时显示高亮边框和阴影,交互反馈清晰。
8. 数据库预填充 — 内置节假日
应用首次启动时,通过 Room 的Callback.onCreate()预填充中国法定节假日:
overridefunonCreate(db:SupportSQLiteDatabase){super.onCreate(db)valholidays=listOf("元旦"tocreateDate(currentYear,0,1),"春节"tocreateDate(currentYear,1,10),"清明节"tocreateDate(currentYear,3,5),"劳动节"tocreateDate(currentYear,4,1),"端午节"tocreateDate(currentYear,5,10),"中秋节"tocreateDate(currentYear,8,15),"国庆节"tocreateDate(currentYear,9,1),"除夕"tocreateDate(currentYear,11,30))for((name,date)inholidays){db.execSQL("INSERT OR IGNORE INTO holidays (name, date, is_enabled, is_default) VALUES (?, ?, 1, 1)",arrayOf(name,date))}}用户可以在"我的"页面随时开启/关闭任意节假日,也可以添加自定义节假日。
9. 全局异常保护
在RememberApp中设置了全局异常处理器,防止未预期错误导致闪退:
Thread.setDefaultUncaughtExceptionHandler{thread,throwable->Log.e("RememberApp","!!! UNCAUGHT EXCEPTION !!!",throwable)// 数据库损坏时尝试自动恢复if(throwable.message?.contains("database")==true||throwable.message?.contains("Room")==true){deleteDatabase("remember_app_db")}defaultHandler?.uncaughtException(thread,throwable)}🚀 构建与运行
环境要求
| 工具 | 版本 |
|---|---|
| Android Studio | Flamingo (2022.2.1) 或更高 |
| JDK | 11+ |
| Android SDK | 33 |
| Gradle | 7.4.2 |
快速开始
# 1. 克隆项目gitclone https://github.com/cwh1234/remember_app.gitcdremember_app# 2. 构建 Debug APK./gradlew assembleDebug# 3. 安装到设备./gradlew installDebug或者直接用 Android Studio 打开项目,点击 Run 按钮运行。
📊 项目亮点总结
- 纯 Jetpack Compose— 全部 UI 使用声明式 Compose 编写,0 XML 布局文件(除了 manifest 和资源文件)
- 标准 MVVM 架构— ViewModel + StateFlow + Repository 模式,单向数据流,职责清晰
- 响应式数据— Room DAO 返回 Flow,数据变化自动触发 UI 刷新
- Material 3 设计— 紧跟 Google 最新设计规范,支持 Dynamic Color
- 完善的异常处理— try-catch 覆盖所有关键路径,全局崩溃保护
- 代码精简— 核心代码约 20 个 Kotlin 文件,小而美,适合学习参考
- 即装即用— 提供预构建 APK,无需编译即可体验
🔮 后续规划
- 通知栏推送(WorkManager + Notification)
- 桌面小组件(App Widget)
- 云端同步(Firebase / 自建后端)
- 农历日期支持
- 数据导出/导入
- 更多自定义主题色
📄 关于开源
本项目采用MIT License开源,你可以自由地:
- ✅ 学习参考
- ✅ 二次开发
- ✅ 商业使用
📌GitHub 仓库:https://github.com/cwh1234/remember_app
如果这个项目对你有帮助,欢迎给个 ⭐ Star,你的支持是我持续更新的动力!
💬 写在最后
这个项目从零到一,完整实践了 Jetpack Compose + MVVM 的现代 Android 开发流程。如果你是 Android 初学者,希望这篇文章能帮你建立对 Compose 开发的基本认知;如果你是老手,也欢迎提出改进建议,互相学习。
如果你在运行过程中遇到任何问题,欢迎在 GitHub 提 Issue,我会尽快回复。