SHOEISHA iD

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

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

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

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

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

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

 APIから取得したデータをDBに保存してキャッシュをする場合の実装は「APIから取得したデータを表示するだけの場合」よりも難しくなります。実装する順番としては以下になります。

  1. DBに保存するKeyとAnimeのエンティティを定義する
  2. DBからデータを取り出し、PagingSourceを返却するメソッドをもつDaoを実装する
  3. APIから取得したデータをキャッシュするDBを実装する
  4. APIからデータを取得し、DBに保存するRemoteMediatorの実装
  5. PagerのpagingSourceFactoryにDaoで作成したメソッドを指定する

 順を追って説明していきます。完成系はサンプルコードのfeature/remote_mediatorをご覧ください。

APIから取得したデータをキャッシュするDBを実装する

 APIから取得したデータをキャッシュするDBを作る際に、保存しておくデータは2つあります。APIから取得したデータ(今回はAnimeクラス)とデータ取得に使用するpageの保存です。

 pageは保存しなくとも、「APIから取得したデータを表示する」の際に実装したようにstateからpageを取得することができますが、関心の分離の観点から公式ではDBなどに保存することが推奨されています。

 今回はsessionIdなどを用いて、業務で使用するような実践的な実装を紹介するため、DBにpageを保存する手法を用いますがpageを保存せずに、stateからpageを取得することで実装は可能です。

Animeエンティティの実装

 まず初めに、DBに保存するEntityを作っていきます。今回、APIから返却されたAnimeクラスを保存するうえ、実装するクラスを減らすため、AnimeクラスをそのままDBのエンティティとして扱います。

 Animeには一意のidがidフィールドに入っているためそれをPrimaryKeyとして指定します。

 また、別の読み込みのアニメが表示されないようにするため、sessionIdをEntityに追加します。sessionIdはAPIから返却されないため、初期値を設定する必要があり、sessionIdはStringなので、空文字列を指定します。

 その際の注意点として、データクラスなどをDBに保存する際は、データベースに保存できるようにするため、Serializableを実装する必要があります。

 すべてを記述すると長くなるため、ポイントのみ抜粋してAnimeクラスを記述すると以下のようになります。

// テーブル名を"anime"としてEntityに指定する
@Entity(tableName = "anime") 
data class Anime(
    @PrimaryKey
    val id: Int,
    val sessionId: String = "",
    ...
    // Gson等によるコンバートができるようにSerializableを実装させます
): Serializable {
    ....
}

Keyエンティティの実装

 Animeエンティティを作成したところで、keyを保存するためのエンティティとしてPageKeyを作成します。

 PageKeyに保存するデータは、以下の3つです。

  • id:PrimaryKeyとして指定するためのカラムとしてautoGenerateをtrueに指定して用意します。
  • sessionId:読み込みごとにセッションIDを生成しないと、どのタイミングの読み込みかわからないため識別子として用意します。Pagingの実装上は必須ではないですが、あると正しくデータを読み込めるため設定することをおすすめします。
  • nextKey:次の読み込みの際に使用するkeyを保存するため用意します。

 実装例としては以下のようになります。

PageKey.kt
@Entity(tableName = "page_key")
data class PageKey(
    @PrimaryKey(autoGenerate = true)
    val id: Int = 0,
    val sessionId: String,
    val nextKey: Int?,
)

TypeConverterの実装

 DBに保存するエンティティの中に、DBにそのまま保存することができない型がある場合は適宜TypeConverterを実装し、DataBaseに指定する必要があります。次はその実装を行います。

 必要になる場合のサンプルケースとしては以下になります。

@Entity(tableName = "anime") 
data class Anime (
    @PrimaryKey
    val id : String, // <Converter必要なし
    val image: Images, // <Converter必要あり
) {
    data class Images (
        imageUrl: List<String>,
    )
}

 TypeConverterは、Convert対象のクラスからStringへの変換とStringからそのクラスへの変換の2パターン必要になります。そのままDBに保存できない型の分だけTypeConverterを実装してください。似たような実装になりますので、今回は1つのみサンプルコードを記載します。

 Gsonを使う場合のTypeConverterは以下のようになります。

AnimeConverter.kt
@TypeConverter
@JvmStatic
fun imagesToString(data: Anime.Images?): String? {
    return data?.let{ Gson().toJson(it) }
}

@TypeConverter
@JvmStatic
fun stringToImages(data: String?): Anime.Images? {
    if (data == null) return null
    val dataType = object : TypeToken<Anime.Images>() {}.type
    return Gson().fromJson(data, dataType)
}

 余談ではありますが、業務で使用する場合単体テストを記述すると思います。その場合withContextのコンテキストにDispatchers.IOを指定するよりも、CoroutineDispatcherをRemoteMediatorの引数に追加し、それでlaunchするとよりテストが記述しやすくなります。

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

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

  • 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」など、さまざまなカンファレンスを企画・運営しています。

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

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

メールバックナンバー

アクセスランキング

アクセスランキング