Slide 1

Slide 1 text

YUMEMI.grow Mobile #9 ©2023 RAKUS Co., Ltd. Jetpack ComposeとPaging3で実現する Endless Grid Design YUMEMI.grow Mobile #9 2023/12/13 @akkiee76

Slide 2

Slide 2 text

YUMEMI.grow Mobile #9 自己紹介 ● Akihiko Sato ● Rakus Inc. ● 楽楽精算 ● @akkiee76 / X 2

Slide 3

Slide 3 text

YUMEMI.grow Mobile #9 アジェンダ 1. ページングライブラリの概要 2. 基本的なコンポーネントの説明 3. ページングライブラリを使った Endless GridDesign の実装方法 4. 状態管理 5. まとめ 3

Slide 4

Slide 4 text

YUMEMI.grow Mobile #9 ページングライブラリ概要 大規模なデータセットからデータのページを ローカルストレージやネットワーク経由で読み込んで 表示することができるライブラリです 4

Slide 5

Slide 5 text

YUMEMI.grow Mobile #9 ページングライブラリの主な機能 ● ページングデータに対するメモリ内キャッシュ ● リクエスト重複排除 ● 画面スクロールに連動した際に自動的なリクエスト ● ページングの状態管理 ● Coroutine、Flow、LiveData、RxJava に対応 リソースを効率的にしたページングデータの操作を実現 5

Slide 6

Slide 6 text

YUMEMI.grow Mobile #9 ページングライブラリの主なコンポーネント ● PagingData ● Pager ● PagingSource ● PagingConfig ● LazyPagingItems 6

Slide 7

Slide 7 text

YUMEMI.grow Mobile #9 ページングライブラリの主なコンポーネント ● PagingData ○ ページングされたデータのコンテナ ● Pager ● PagingSource ● PagingConfig ● LazyPagingItems 7

Slide 8

Slide 8 text

YUMEMI.grow Mobile #9 ページングライブラリの主なコンポーネント ● PagingData ● Pager ○ PagingDataのリアクティブストリームの生成を行うクラス ● PagingSource ● PagingConfig ● LazyPagingItems 8

Slide 9

Slide 9 text

YUMEMI.grow Mobile #9 ページングライブラリの主なコンポーネント ● PagingData ● Pager ● PagingSource ○ 特定のページクエリのデータチャンクを読み込むためのクラス ● PagingConfig ● LazyPagingItems 9

Slide 10

Slide 10 text

YUMEMI.grow Mobile #9 ページングライブラリの主なコンポーネント ● PagingData ● Pager ● PagingSource ● PagingConfig ○ ページング動作を決定するパラメータを定義するクラス ● LazyPagingItems 10

Slide 11

Slide 11 text

YUMEMI.grow Mobile #9 ページングライブラリの主なコンポーネント ● PagingData ● Pager ● PagingSource ● PagingConfig ● LazyPagingItems ○ PagingData のフローから取得したデータを表示するためのクラス 11

Slide 12

Slide 12 text

YUMEMI.grow Mobile #9 サンプルアプリ 主に使用しているライブラリなど ● paging 3 ● paging-compose 3 ● coil ● Unsplash Image API https://github.com/akkie76/Paging-App-Sample 12

Slide 13

Slide 13 text

YUMEMI.grow Mobile #9 アーキテクチャ 13

Slide 14

Slide 14 text

YUMEMI.grow Mobile #9 Repository 14

Slide 15

Slide 15 text

YUMEMI.grow Mobile #9 Repository class UnsplashSearchRepositoryImpl @Inject constructor( private val service: UnsplashService ): UnsplashSearchRepository { override fun searchPhotos(query: String): Flow> { return Pager( config = PagingConfig(enablePlaceholders = false, pageSize = PAGE_SIZE), pagingSourceFactory = { UnsplashPagingSource(service, query) } ).flow } companion object { private const val PAGE_SIZE = 25 } } 15

Slide 16

Slide 16 text

YUMEMI.grow Mobile #9 Repository class UnsplashPagingSource( private val service: UnsplashService, private val query: String ) : PagingSource() { override suspend fun load(params: LoadParams): LoadResult { val page = params.key ?: UNSPLASH_STARTING_PAGE_INDEX return try { val response = service.searchPhotos(query, page, params.loadSize) val photos = response.results LoadResult.Page( data = photos, prevKey = if (page == UNSPLASH_STARTING_PAGE_INDEX) null else page - 1, nextKey = if (page == response.totalPages) null else page + 1 ) } catch (exception: Exception) { LoadResult.Error(exception) } } 16

Slide 17

Slide 17 text

YUMEMI.grow Mobile #9 ViewModel 17

Slide 18

Slide 18 text

YUMEMI.grow Mobile #9 ViewModel @HiltViewModel class MainViewModel @Inject constructor( searchRepository: UnsplashSearchRepository ): ViewModel() { val photos = searchRepository.searchPhotos(QUERY).cachedIn(viewModelScope) companion object { private const val QUERY = "fruit" } } 18

Slide 19

Slide 19 text

YUMEMI.grow Mobile #9 UI Layer 19

Slide 20

Slide 20 text

YUMEMI.grow Mobile #9 UI Layer @Composable fun MainScreen(viewModel: MainViewModel) { Scaffold { padding -> val lazyPagingItems = viewModel.photos.collectAsLazyPagingItems() LazyVerticalGrid( columns = GridCells.Adaptive(minSize = 128.dp) ) { items( count = lazyPagingItems.itemCount, key = lazyPagingItems.itemKey() ) { index -> val photo = lazyPagingItems[index] ?: return@items AsyncImage( model = photo.urls.small, contentDescription = null, modifier = Modifier.fillMaxWidth().height(128.dp), contentScale = ContentScale.FillWidth ) } } 20

Slide 21

Slide 21 text

YUMEMI.grow Mobile #9 とっても簡単 🎉 21

Slide 22

Slide 22 text

YUMEMI.grow Mobile #9 ページングライブラリの状態管理 CombinedLoadStates 型で「読み込み状態」にアクセスすることが可能 ● LoadStates.append ○ ユーザーの現在位置より後に取得されるアイテムの LoadState ● LoadStates.prepend ○ ユーザーの現在位置より前に取得されるアイテムの LoadState ● LoadStates.refresh ○ 初期読み込みの LoadState 22

Slide 23

Slide 23 text

YUMEMI.grow Mobile #9 ページングライブラリの状態管理 LoadState から取得できる状態 ● LoadState.Loading ○ アイテムを読み込み中 ● LoadState.NotLoading ○ アイテムを読み込んでいない(初期状態も含まれる) ● LoadState.Error ○ 読み込みエラーが発生 23

Slide 24

Slide 24 text

YUMEMI.grow Mobile #9 UI Layer(CircularProgress) when (lazyPagingItems.loadState.refresh) { is LoadState.Loading -> { Column( modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, ) { Text( modifier = Modifier.padding(8.dp), text = "Loading" ) CircularProgressIndicator( modifier = Modifier.padding(top = 8.dp), color = Color.LightGray ) } } else -> {} } 24

Slide 25

Slide 25 text

YUMEMI.grow Mobile #9 状態管理も簡単 💪 25

Slide 26

Slide 26 text

YUMEMI.grow Mobile #9 まとめ ページングライブラリを使うと、 シンプルな実装でリソースを効率化したエンドレススクロールが実現可能です。 画像読み込みライブラリも合わせて使うことで、 デザインの幅も広げることも可能です。 26

Slide 27

Slide 27 text

YUMEMI.grow Mobile #9 参考 ● Android ページングの基本 ● ページングライブラリの概要 27

Slide 28

Slide 28 text

YUMEMI.grow Mobile #9 Thank you ! 28