本软件完全遵循Material Design 3的设计规范, 并且在Android12+支持Dynamic colors
在架构模式上使用了MVI架构, 并且以个人的理解实现了Google推荐的数据管理方式, 即唯一可信数据源, 单向数据流
UI完全由用 Jetpack Compose 以声明式编程的方式构建, Jetpack Compose 是 Android 推荐的用于构建本机 UI 的现代工具包。它简化并加速了 Android 上的 UI 开发。通过更少的代码、强大的工具和直观的 Kotlin API,快速使您的应用栩栩如生。Jetpack Compose 可以大幅提升界面的复用性, 在嵌套布局中几乎不会消耗性能,但是要注意的是, Compose 组件会多次重组, 所以请不要将不必要的数据传入或写进组件内, 如果实在需要请使用remember
来避免不必要的重复赋值。Jetpack Compose 可以完美的契合 Kotlin 的 Flow 数据流,省去了其他繁琐的数据状态观察方式
网络请求框架使用Retrofit2, 此框架可以快速方便的构建网络请求
数据持久层使用了Room数据库来存储可能需要多次访问的数据, 并配合Paging3的RemoteMediator
来实现从多个数据源获取数据
inline
内联函数进行API数据实体类转化, 可以大幅提高 Lambda 函数运行的效率, 并且在函数中添加了API数据errorCode
的监控, 以便在用户未登录时或其他问题时提醒用户启动页 | 首页 | 项目 | 导航 | 账号 |
---|---|---|---|---|
启动页-深色 | 首页-深色 | 项目-深色 | 导航-深色 | 账号-深色 |
---|---|---|---|---|
Room具有以下优势:
Room可以利用注解以及查询语句快速的自动生成java对象供开发者使用
@Dao
interface HomeDao {
@Query("SELECT * FROM home_article_table")
fun getAll(): PagingSource<Int, HomeEntity>
@Insert(onConflict = REPLACE)
suspend fun insertAll(articles: List<HomeEntity>)
@Query("DELETE FROM home_article_table")
suspend fun clearAll()
}
Paging具有以下功能:
我们只需要构建一个BasePagingMediator就可以为后来的RemoteMediator提供一个简单的多数据源模板
/**
* @param T:Entity
* @param R:RemoteKeys
* @param E:ApiData
*/
@OptIn(ExperimentalPagingApi::class)
abstract class BaseRemoteMediator<T : Any, R : Any, E : Any>(
private val articleDatabase: ArticleDatabase
) : RemoteMediator<Int, T>() {
abstract suspend fun getApi(currentPage: Int): Result<List<E>>
abstract fun convertToEntity(data: E): T
abstract fun convertToRemoteKeys(data: T, prevPage: Int?, nextPage: Int?): R
abstract suspend fun clearAll()
abstract suspend fun insertAll(remoteKeys: List<R>, data: List<T>)
abstract suspend fun lastUpdated(): Long
override suspend fun initialize(): InitializeAction {
val cacheTimeout = TimeUnit.MILLISECONDS.convert(1, TimeUnit.HOURS)
return if (System.currentTimeMillis() - lastUpdated() <= cacheTimeout) {
InitializeAction.SKIP_INITIAL_REFRESH
} else {
InitializeAction.LAUNCH_INITIAL_REFRESH
}
}
override suspend fun load(
loadType: LoadType,
state: PagingState<Int, T>
): MediatorResult {
return try {
val currentPage = when (loadType) {
LoadType.REFRESH -> {
val remoteKeys = getRemoteKeyClosestToCurrentPosition(state)
remoteKeys?.minus(1) ?: 0
}
LoadType.PREPEND -> {
val remoteKeys = getRemoteKeyForFirstItem(state)
val pervPage = remoteKeys ?: return MediatorResult.Success(
endOfPaginationReached = false
)
pervPage
}
LoadType.APPEND -> {
val remoteKeys = getRemoteKeyForLastItem(state)
val nextPage = remoteKeys ?: return MediatorResult.Success(
endOfPaginationReached = false
)
nextPage
}
}
val result = getApi(currentPage)
val endOfPaginationReached = result.getOrNull().isNullOrEmpty()
val prevPage = if (currentPage == 0) null else currentPage - 1
val nextPage = if (endOfPaginationReached) null else currentPage + 1
articleDatabase.withTransaction {
if (loadType == LoadType.REFRESH) {
clearAll()
}
result.getOrNull()?.let {
val entity = it.map { data -> convertToEntity(data) }
val keys = entity.map { data ->
convertToRemoteKeys(data, prevPage, nextPage)
}
insertAll(keys, entity)
}
}
MediatorResult.Success(endOfPaginationReached)
} catch (e: Exception) {
MediatorResult.Error(e)
}
}
/**
*
*@return nextPage
*/
abstract suspend fun getRemoteKeyClosestToCurrentPosition(state: PagingState<Int, T>): Int?
/**
*@return pervPage
*/
abstract suspend fun getRemoteKeyForFirstItem(state: PagingState<Int, T>): Int?
/**
*@return nextPage
*/
abstract suspend fun getRemoteKeyForLastItem(state: PagingState<Int, T>): Int?
}
MVI
架构是什么?MVI
与 MVVM
很相似,其借鉴了前端框架的思想,更加强调数据的单向流动和唯一数据源,架构图如下所示
其主要分为以下几部分
Model
: 与MVVM
中的Model
不同的是,MVI
的Model
主要指UI
状态(State
)。例如页面加载状态、控件位置等都是一种UI
状态View
: 与其他MVX
中的View
一致,可能是一个Activity
或者任意UI
承载单元。MVI
中的View
通过订阅Model
的变化实现界面刷新Intent
: 此Intent
不是Activity
的Intent
,用户的任何操作都被包装成Intent
后发送给Model
层进行数据请求MVI
强调数据的单向流动,主要分为以下几步:
Intent
的形式通知Model
Model
基于Intent
更新State
View
接收到State
变化刷新UI。数据永远在一个环形结构中单向流动,不能反向流动:
上面简单的介绍了下MVI
架构,下面我们一起来看下具体是怎么使用MVI
架构的
我们使用ViewModel
来承载MVI
的Model
层,总体结构也与MVVM
类似,主要区别在于Model
与View
层交互的部分
Model
层承载UI
状态,并暴露出ViewState
供View
订阅,ViewState
是个data class
,包含所有页面状态View
层通过Action
更新ViewState
,替代MVVM
通过调用ViewModel
方法交互的方式此段关于MVI架构的解释部分引用自掘金用户:程序员江同学的MVVM 进阶版:MVI 架构了解一下~
鸿洋大佬的WanAndroid提供的开放Api
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。