# family_share_andorid **Repository Path**: voghost/family_share_andorid ## Basic Information - **Project Name**: family_share_andorid - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2021-05-18 - **Last Updated**: 2021-10-29 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README --- title: android 家庭分享 [HomeHelper] date: 2021-05-19 20:34:48 tags: android cover: https://oss.ghovos.top/hexo-blog/android-cover.png --- ## 一. 简介 > 本项目为了解决家庭成员在生活中帐目信息不能同步,家庭备忘录不同步的问题,并且有一定的互动功能 * 项目 [gitee地址](https://gitee.com/voghost/family_share_andorid) * 项目 [文档介绍](https://blog.ghovos.com/2021/05/19/andorid-family-share/) * 应用下载 [链接](https://android-share.ghovos.top/download/) ### 1.1. `HomeHelper` 家庭帮手 特点 * :chart_with_upwards_trend:家庭记账,和分析近期情况,并且能家庭同步 * :golf:同步分享动态 * :ledger:同步家庭备忘录 * :closed_lock_with_key: 二维码分享加入家庭,且token校验 ### 1.2. 界面 1. 主界面 2. 个人中心 ### 1.3. 使用的技术栈 * `Android jetpack` * `Room` 数据库的ORM框架 * `LiveData` 动态刷新页面 * `ViewModel` 保存页面数据,用其维护数据 * `kotlinx.coroutines` **协程**, io操作在协程中运行, 防止阻塞ui线程 * `retrofit2` 和 `OkHttp2` 和后台发送网络请求 * 后台使用 `spring boot` `spring security` 开发的api, 以及安全验证功能 ## 二. 主要功能介绍 ### 2.1. 添加帐单并同步 * 记账 * 同步家庭帐单 * 通过折线图统计近期家庭帐单信息 ### 2.2. 扫描二维码加入家庭 * 二维码分享 * 扫描二维码加入家庭 ### 2.3. 备忘录添加并同步 * 同步备忘录, * 家庭成员都能看到备忘录 ### 2.4. 分享功能 * 分享近期的动态(表情,图片,回复) ## 三. 主要功能逻辑实现 ### 3.1. 登陆功能 `api-android.ghovos.top/user/login` ```plantuml @startuml 安卓端 -> 后台 : 帐号密码 后台 -> 安卓端: 校验, 并返回结果, 通过则返回token 安卓端 -> 安卓端: 存储返回的token @enduml ``` ### 3.2. 注册功能 `api-android.ghovos.top/user/regist` * 大致流程与登陆相同, 但是要判断用户名**是否重复** ### 3.3. 二维码分享加入家庭 `api-android.ghovos.top/user/join_family` ```plantuml @startuml 安卓端_邀请人 -> 安卓端_邀请人 : 通过本地存储的token,\n 生成二维码 安卓端_邀请人 -> 安卓端_被邀请人 : 扫描二维码 安卓端_被邀请人 -> 后台: 将token传给服务器 后台 -> 后台 : 通过token识别邀请人的家庭id,\n并设置被邀请人的id 后台 -> 安卓端_被邀请人 : 返回家庭成员列表 @enduml ``` ### 3.4. 同步功能(以帐单为例)`api-android.ghovos.top/user/syn_family` ```plantuml @startuml 安卓端 -> 安卓端 : 更新数据后, 本地数据版本号加一 安卓端 -> 后台 : 同步数据给后台 后台 -> 后台 : 比对数据版本\n将旧数据更新 后台 -> 安卓端 : 传回较新的数据给安卓端 @enduml ``` ### 3.5. 头像上传功能 * 本地读取图片, 通过api上传端后台 * 后台将图片存入oss, 加速图片访问 ## 四. 页面部分 ### 4.1 RecycleView 和 LiveData配合使用 ```xml tools:listitem="@layout/memorandum_item" /> ``` * 通过自定义adapter ,设置view的数据, 以及 因为有livedata,数据可以实时自动刷新 ```kotlin viewModel.getLiveData() ?.observe(viewLifecycleOwner, { memorandumList: List -> memorandum_recycle_view.adapter = MemorandumAdapter( ctx, memorandumList, viewModel, ) memorandum_recycle_view.layoutManager = LinearLayoutManager(activity) }) ``` ### 4.2 Fragment * 在MainActivity中使用Fragment * 多个页面在Activity中嵌套切换 ```kotlin //底部导航栏绑定 bottomNavigationView = findViewById(R.id.bottomNavigationView) navHostFragment = supportFragmentManager.findFragmentById(R.id.fragment) as NavHostFragment navController = navHostFragment.navController configuration = AppBarConfiguration.Builder(bottomNavigationView.menu).build() NavigationUI.setupActionBarWithNavController(this, navController, configuration) NavigationUI.setupWithNavController(bottomNavigationView, navController) ``` ## 五. 功能主要代码实现 ### 5.1. 网络部分 1. 添加网络权限 ```xml ``` 2. 使用retrofit 封装不同的请求 ```kotlin /** * example */ @POST("account/syn") suspend fun syn(@Body accountList: List): BaseResponse> ``` 3. 创建retrofit对象 ```kotlin /** * example * 通过 retrofit 网络发送请求 */ object UserApi { private val gsonFormat = GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create() private val api by lazy { val retrofit = Retrofit.Builder() .addConverterFactory(GsonConverterFactory.create(gsonFormat)) .client( OkHttpClient .Builder() .addInterceptor(MyIntercept()) .build() ) .build() retrofit.create(UserService::class.java) } fun get(): UserService { return api } } ``` 4. retrofit异常处理 ```kotlin suspend inline fun apiCall(crossinline call: suspend CoroutineScope.() -> BaseResponse): BaseResponse { return withContext(Dispatchers.IO) { val res: BaseResponse try { res = call() } catch (e: Throwable) { Log.e("ApiCaller", "request error", e) // 请求出错,将状态码和消息封装为 ResponseResult return@withContext ApiException.build(e).toResponse() } if (res.code == ApiException.CODE_AUTH_INVALID) { Log.e("ApiCaller", "request auth invalid") // 登录过期,取消协程,跳转登录界面 // 省略部分代码 cancel() } return@withContext res } } // 网络、数据解析错误处理 class ApiException( private val code: Int, override val message: String?, override val cause: Throwable? = null ) : RuntimeException(message, cause) { companion object { // 网络状态码 const val CODE_NET_ERROR = 4000 const val CODE_TIMEOUT = 4080 const val CODE_JSON_PARSE_ERROR = 4010 const val CODE_SERVER_ERROR = 5000 // 业务状态码 const val CODE_AUTH_INVALID = 401 fun build(e: Throwable): ApiException { /** * 各种异常 不同处理 */ } } fun toResponse(): BaseResponse { return BaseResponse(code, message) } } ``` 5. 后端和安卓端之间传递数据用json ### 5.2. 数据库部分 1. 创建实体 `Account` `User` `Memorandum` 2. 编写数据库查询语句 dao ```kotlin /** * 获取所有数据的列表 * @return LiveData 实时数据 不用在新的线程执行 */ @Query("SELECT * FROM account_table WHERE isDeleted=0") fun getAll(): LiveData> ``` 3. 用单例模式创建数据库 ```kotlin /** * 单例模式 */ companion object { @Volatile private var INSTANCE: FamilyShareDatabase? = null private val applicationScope = CoroutineScope(SupervisorJob()) fun getInstance(context: Context): FamilyShareDatabase = INSTANCE ?: synchronized(this) { INSTANCE ?: buildDatabase(context).also { INSTANCE = it } } private fun buildDatabase(context: Context) = Room.databaseBuilder( context.applicationContext, FamilyShareDatabase::class.java, "family_share_db" ).addCallback(UserDatabaseCallback(applicationScope)) // 加入callback .build() } ``` 4. 调用数据库例子 ```kotlin suspend fun getUserById(id: Long) = withContext(viewModelScope.coroutineContext) { userDao?.getUserById(id) } /** * 协程中调用 */ GlobalScope.launch{ getUserById(1) } ``` > 为了性能,数据库的增删改查都在新的**协程**执行