SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

アプリケーション開発の最新トレンド

Androidのページング処理のライブラリ「Paging 3」の移行と実装のポイント

  • X ポスト
  • このエントリーをはてなブックマークに追加

APIから取得したデータをDBに保存してキャッシュをする場合

Repositoryの実装

 そして、APIから取得したデータを表示するだけの場合とPagerで指定する引数が異なります。

 表示するデータを取得するpagingSourceFactoryにAnimeのPagingSourceを返却するDaoのメソッドを指定し、データが不足した際にloadが呼び出されるRemoteMediatorには実装したAnimeMediatorを指定します。

 今回の要件では、Repositoryの引数と返却するPagerの引数は以下のようになります。

class AnimeRepository @Inject constructor(
    private val animeService: AnimeService,
    private val animeDao: AnimeDao,
    private val pageKeyDao: PageKeyDao
) {
    fun getAnime(): Pager<Int, Anime> {
        val sessionId = UUID.randomUUID().toString()

        return Pager(
            config = PagingConfig(
                pageSize = 20,
                prefetchDistance = 80,
                enablePlaceholders = false,
            ),
            remoteMediator = AnimeMediator(
                sessionId = sessionId,
                animeService = animeService,
                animeDao = animeDao,
                pageKeyDao = pageKeyDao
            ),
            pagingSourceFactory = {
                animeDao.getAnimePagingSource(sessionId)
            },
        )
    }
}

 AdapterやFragmentの実装は、共通のものと同じですが、改めて記載しておきます。

 共通の部分の説明でコードの解説をしているため、今回は記載だけに留めます。

ViewModelの実装

AnimeViewModel.kt
// Flowの場合
fun getAnimesAsFlow(): Flow<PagingData<Anime>> =
    animeRepository.getAnimes().flow

// LiveDataの場合
fun getAnimesAsLiveData(): LiveData<PagingData<Anime>> =
    animeRepository.getAnimes().liveData

RecyclerViewでリスト表示するレイアウトの実装

<layout xmlns:tools="http://schemas.android.com/tools"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <variable
            name="anime"
            type="com.example.pagingsampleapp.data.vo.Anime" />
    </data>

    <com.google.android.material.card.MaterialCardView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="8dp">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">
            <ImageView
                android:layout_width="80dp"
                android:layout_height="80dp"
                app:loadImage="@{ anime.images.recommended_url }" />
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@{ anime.title }"/>
        </LinearLayout>
    </com.google.android.material.card.MaterialCardView>
</layout>

AnimeAdapterの実装

AnimeAdapter.kt
class AnimeAdapter : PagingDataAdapter<Anime, AnimeViewHolder>(
    object : DiffUtil.ItemCallback<Anime>() {
        override fun areItemsTheSame(oldItem: Anime, newItem: Anime): Boolean {
            return oldItem.id == oldItem.id
        }

        override fun areContentsTheSame(oldItem: Anime, newItem: Anime): Boolean {
            return oldItem == newItem
        }
    }
) {

    override fun onBindViewHolder(holder: AnimeViewHolder, position: Int) {
        val item = getItem(position)
        holder.binding.anime = item
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AnimeViewHolder =
        AnimeViewHolder(
            DataBindingUtil.inflate(
                LayoutInflater.from(parent.context),
                R.layout.view_anime_title,
                parent,
                false
            )
        )

    class AnimeViewHolder(val binding: ViewAnimeTitleBinding) : RecyclerView.ViewHolder(binding.root)
}

Fragmentの実装

fragment_paging_source.xml
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data></data>

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/anime_recycler"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
</layout>
PagingSourceFragment.kt
val adapter = AnimeAdapter()

binding.animeRecycler.adapter = adapter 

// Flowの場合
lifecycleScope.launch {
    viewModel.getReposAsFlow().collectLatest {
        adapter.submitData(it)
    }
}

// LiveDataの場合
viewModel.getReposAsLiveData().observe(viewLifecycleOwner) {
    lifecycleScope.launch {
        adapter.submitData(it)
    }
}

 これで、APIから取得したデータをDBに保存してキャッシュをする場合の実装は以上になります。試しにアプリを実行してみましょう。

取得したデータをキャッシュする
取得したデータをキャッシュする

 APIから取得したデータを表示するだけの際と同様にRecyclerViewでリスト表示ができています。そしてスクロールすることでページング処理を行えていることが確認できます。

 都合上サンプルアプリと異なり上記の画面キャプチャではサムネイルは非表示にしています。

次のページ
おわりに

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
アプリケーション開発の最新トレンド連載記事一覧

もっと読む

この記事の著者

長濱 伶(ヤフー株式会社)(ナガハマ レイ)

 2000年沖縄県生まれ。 学生時代に計算機シミュレーションを用いた文化の安定性に関する研究に従事。 2021年3月に沖縄工業高等専門学校のメディア情報工学科卒業後、4月にヤフー株式会社に入社。 2021年7月からPayPayフリマのAndroidアプリ開発に携わる。 Twitter: @Fel1Tech

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/15314 2022/02/17 11:00

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング