SHOEISHA iD

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

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

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

一歩進んだAndroidアプリ開発ができる「Android Jetpack」入門

「ページングライブラリ」でネット上のリストデータをページングする方法

一歩進んだAndroidアプリ開発ができる「Android Jetpack」入門 第13回

JavaでのPagingSourceクラスの作り方

 KotlinでのPagingSourceクラスの作り方が一段落したところで、Java版に話を移すことにします。

JavaではListenableFuturePagingSourceを継承

 Java版のPhonePagingSourceは、リスト5のコードになります。

[リスト5]Java版PhonePagingSourceのコード
public class PhonePagingSource extends ListenableFuturePagingSource<Integer, Phone> {  // (1)
  @NonNull
  @Override
  public ListenableFuture<LoadResult<Integer, Phone>> loadFuture(@NonNull LoadParams<Integer> loadParams) {  // (2)
    LoadResult<Integer, Phone> returnVal;  // (3)
    ExecutorService executorService = Executors.newSingleThreadExecutor();
    try {
      int startKey = 1;
      if(loadParams.getKey() != null) {
        startKey = loadParams.getKey();
      }
      PhoneListSizeFetcher phoneListSizeFetcher = new PhoneListSizeFetcher();  // (4)
      Future<Integer> phoneListSizeFuture = executorService.submit(phoneListSizeFetcher);  // (5)
      int phoneListSize = phoneListSizeFuture.get();  // (5)
      int endKey = startKey + loadParams.getLoadSize() - 1;
      if(endKey > phoneListSize) {
        endKey = phoneListSize;
      }
      PhoneListFetcher phoneListFetcher = new PhoneListFetcher(startKey, endKey);  // (6)
      Future<List<Phone>> phoneListFuture = executorService.submit(phoneListFetcher);  // (7)
      List<Phone> extractedPhoneList = phoneListFuture.get();  // (7)
      Integer prevKey = null;
      if(startKey - loadParams.getLoadSize() > 0) {
        prevKey = startKey - loadParams.getLoadSize();
      }
      Integer nextKey = null;
      if(endKey + 1 < phoneListSize) {
        nextKey = endKey + 1;
      }
      returnVal = new LoadResult.Page<>(extractedPhoneList, prevKey, nextKey);  // (8)
    }
    catch(Exception ex) {
      returnVal = new LoadResult.Error<>(ex);  // (9)
    }

    return Futures.immediateFuture(returnVal);  // (10)
  }

  @Nullable
  @Override
  public Integer getRefreshKey(@NonNull PagingState<Integer, Phone> pagingState) {
    Integer returnVal = null;
    Integer anchorPosition = pagingState.getAnchorPosition();
    if(anchorPosition != null) {
      Phone phone = pagingState.closestItemToPosition(anchorPosition);
      if(phone != null) {
        PagingConfig config = pagingState.getConfig();
        int returnKey = phone.id.intValue() - config.pageSize / 2;
        if(returnKey > 0) {
          returnVal = returnKey;
        }
      }
    }
    return returnVal;
  }
}

 基本的には、KotlinコードをJavaコードに置き換えた内容になっていますが、いくつか注意点があります。

 まず、一番の違いは親クラスです。Kotlinではネットアクセスなどの非同期処理にコルーチンを利用できます。そのため、load()メソッドはsuspend関数となっており、内部でのネットアクセス関数も、suspend関数として用意できます。

 一方、Javaにはそういった仕組みはありませんが、必ず非同期処理に対応しておく必要があります。そのため、リスト5の(1)のように、親クラスを、PagingSourceではなく、ListenableFutureでラップされたListenableFuturePagingSourceとします。ListenableFuturePagingSourceも、PagingSource同様に抽象クラスであり、抽象メソッドを2個オーバーライドする必要があります。そのうち、getRefreshKey()メソッドは、シグネチャも内部の処理コードもKotlinと変わりません。

loadFuture()の戻り値

 一方、もうひとつの抽象メソッドは、リスト5の(2)のように、load()ではなくloadFuture()と名称が変わっています。

 さらに、名称が変わっただけではなく、その戻り値もLoadResultからListenableFuture<LoadResult>に、すなわち、LoadResultをListenableFutureでラップする必要があります。これこそが、まさに、Kotlinでのsuspend関数の代わりといえます。

 リスト5では、Kotlinコード同様に(3)でLoadResult型のreturnValを用意しておき、tryブロックの最後の(8)で無事リストデータが取得できた場合であるLoadResult.Pageインスタンスを格納します。一方で、catchブロック内の(9)で失敗した場合であるLoadResult.Errorインスタンスを格納しています。

 ただし、このreturnValをListenableFutureでラップするために、(10)でFuturesクラスのimmediateFuture()メソッドを利用しています。

immediateFuture()の注意点

 このimmediateFuture()を利用することで、簡単に引数をListenableFutureでラップすることができます。ただし、このメソッドを利用する場合は、引数で渡す値が、引数で渡すタイミングで確実に確定されているコードパターンでのみ使用し、immediateFuture()の実行タイミングでは、非同期処理は全て終了していることを前提にしてください。

 リスト5では(4)でnewしているPhoneListSizeFetcherクラスは、Callableインターフェースを実装したクラスであり、リスト2の(2)で実行しているsuspend関数であるfetchPhoneListSize()の代わりとなるものです。それを非同期実行しているのが(5)のexecutorService.submit()であり、その結果として取得したFutureオブジェクトであるphoneListSizeFutureのget()メソッドを実行することで、非同期処理が終了するのを待った上で、戻り値の全リストサイズを取得できます。

 同様に、(6)でnewしているPhoneListFetcherクラスは、同じく、Callableインターフェースを実装したクラスであり、リスト2の(5)で実行しているsuspend関数であるfetchPhoneList()の代わりとなるものです。その後の(7)のコードは、(5)同様に、PhoneListFetcherを非同期実行した上で、その非同期処理が終了するのを待った上でリストデータを取得することができます。

 これらこのコードパターンのおかげで、(7)のコードが終了した時点で非同期処理が解消されていることになり、immediateFuture()が利用しても問題とならないようになります。ただし、この場合、それぞれの非同期処理、すなわち、PhoneListSizeFetcherクラス内処理、および、PhoneListFetcherクラス内処理にそれほど時間がかからないことを前提としています。もし処理時間が長時間になる場合は、(5)のget()、および(7)のget()で処理がブロックされてしまうので注意してください。

まとめ

 Android Jetpackについて紹介していく本連載の第13回は、いかがでしたでしょうか。

 今回は、ページングライブラリを利用して、ネット上にあるリストデータをページングする方法を紹介しました。次回は、ページングライブラリを利用して、ネット上のリストデータをデータベースにキャッシュして利用する方法を紹介します。

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

連載通知を行うには会員登録(無料)が必要です。
既に会員の方はを行ってください。
一歩進んだAndroidアプリ開発ができる「Android Jetpack」入門連載記事一覧

もっと読む

この記事の著者

WINGSプロジェクト 齊藤 新三(サイトウ シンゾウ)

WINGSプロジェクトについて>有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。 2026年時点での登録メンバは約50名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂きたい。著書記事多数。 RSS X: @WingsPro_info(公式)、@WingsPro_info/wings(メンバーリスト) Facebook <個人紹介>WINGSプロジェクト所属のテクニカルライター。Web系製作会社のシステム部門、SI会社を経てフリーランスとして独立。屋号はSarva(サルヴァ)。HAL大阪の非常勤講師を兼務。

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

山田 祥寛(ヤマダ ヨシヒロ)

静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for Visual Studio and Development Technologies。執筆コミュニティ「WINGSプロジェクト」代表。主な著書に「独習シリーズ(Java・C#・Python・PHP・Ruby・JSP&サーブレットなど)」「速習シリーズ(ASP.NET Core・Vue.js・React・TypeScript・ECMAScript、Laravelなど)」「改訂3版JavaScript本格入門」「これからはじめるLaravel実践入門」「はじめてのAndroidアプリ開発 Kotlin編 」他、著書多数

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

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

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/21204 2025/03/27 11:00

イベント

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

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

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

メールバックナンバー