graphql-pokemon と Paging Library を使ってアプリを作った

github.com

なかなか GraphQL を使っている Android アプリの実装が見当たらなかったので、自分で作ってみた。 ついでに、Paging Library を使いたかったので使ってみた。 題材は赤・青・緑時代の 151 匹のポケモンに関する情報を取ってくることができる API。

github.com

解説

GraphQL

以前ざっくり紹介したので概要はそちらを見ていただくとして

speakerdeck.com

今回は以下のようなライブラリを使って実装した。

GraphQL を使えるようにするために

  1. build.gradle 書く
  2. graphql.config.json 書く
  3. apollo-codegen で schema.json ダウンロードする
  4. schema.graphql 書く
  5. ビルドしたら apollo-android 経由で API を叩けるようになる

といった流れで設定している。このコミットが該当。

github.com

Paging Library

The paging library makes it easier for your app to gradually load information as needed from a data source, without overloading the device or waiting too long for a big database query.

とのこと。(公式より)

DataSource

データを取得する箇所。3 つ用意されているが、今回は PositionalDataSource を使った。

class PositionalPokemonDataSource constructor(private val apiClient: ApiClient) : PositionalDataSource<PokemonsQuery.Pokemon>() {
    override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<PokemonsQuery.Pokemon>) {
// 本当は first と offset を指定したいが、API 側で提供されていないのでとりあえず足している
        apiClient.getPokemons(params.loadSize + params.startPosition)
                .subscribeBy(
                        onSuccess = {
                            callback.onResult(it.pokemons() ?: emptyList())
                        },
                        onError = {
                            Timber.d(it)
                        }
                )
    }

    override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback<PokemonsQuery.Pokemon>) {
        apiClient.getPokemons(params.requestedLoadSize)
                .subscribeBy(
                        onSuccess = {
                            callback.onResult(it.pokemons()
                                    ?: emptyList(), params.requestedLoadSize)
                        },
                        onError = {
                            Timber.d(it)
                        }
                )
    }

}

最初に loadInitial が呼ばれ、ページング処理をするときに loadRange が呼ばれる。

DataSource.Factory

Factory では DataSource を返すだけ。

class PokemonDataSourceFactory constructor(private val apiClient: ApiClient) : DataSource.Factory<Int, PokemonsQuery.Pokemon>() {
    override fun create(): DataSource<Int, PokemonsQuery.Pokemon> = PositionalPokemonDataSource(apiClient)
}

ViewModel

ViewModel では PagedListBuilder を使って PagedList の LiveData を作る。 RxPagedListBuilder を使うと RxJava の Flowable か Observable でデータを監視することができる。 詳しくは ↓

Paging Library  |  Android Developers

class PokemonViewModel : ViewModel() {

    companion object {
        private const val LOAD_COUNT = 10
    }

    private val config = PagedList.Config.Builder()
            .setEnablePlaceholders(false)
            .setInitialLoadSizeHint(LOAD_COUNT)
            .setPageSize(LOAD_COUNT)
            .build()
    val pokemons: Flowable<PagedList<PokemonsQuery.Pokemon>>

    init {
        pokemons = RxPagedListBuilder(PokemonDataSourceFactory(ApiClient()), config)
                .setFetchScheduler(AndroidSchedulers.mainThread())
                .buildFlowable(BackpressureStrategy.LATEST)
    }

}

感想

graphql-pokemon の API で、ポケモン一覧を取ってくる Query が first しか指定できなくてつらかった。 他の API にしようかと思った(スターウォーズの API とかある)けど、ポケモンのかわいさに勝てなかった。(ウィンディかわいい)

Paging Library はサンプルがわかりにくかったけど、どのクラスで何をするのかを理解したらわかりやすかった。ViewModel でやってた処理が DataSource に移るのでちょっと違和感。