【Android Architecture Components】Paging Library 和訳

***[Android Architecture Components](https://developer.android.com/topic/libraries/architecture/index.html)の1.0.0 Alpha 9-1から新しく追加されたPagingライブラリのドキュメントを和訳したものになります。***



Pagingライブラリはデバイスに過負荷をかけることなく、またはDBにリクエストをするために長時間待機されることなく、データソースから徐々にデータを取得する事を容易にしてくれます。

## 概要

多くのアプリは多くのデータを使用して動いていますが、画面を表示するのに必要な情報はその一部の小さなデータです。
アプリは何千ものアイテムを表示するかもしれませんが、１度のアクセスでは纏まった少量のデータだけ必要かもしれません。
もし考慮が足りない場合には実際に必要のないデータまで要求してしまう事になり、デバイスとネットワークのパフォーマンスに負担がかかります。
データがリモートDBに保存または同期されている場合は、アプリの処理速度が遅くなり、ユーザーの通信が無駄に浪費される可能性があります。

既存のAndroid APIはコンテンツのページングを許可していましたが、大きな制約と欠点がありました。

* [CursorAdapter](https://developer.android.com/reference/android/widget/CursorAdapter.html)を使用すると、データベースのクエリ結果を[ListView](https://developer.android.com/reference/android/widget/ListView.html)アイテムに簡単にマッピングできます。しかし、データベースのクエリはUIスレッド上で実行され、[Cursor](https://developer.android.com/reference/android/database/Cursor.html)で非効率にページコンテンツを表示します。[CursorAdapter](https://developer.android.com/reference/android/widget/CursorAdapter.html)の使用上の不明点はブログに投稿された[Large Database Queries on Android](https://medium.com/google-developers/large-database-queries-on-android-cb043ae626e8)をみてください。
* [AsyncListUtil](https://developer.android.com/reference/android/support/v7/util/AsyncListUtil.html)は位置ベースのデータを[RecyclerView](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html)にページングすることを可能にしますが、非ページングを許可せず、カウント可能なデータセットにプレースホルダとしてのNULLを強制します。

新しいページングライブラリは、これらの問題に対処します。
このライブラリには、必要なときにデータを要求するプロセスを合理化するためのいくつかのクラスが含まれています。
これらのクラスは、[Room](https://developer.android.com/topic/libraries/architecture/room.html)などの既存のアーキテクチャコンポーネントとシームレスに連携します。

## クラス

Pagingライブラリには、次のクラスと追加のサポートクラスが用意されています。

### [DataSource](https://developer.android.com/reference/android/arch/paging/DataSource.html)

このクラスを使用して、取得する必要があるページデータのデータソースを定義します。データへのアクセス方法に応じて、2つのサブクラスのうち1つを継承します。

* アイテムNのデータを使用してアイテムN + 1をフェッチする必要がある場合は、[KeyedDataSource](https://developer.android.com/reference/android/arch/paging/KeyedDataSource.html)を使用します。たとえば、ディスカッションアプリのスレッドコメントを取得する場合は、次のコメントの内容を取得するために1つのコメントのIDを渡す必要があります。
* データストアで選択した任意の場所からページのデータを取得する必要がある場合は、[TiledDataSource](https://developer.android.com/reference/android/arch/paging/TiledDataSource.html)を使用します。このクラスは、「1200番目から20個のデータを返す」のように、指定した場所からデータのセットを要求することをサポートしています。

もし、[Room](https://developer.android.com/topic/libraries/architecture/room.html)ライブラリでデータ管理をしている場合は、[TiledDataSource](https://developer.android.com/reference/android/arch/paging/TiledDataSource.html)を自動的に生成することができます。

```java
@Query("select * from users WHERE age > :age order by name DESC, id ASC")
TiledDataSource<User> usersOlderThan(int age);
```

### [PagedList](https://developer.android.com/reference/android/arch/paging/PagedList.html)

このクラスは[DataSource](https://developer.android.com/reference/android/arch/paging/DataSource.html)からデータをロードします。あなたは一回の読み込みでどのくらいデータを読み込むか、どのくらいのデータを先読みするかを設定し、ユーザーがデータのロードを待つ時間を最小限に抑えます。
このクラスは、[RecyclerView.Adapter](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html)などの他のクラスに更新を知らせることができ、データがページにロードされるときに[RecyclerView](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html)の内容を更新できるようにします。

### [PagedListAdapter](https://developer.android.com/reference/android/arch/paging/PagedListAdapter.html)

このクラスは、[PagedList](https://developer.android.com/reference/android/arch/paging/PagedList.html)からデータを渡すために[RecyclerView.Adapter](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html)を実装したクラスです。
例えば新しいページをロードした時、[PagedListAdapter](https://developer.android.com/reference/android/arch/paging/PagedListAdapter.html)は[RecyclerView](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html)にデータが来た事を知らせます。
これにより、[RecyclerView](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html)はプレースホルダを実際のアイテムに置き換え、適切なアニメーションを実行できます。
また、[PagedListAdapter](https://developer.android.com/reference/android/arch/paging/PagedListAdapter.html)はバックグラウンドスレッドを使用して、ある[PagedList](https://developer.android.com/reference/android/arch/paging/PagedList.html)から次の[PagedList](https://developer.android.com/reference/android/arch/paging/PagedList.html)への変更を計算して（たとえば、データベースの変更によって更新されたデータで新しいPagedListが生成された場合など）、必要に応じて[notifyItem...()](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html#notifyItemChanged(int))メソッドを呼び出してリストの内容を更新します。
その後、[RecyclerView](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html)は必要な変更を実行します。たとえば、アイテムが[PagedList](https://developer.android.com/reference/android/arch/paging/PagedList.html)バージョン間で位置が変更した場合、[RecyclerView](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html)は、そのアイテムをリスト内の新しい場所にアニメーションして移動します。

### [LivePagedListProvider](https://developer.android.com/reference/android/arch/paging/LivePagedListProvider.html)

このクラスはあなたが提供する[DataSource](https://developer.android.com/reference/android/arch/paging/DataSource.html)から[LiveData](https://developer.android.com/reference/android/arch/lifecycle/LiveData.html)<[PagedList](https://developer.android.com/reference/android/arch/paging/PagedList.html)>を生成します。さらに、DBの管理に[Room](https://developer.android.com/topic/libraries/architecture/room.html)ライブラリを使用している場合、DAOは[TiledDataSource](https://developer.android.com/reference/android/arch/paging/TiledDataSource.html)を使用して[LivePagedListProvider](https://developer.android.com/reference/android/arch/paging/LivePagedListProvider.html)を生成できます。たとえば、次のようになります。

```java
@Query("SELECT * from users order WHERE age > :age order by name DESC, id ASC")
public abstract LivePagedListProvider<Integer, User> usersOlderThan(int age);
```


```Integer``` パラメータは、[Room](https://developer.android.com/topic/libraries/architecture/room.html)に、位置ベースの読み込みで[TiledDataSource](https://developer.android.com/reference/android/arch/paging/TiledDataSource.html)を使用するように指示します。


同時に、Pagingライブラリのコンポーネントは、バックグラウンドスレッドからUIスレッドへのデータフローを構成します。たとえば、新しい項目がDBに挿入されると、[DataSource](https://developer.android.com/reference/android/arch/paging/DataSource.html)は無効になり、[LivePagedListProvider](https://developer.android.com/reference/android/arch/paging/LivePagedListProvider.html)はバックグラウンドスレッドで新しい[PagedList](https://developer.android.com/reference/android/arch/paging/PagedList.html)を生成します。

![paging-threading.gif](https://qiita-image-store.s3.amazonaws.com/0/88858/3eb879a6-2507-e004-a1d3-88b6ab4e2136.gif "paging-threading.gif")

新しく生成された[PagedList](https://developer.android.com/reference/android/arch/paging/PagedList.html)はUIスレッド上で[PagedListAdapter](https://developer.android.com/reference/android/arch/paging/PagedListAdapter.html)に送られます。
[PagedListAdapter](https://developer.android.com/reference/android/arch/paging/PagedListAdapter.html)はバックグラウンドで[DiffUtil](https://developer.android.com/reference/android/support/v7/util/DiffUtil.html)を使って新しいリストと現在のリストの差分を計算します。比較が終わったら[PagedListAdapter](https://developer.android.com/reference/android/arch/paging/PagedListAdapter.html)はリストの差分情報を使用して[RecyclerView.Adapter.notifyItemInserted()]()を適切に呼び出して、新しい項目が挿入されたことを知らせます。

UIスレッドの[RecyclerView](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html)は一つの新しい項目をアニメーションさせながらバインドすることだけを知ります。

次のサンプルコードは、これらのクラスを使った一連のコードを示しています。
ユーザーがデータベースに追加、削除、変更されると、[RecyclerView](https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html)のコンテンツは自動的かつ効率的に更新されます。

```java
@Dao
interface UserDao {
    @Query("SELECT * FROM user ORDER BY lastName ASC")
    public abstract LivePagedListProvider<Integer, User> usersByLastName();
}

class MyViewModel extends ViewModel {
    public final LiveData<PagedList<User>> usersList;
    public MyViewModel(UserDao userDao) {
        usersList = userDao.usersByLastName().create(
                /* initial load position */ 0,
                new PagedList.Config.Builder()
                        .setPageSize(50)
                        .setPrefetchDistance(50)
                        .build());
    }
}

class MyActivity extends AppCompatActivity {
    @Override
    public void onCreate(Bundle savedState) {
        super.onCreate(savedState);
        MyViewModel viewModel = ViewModelProviders.of(this).get(MyViewModel.class);
        RecyclerView recyclerView = findViewById(R.id.user_list);
        UserAdapter<User> adapter = new UserAdapter();
        viewModel.usersList.observe(this, pagedList -> adapter.setList(pagedList));
        recyclerView.setAdapter(adapter);
    }
}

class UserAdapter extends PagedListAdapter<User, UserViewHolder> {
    public UserAdapter() {
        super(DIFF_CALLBACK);
    }
    @Override
    public void onBindViewHolder(UserViewHolder holder, int position) {
        User user = getItem(position);
        if (user != null) {
            holder.bindTo(user);
        } else {
            // Null defines a placeholder item - PagedListAdapter will automatically invalidate
            // this row when the actual object is loaded from the database
            holder.clear();
        }
    }
    public static final DiffCallback<User> DIFF_CALLBACK = new DiffCallback<User>() {
        @Override
        public boolean areItemsTheSame(@NonNull User oldUser, @NonNull User newUser) {
            // User properties may have changed if reloaded from the DB, but ID is fixed
            return oldUser.getId() == newUser.getId();
        }
        @Override
        public boolean areContentsTheSame(@NonNull User oldUser, @NonNull User newUser) {
            // NOTE: if you use equals, your object must properly override Object#equals()
            // Incorrectly returning false here will result in too many animations.
            return oldUser.equals(newUser);
        }
    }
}
```