当前位置: 首页 > news >正文

建网站的工具个人网站源码下载

建网站的工具,个人网站源码下载,做做网站2023下载,丰台手机网站设计公司Android Jetpack Compose 中的分页与缓存展示 在几乎任何类型的移动项目中#xff0c;移动开发人员在某个时候都会处理分页数据。如果数据列表太大#xff0c;无法一次从服务器检索完毕#xff0c;这就是必需的。因此#xff0c;我们的后端同事为我们提供了一个端点#… Android Jetpack Compose 中的分页与缓存展示 在几乎任何类型的移动项目中移动开发人员在某个时候都会处理分页数据。如果数据列表太大无法一次从服务器检索完毕这就是必需的。因此我们的后端同事为我们提供了一个端点返回分页数据列表并期望我们知道如何在客户端处理它。 在本文中我们将重点介绍如何使用 Android 在 2023 年 6 月推荐的最新方法来获取、缓存和显示分页数据。我们将经过以下步骤 从公共 GraphQL API 中按页获取 Pokemon 数据列表使用 Room 将获取的数据缓存到本地数据库使用最新的 Paging 库组件来处理分页使用 LazyColumn 智能地显示页面项只渲染可见内容 对于示例项目我将在文章末尾分享 GitHub 存储库链接我们将使用 Hilt 作为我们的依赖注入库并使用干净架构表示层 → 领域层 ← 数据层。因此我将从数据层开始解释事物然后转向领域层最后结束在表示层。 数据层 这一层是关于分页和缓存的大部分内容。因此如果您能够通过这一部分您将基本完成了它。 远程数据源 作为远程数据源我们将使用一个公共的 GraphQL Pokemon API。与我们用于与 REST API 交互的 Retrofit 不同我们使用 Apollo 的 Kotlin 客户端来处理 GraphQL API。它允许我们执行 GraphQL 查询并根据请求和响应自动生成 Kotlin 模型。 首先我们需要将以下行添加到我们的模块级别的 build.gradle 文件中 plugins {// ...id com.apollographql.apollo3 version $apollo_version }apollo {service(pokemon) {packageName.set(dev.thunderbolt.pokemonpager.data)} }dependencies {// ...implementation com.apollographql.apollo3:apollo-runtime:$apollo_version }在这里我们在 apollo 块中设置了 Apollo 库的配置。它提供了许多设置您可以通过其文档查看所有设置。目前我们只需要将包名设置为 dev.thunderbolt.pokemonpager.data这样生成的 Kotlin 文件将位于正确的包中也就是数据层。 然后我们需要下载服务器的模式以便库能够生成模型并且我们可以使用自动完成来编写查询。为了下载模式我们使用 Apollo 提供的以下命令 ./gradlew :app:downloadApolloSchema --endpointhttps://graphql-pokeapi.graphcdn.app/graphql --schemaapp/src/main/graphql/schema.graphqls这将在 app/src/main/graphql/schema.graphqls 目录中下载服务器的模式。 现在是时候在一个名为 pokemon.graphql 的文件中编写我们的查询该文件与模式文件位于同一文件夹中。 query PokemonList($offset: Int!$limit: Int! ) {pokemons(offset: $offset,limit: $limit) {nextOffsetresults {idnameimage}} }当我们构建项目时Apollo Kotlin 将通过自动运行名为 generateApolloSources 的 Gradle 任务为此查询生成模型。 回到 Kotlin 的世界我们将定义我们的 PokemonApi 类以封装与 GraphQL 的所有交互如下所示 class PokemonApi {private val BASE_URL https://graphql-pokeapi.graphcdn.app/graphqlprivate val apolloClient ApolloClient.Builder().serverUrl(BASE_URL).addHttpInterceptor(LoggingInterceptor()).build()suspend fun getPokemonList(offset: Int, limit: Int): PokemonListQuery.Pokemons? {val response apolloClient.query(PokemonListQuery(offset offset,limit limit,)).execute()// IF RESPONSE HAS ERRORS OR DATA IS NULL, THROW EXCEPTIONif (response.hasErrors() || response.data null) {throw ApolloException(response.errors.toString())}return response.data!!.pokemons} }在这里我们使用所需的配置初始化 Apollo Client 实例并实现了我们执行在 pokemon.graphql 文件中编写的生成的 Kotlin 版本查询的函数。该函数基本上会获取 offset 和 limit 参数执行查询如果一切顺利就会返回查询的响应这也是由 Apollo 自动生成的。 本地数据源/存储 为了在本地存储关系型数据并创建一个离线优先的应用程序我们将依赖于 Room这是一个在 SQLite 之上编写的 Android 持久性库。 首先我们需要将 Room 依赖项添加到我们的 build.gradle 文件中 dependencies {// ...implementation androidx.room:room-ktx:$room_versionkapt androidx.room:room-compiler:$room_versionimplementation androidx.room:room-paging:$room_version }然后我们将定义两个实体类一个用于在我们的数据库中存储 Pokemon 数据另一个用于跟踪要获取的下一页的页数。 Entity(pokemon) data class PokemonEntity(PrimaryKey val id: Int,val name: String,val imageUrl: String, )Entity(remote_key) data class RemoteKeyEntity(PrimaryKey val id: String,val nextOffset: Int, )在这方面我们还需要两个 DAO数据访问对象类来定义其中的所有数据库交互。 Dao interface PokemonDao {Insert(onConflict OnConflictStrategy.REPLACE)suspend fun insertAll(items: ListPokemonEntity)Query(SELECT * FROM pokemon)fun pagingSource(): PagingSourceInt, PokemonEntityQuery(DELETE FROM pokemon)suspend fun clearAll() }Dao interface RemoteKeyDao {Insert(onConflict OnConflictStrategy.REPLACE)suspend fun insert(item: RemoteKeyEntity)Query(SELECT * FROM remote_key WHERE id :id)suspend fun getById(id: String): RemoteKeyEntity?Query(DELETE FROM remote_key WHERE id :id)suspend fun deleteById(id: String) }在这里我们需要特别关注的关键函数是 pagingSource()。Room 可以返回数据列表作为 PagingSource以便我们稍后将创建的 Pager 对象将其用作生成 PagingData 流的单一源。 最后我们需要一个 RoomDatabase 类在本地数据库中为这些实体创建表并提供 DAO 以与这些表进行交互。 Database(entities [PokemonEntity::class, RemoteKeyEntity::class],version 1, ) abstract class PokemonDatabase : RoomDatabase() {abstract val pokemonDao: PokemonDaoabstract val remoteKeyDao: RemoteKeyDao } 这两个类即 PokemonDatabase 和之前定义的 PokemonApi 类都由我们数据层的 Hilt 模块实例化并提供为单例对象。 Module InstallIn(SingletonComponent::class) class DataModule {ProvidesSingletonfun providePokemonDatabase(ApplicationContext context: Context): PokemonDatabase {return Room.databaseBuilder(context,PokemonDatabase::class.java,pokemon.db,).fallbackToDestructiveMigration().build()}ProvidesSingletonfun providePokemonApi(): PokemonApi {return PokemonApi()}// ... }远程中介器Remote Mediator 现在我们要实现我们的远程中介器类RemoteMediator它将负责在需要时从远程 API 加载分页数据到本地数据库中。需要注意的是远程中介器并不直接向用户界面提供数据。如果分页数据用尽分页库会触发远程中介器的 load(…) 方法以从远程获取并存储更多的数据到本地。因此我们的本地数据库始终可以保持作为唯一的真实数据源。 在 load(…) 函数中我们首先需要检查我们正在处理哪种类型的加载。如果 LoadType 是 REFRESH这意味着我们要么处于初始加载状态要么数据已经无效我们需要从头开始获取数据。因此如果是这种情况我们将偏移值设置为 “0”以获取第一页的数据。PREPEND我们需要获取当前页面之前的页面数据。在这个示例的范围内不需要在向上滚动时获取任何内容。因此我们只需返回 MediatorResult.Success(endOfPaginationReached true)以指示不应再进行数据加载。APPEND我们需要获取当前页面之后的页面数据。在这种情况下我们会获取已经由前一个数据加载存储在本地数据库中的远程键remote key对象。如果没有或者其 nextOffset 值为 “0”则表示没有更多数据可加载和追加。顺便说一下这就是该 API 的工作方式。你的 API 可能以不同方式指示数据的结束因此需要相应地编写你的 APPEND 逻辑。 在确定了正确的偏移值之后现在是时候使用此偏移值和配置中提供的 pageSize 进行 API 调用了。我们将在下一步创建 Pager 对象时设置页面大小。 如果 API 调用成功返回新的页面数据我们将使用相应的 DAO 函数将项目和下一个偏移值存储在我们的数据库中。在这里我们需要在事务块中执行所有数据库交互以便如果任何交互失败数据库不会发生任何更改。 最后如果在数据库调用之后一切顺利我们将返回 MediatorResult.Success通过将最新加载返回的项目数与我们将在配置中定义的页面大小进行比较来检查是否已达到分页的末尾。 Pager 对象 现在我们要再次回到我们数据层的 Hilt 模块并创建我们的 Pager 对象。这个对象将把我们到目前为止所定义的所有内容整合在一起作为 PagingData 流的构造函数工作。 Module InstallIn(SingletonComponent::class) class DataModule {// ...ProvidesSingletonfun providePokemonPager(pokemonDatabase: PokemonDatabase,pokemonApi: PokemonApi,): PagerInt, PokemonEntity {return Pager(config PagingConfig(pageSize 20),remoteMediator PokemonRemoteMediator(pokemonDatabase pokemonDatabase,pokemonApi pokemonApi,),pagingSourceFactory {pokemonDatabase.pokemonDao.pagingSource()},)} }在这里我们向 Pager 的构造函数提供了三个要素。首先我们设置了所需的页面大小的 PagingConfig正如我之前提到的。其次我们提供了我们的远程中介器实例。第三我们将由 Room 提供的分页源设置为 Pager 的唯一数据源。 仓库Repository 由于我们在远程中介器中完成了大部分工作所以我们的仓库实现将相当简单。 class PokemonRepositoryImpl Inject constructor(private val pokemonPager: PagerInt, PokemonEntity ) : PokemonRepository {override fun getPokemonList(): FlowPagingDataPokemon {return pokemonPager.flow.map { pagingData -pagingData.map { it.toPokemon() }}} }使用我们的 Pager 实例我们只需将其 PagingData 流返回给使用者。但在这之前我们还需要将 PokemonEntity 映射到领域的 Pokemon 模型。这是因为根据 Clean Architecture 的基础我们的领域层不了解数据或表示层因此不应将数据模型传递到领域层。 领域层Domain Layer 在这个纯 Kotlin 层中实际上没有太多事情发生。在这里我们有我们的 Pokemon 模型、仓库接口以及与该仓库交互的简单用例类。 // REPOSITORY INTERFACE interface PokemonRepository {fun getPokemonList(): FlowPagingDataPokemon }// USE CASE class GetPokemonList Inject constructor(private val pokemonRepository: PokemonRepository ) {operator fun invoke(): FlowPagingDataPokemon {return pokemonRepository.getPokemonList().flowOn(Dispatchers.IO)} }// MODEL data class Pokemon(val id: Int,val name: String,val imageUrl: String, )在这里你可能会有一个问题即如何在纯 Kotlin 层中使用PagingData而在这里我们没有依赖于任何 Android 组件。实际上很简单分页库为非 Android 模块提供了特定的依赖项因此我们可以访问所有简单的 Paging 组件如 PagingSource、PagingData、Pager甚至是 RemoteMediator。 dependencies {// ...implementation androidx.paging:paging-common:$paging_version }表示层Presentation Layer 在快速涵盖了领域层之后让我们直接跳入表示层其中的关键内容都在这里。但首先我们需要将以下 Paging 依赖项添加到我们的 build.gradle 文件中 dependencies {// ...implementation androidx.paging:paging-runtime-ktx:$paging_versionimplementation androidx.paging:paging-compose:$paging_version }除了 runtime-ktx 依赖项之外这里还需要 compose 依赖项因为它在我们的分页数据流和 UI 之间提供了一些中间件。 ViewModel 这又是本文中的一个简单类在这里我们只需获取由用例提供的流该流已由仓库提供并将其存储在一个值中。 HiltViewModel class PokemonListViewModel Inject constructor(private val getPokemonList: GetPokemonList ) : ViewModel() {val pokemonPagingDataFlow: FlowPagingDataPokemon getPokemonList().cachedIn(viewModelScope) }我们通过调用cachedIn(viewModelScope)来存储该流以便在 ViewModel 的生命周期内保持其活动状态。此外它还可以在屏幕旋转等配置更改时保持存活这样你就可以获取相同的现有数据而不必从头开始获取。 这种方法还可以保持我们的冷流状态不变并且不会像 stateIn(…) 方法一样将其转换为热流StateFlow。这意味着如果流未被收集就不会执行不必要的代码。 屏幕UI 现在我们来到了分页的最后一步在这一步中我们将在LazyColumn中显示我们的分页项。在 Jetpack Compose 中不再有 RecyclerView 或适配器。所有这些都在下面进行处理而且我们大量的项目仍然可以智能布局而不会引起任何性能问题。 Composable fun PokemonListScreen(snackbarHostState: SnackbarHostState ) {val viewModel hiltViewModelPokemonListViewModel()val pokemonPagingItems viewModel.pokemonPagingDataFlow.collectAsLazyPagingItems()if (pokemonPagingItems.loadState.refresh is LoadState.Error) {LaunchedEffect(key1 snackbarHostState) {snackbarHostState.showSnackbar((pokemonPagingItems.loadState.refresh as LoadState.Error).error.message ?: )}}Box(modifier Modifier.fillMaxSize()) {if (pokemonPagingItems.loadState.refresh is LoadState.Loading) {CircularProgressIndicator(modifier Modifier.align(Alignment.Center))} else {LazyColumn(modifier Modifier.fillMaxSize(),horizontalAlignment Alignment.CenterHorizontally,) {items(count pokemonPagingItems.itemCount,key pokemonPagingItems.itemKey { it.id },) { index -val pokemon pokemonPagingItems[index]if (pokemon ! null) {PokemonItem(pokemon,modifier Modifier.fillMaxWidth(),)}}item {if (pokemonPagingItems.loadState.append is LoadState.Loading) {CircularProgressIndicator(modifier Modifier.padding(16.dp))}}}}} }在我们的组合屏幕中首先要做的是创建我们的 ViewModel 实例并使用辅助函数 collectAsLazyPagingItems() 收集其中存储的分页数据流。这将冷流转换为 LazyPagingItems 实例。通过这个实例我们可以访问已加载的项目以及不同的加载状态以相应地改变 UI。除此之外我们甚至可以使用此实例触发数据刷新或重新尝试以前失败的加载。 在 Box 布局中如果 LazyPagingItems 的“refresh”加载状态为 Loading则我们知道我们正在初始加载并且尚无项目可显示。因此我们显示一个进度指示器。否则我们会显示一个 LazyColumn以及使用我们的 LazyPagingItems 实例设置的项目列表的数量和键参数。在每个项目中我们只需使用给定的索引访问相应的 Pokemon 对象并呈现 PokemonItem 组合出于简单起见这里不给出实现细节。 我们还有一种特殊情况即需要在这些项目下方显示加载指示器。这发生在我们正在获取更多数据的过程中可以通过 LazyPagingItems 的“append”加载状态来检测到。因此如果是这种情况我们将一个进度指示器追加到列表的末尾。 最后请不要认为我们在开始部分忽略了LaunchedEffect部分。LaunchedEffect 组合用于在组合内部安全地调用挂起函数。在 Jetpack Compose 中我们需要协程范围来显示 Snackbar因为 SnackbarHostState.showSnackbar(…) 是一个挂起函数。在这里我们显示一个 Snackbar 消息以防刷新错误基本上对应于我们的情况下的“初始加载”错误。然而正如我之前提到的我们在这里构建了一个离线优先的应用因此如果我们在 Room 中已经缓存了数据用户将看到该数据以及错误消息。 希望您在 Android Jetpack Compose 中的分页和缓存的这段具有挑战性的旅程中能够与我同行。我尽力坚持最新和推荐的操作方式。请随时指出错误或可以做得更好的地方。整个项目已经作为 GitHub 存储库共享以便您可以下载并进行测试。 GitHub https://github.com/thunderbolt-codes/Pokemon-Pager
http://www.yingshimen.cn/news/135977/

相关文章:

  • 免费如何创建网站平台网页界面设计中表单的组成部分有那四种
  • 去网站做dnf代练要押金吗聊城网站建设代理商
  • 建站网站模板下载爱站seo工具包
  • 绿色企业网站保定模板建站哪家好
  • 常州专业网站建设公司咨询淘宝优惠券网站怎么做的
  • 互联网站账户e服务平台天津搜索引擎推广
  • 重庆网站建设联系电话万维网的代表网站
  • 有没有什么推荐的网站专业做外贸网站的公司
  • 建设网站需要的关键技术园林企业建设网站
  • 做网站设计的价格做二手平台公益的网站
  • 全球建筑网站德清县城乡建设局网站
  • 互联网网站设计工业软件公司排名
  • 开发一个app要多久贵州seo排名
  • 用shopify 做网站江华网站建设
  • 网站建设调研提纲住建局网站信息化建设
  • 大连建设网节能办公室网站电脑网页设计培训
  • 怎么做网站需求分析二建证从住房建设厅网站调出流程
  • 嘉兴 网站建设视频网站备案怎么做
  • 怎呀做网站长沙有实力seo优化公司
  • 容城县建设银行网站网站建设的优缺点
  • 新余网页制作公司推动防控措施持续优化
  • 山西免费网站建设深圳php网站开发
  • 北京网站建设定制网站开发可选择的方案有哪些
  • 效果型网站建设湘潭建设路街道网站
  • 上海哪家公司做网站好免费制作视频的软件手机版
  • 营销型网站具备的二大能力德洲网站建设
  • 如何在网站做投票电商网站建设技术交流问题
  • 顺德品牌网站建设公司购物网站平台建设
  • 凡科网站开发京东网上商城书店官网
  • 专业广州网站设计不用登录的传奇游戏