在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
开源软件名称(OpenSource Name):fuusy/component-jetpack-mvvm开源软件地址(OpenSource Url):https://github.com/fuusy/component-jetpack-mvvm开源编程语言(OpenSource Language):Kotlin 96.9%开源软件介绍(OpenSource Introduction):组件化+Jetpack+Kotlin+MVVM一、项目简介
该项目主要以 网络请求部分使用 对于具体的网络封装思路,可参考 【Jetpack篇】协程+Retrofit网络请求状态封装实战 【Jetpack篇】协程+Retrofit网络请求状态封装实战(2) 如果此项目对你有帮助和价值,烦请给个star,或者有什么好的建议或意见,也可以发个issues,感谢! 二、项目详情2.1、组件化搭建项目时暴露出的问题2.1.1、如何独立运行一个Module?运行总App时,子Module是属于 如何使用呢? 在每个 if (!singleModule.toBoolean()) {
apply plugin: 'com.android.library'
} else {
apply plugin: 'com.android.application'
}
......
dependencies {
} 如果需要独立运行只需要修改 2.1.2、编译运行后,桌面会出现多个相同图标;当新建多个Moudle的时候,运行后你会发现桌面上会出现多个相同的图标, 其实每个图标都能够独立运行,但是到最后App发布的时候,肯定是只需要一个总入口就可以了。 发生这种情况的原因很简单,因为新建一个 解决方案很简单,删除上图红色框框中的代码即可。
以下图项目为例: 我们可以在”webview“Module中,新建一个和java同层级的包,取名:manifest,将AndroidManifest.xml复制到该包下,并且将/manifest/AndroidManifest.xml中内容进行删除修改。 只留有一个空壳子,原来的 android{
sourceSets{
main {
if (!singleModule.toBoolean()) {
//如果是library,则编译manifest下AndroidManifest.xml
manifest.srcFile 'src/main/manifest/AndroidManifest.xml'
} else {
//如果是application,则编译主目录下AndroidManifest.xml
manifest.srcFile 'src/main/AndroidManifest.xml'
}
}
}
} 通过修改 上述处理后,子Moudule当作library时不会出现多个图标的情况,同时也可以独立运行。 2.1.3、组件间通信主要借助阿里的路由框架ARouter,具体使用请参考https://github.com/alibaba/ARouter 2.2、Jetpack组件2.2.1、NavigationNavigation是一个管理Fragment切换的组件,支持可视化处理。开发者也完全不用操心Fragment的切换逻辑。基本使用请参考官方说明 在使用 另外,官方对于与 将之前 /**
* navigation绑定BottomNavigationView
*/
private fun setupBottomNavigationBar() {
val navGraphIds =
listOf(R.navigation.navi_home, R.navigation.navi_project, R.navigation.navi_personal)
val controller = mBinding?.navView?.setupWithNavController(
navGraphIds = navGraphIds,
fragmentManager = supportFragmentManager,
containerId = R.id.nav_host_container,
intent = intent
)
currentNavController = controller
} 官方这么做的目的在于让每个模块单独管理自己的 2.2,2、Paging3Paging是一个分页组件,主要与Recyclerview结合分页加载数据。具体使用可
class DailyQuestionFragment : BaseFragment<FragmentDailyQuestionBinding>() {
...
private fun loadData() {
lifecycleScope.launchWhenCreated {
mViewModel.dailyQuestionPagingFlow().collectLatest {
dailyPagingAdapter.submitData(it)
}
}
}
...
}
class ArticleViewModel(private val repo: HomeRepo) : BaseViewModel(){
/**
* 请求每日一问数据
*/
fun dailyQuestionPagingFlow(): Flow<PagingData<DailyQuestionData>> =
repo.getDailyQuestion().cachedIn(viewModelScope)
}
class HomeRepo(private val service: HomeService, private val db: AppDatabase) : BaseRepository(){
/**
* 请求每日一问
*/
fun getDailyQuestion(): Flow<PagingData<DailyQuestionData>> {
return Pager(config) {
DailyQuestionPagingSource(service)
}.flow
}
}
/**
* @date:2021/5/20
* @author fuusy
* @instruction: 每日一问数据源,主要配合Paging3进行数据请求与显示
*/
class DailyQuestionPagingSource(private val service: HomeService) :
PagingSource<Int, DailyQuestionData>() {
override fun getRefreshKey(state: PagingState<Int, DailyQuestionData>): Int? = null
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, DailyQuestionData> {
return try {
val pageNum = params.key ?: 1
val data = service.getDailyQuestion(pageNum)
val preKey = if (pageNum > 1) pageNum - 1 else null
LoadResult.Page(data.data?.datas!!, prevKey = preKey, nextKey = pageNum + 1)
} catch (e: Exception) {
LoadResult.Error(e)
}
}
} 2.2.3、Room
class HomeRepo(private val service: HomeService, private val db: AppDatabase) : BaseRepository() {
/**
* 请求首页文章,
* Room+network进行缓存
*/
fun getHomeArticle(articleType: Int): Flow<PagingData<ArticleData>> {
mArticleType = articleType
return Pager(
config = config,
remoteMediator = ArticleRemoteMediator(service, db, 1),
pagingSourceFactory = pagingSourceFactory
).flow
}
}
@Dao
interface ArticleDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertArticle(articleDataList: List<ArticleData>)
@Query("SELECT * FROM tab_article WHERE articleType =:articleType")
fun queryLocalArticle(articleType: Int): PagingSource<Int, ArticleData>
@Query("DELETE FROM tab_article WHERE articleType=:articleType")
suspend fun clearArticleByType(articleType: Int)
}
@Database(
entities = [ArticleData::class, RemoteKey::class],
version = 1,
exportSchema = false
)
abstract class AppDatabase : RoomDatabase() {
abstract fun articleDao(): ArticleDao
abstract fun remoteKeyDao(): RemoteKeyDao
companion object {
private const val DB_NAME = "app.db"
@Volatile
private var instance: AppDatabase? = null
fun get(context: Context): AppDatabase {
return instance ?: Room.databaseBuilder(context, AppDatabase::class.java,
DB_NAME
)
.build().also {
instance = it
}
}
}
}
/**
* @date:2021/5/20
* @author fuusy
* @instruction:RemoteMediator 的主要作用是:在 Pager 耗尽数据或现有数据失效时,从网络加载更多数据。
* 可以使用此信号从网络加载更多数据并将其存储在本地数据库中,PagingSource 可以从本地数据库加载这些数据并将其提供给界面进行显示。
* 当需要更多数据时,Paging 库从 RemoteMediator 实现调用 load() 方法。这是一项挂起功能,因此可以放心地执行长时间运行的工作。
* 此功能通常从网络源提取新数据并将其保存到本地存储空间。
* 此过程会处理新数据,但长期存储在数据库中的数据需要进行失效处理(例如,当用户手动触发刷新时)。
* 这由传递到 load() 方法的 LoadType 属性表示。LoadType 会通知 RemoteMediator 是需要刷新现有数据,还是提取需要附加或前置到现有列表的更多数据。
*/
@OptIn(ExperimentalPagingApi::class)
class ArticleRemoteMediator(
private val api: HomeService,
private val db: AppDatabase,
private val articleType: Int
) : RemoteMediator<Int, ArticleData>() {
override suspend fun load(
loadType: LoadType,
state: PagingState<Int, ArticleData>
): MediatorResult {
/*
1.LoadType.REFRESH:首次访问 或者调用 PagingDataAdapter.refresh() 触发
2.LoadType.PREPEND:在当前列表头部添加数据的时候时触发,实际在项目中基本很少会用到直接返回 MediatorResult.Success(endOfPaginationReached = true) ,参数 endOfPaginationReached 表示没有数据了不在加载
3.LoadType.APPEND:加载更多时触发,这里获取下一页的 key, 如果 key 不存在,表示已经没有更多数据,直接返回 MediatorResult.Success(endOfPaginationReached = true) 不会在进行网络和数据库的访问
*/
try {
Log.d(TAG, "load: $loadType")
val pageKey: Int? = when (loadType) {
LoadType.REFRESH -> null
LoadType.PREPEND -> return MediatorResult.Success(true)
LoadType.APPEND -> {
//使用remoteKey来获取下一个或上一个页面。
val remoteKey =
state.lastItemOrNull()?.id?.let {
db.remoteKeyDao().remoteKeysArticleId(it, articleType)
}
//remoteKey' null ',这意味着在初始刷新后没有加载任何项目,也没有更多的项目要加载。
if (remoteKey?.nextKey == null) {
return MediatorResult.Success(true)
}
remoteKey.nextKey
}
}
val page = pageKey ?: 0
//从网络上请求数据
val result = api.getHomeArticle(page).data?.datas
result?.forEach {
it.articleType = articleType
}
val endOfPaginationReached = result?.isEmpty()
db.withTransaction {
if (loadType == LoadType.REFRESH) {
//清空数据
db.remoteKeyDao().clearRemoteKeys(articleType)
db.articleDao().clearArticleByType(articleType)
}
val prevKey = if (page == 0) null else page - 1
val nextKey = if (endOfPaginationReached!!) null else page + 1
val keys = result.map {
RemoteKey(
articleId = it.id,
prevKey = prevKey,
nextKey = nextKey,
articleType = articleType
)
}
db.remoteKeyDao().insertAll(keys)
db.articleDao().insertArticle(articleDataList = result)
}
return MediatorResult.Success(endOfPaginationReached!!)
} catch (e: IOException) {
return Mediato |
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论