blog

Googleは、プロジェクトでsealedとRemoteMediatorを使うことを推奨している!

この記事を読み始める前に、PokemonGoの最新のコードを更新し、コードと一緒に読むことをお勧めします。 Flowをベースに成否処理ロジックをカプセル化する方法について、何人かの人から質問を受けたの...

Apr 28, 2020 · 13 min. read
シェア

この記事はポケモンの特徴の一部を包括的に分析したもので、以下の内容が含まれています:

  • Flowの上で成功や失敗の処理をカプセル化するには?
  • RemoteMediatorをカスタマイズして、データベースやネットワークの負荷データを実装するには?
  • Paging3におけるRemoteMediatorとPagingSourceの違いは何ですか?
  • Paging3のcachedInとは何ですか?どのような問題を解決するのですか?

この記事を読み始める前に、PokemonGoの最新のコードを更新し、コードと一緒に読むことをお勧めします、スペースを節約するために、記事ではコアコードのみを記載します。

フローの上に成功または失敗の処理をカプセル化する方法

何人かの人は、どのようにFlowの基礎の上に成功または失敗の処理ロジックをカプセル化するか、この問題について、私に尋ねてきた、実際には、GoogleのAndroidチームのエンジニアは、 Sealed with a class 使用を推奨し、Paging3のソースコードでは、 sealedの 使用も 多いという 記事を発表した媒体で。

カプセル化ロジックを分析するために、まずPaging3のソースコードがどのようにカプセル化ロジックを扱っているかを見てみましょう。 Paging3にはRemoteMediatorという重要なクラスがあり、RemoteMediatorにはload()という重要なメソッドがあります。

abstract suspend fun load(loadType: LoadType, state: PagingState<Key, Value>): MediatorResult
sealed class MediatorResult {
 class Error(val throwable: Throwable) : MediatorResult()
 class Success(
 @get:JvmName("endOfPaginationReached") val endOfPaginationReached: Boolean
 ) : MediatorResult()
}

実際、MediatorResultは、成功と失敗のロジックをカプセル化したErrorとSuccessの2つのサブクラスを持つシーリングされたクラスです。

分析のPaging3ネットワークの実践と原則のJetpackの新しいメンバーで 、別のクラスLoadStateを見て - ネットワークリクエストの 記事の 状態にも 言及 リフレッシュを聞いて 、プリペンドとアペンドはLoadStateオブジェクトは、LoadStateのソースコードの実装を見てください。

sealed class LoadState( val endOfPaginationReached: Boolean) {
 class NotLoading( endOfPaginationReached: Boolean) :LoadState(endOfPaginationReached) {
 ......
 }
 object Loading : LoadState(false) {
 ......
 }
 class Error(val error: Throwable) : LoadState(false) {
 ......
 }
}
Errorローディングの失敗を示します。
Loadingローディング中であることを示します。
NotLoading現在ロードされていないことを示します。
sealed class PokemonResult<out T> {
 data class Success<out T>(val value: T) : PokemonResult<T>()
 data class Failure(val throwable: Throwable?) : PokemonResult<Nothing>()
}
override suspend fun featchPokemonInfo(name: String): Flow<PokemonResult<PokemonInfoModel>> {
 return flow {
 try {
 
 emit(PokemonResult.Success(model)) //  
 } catch (e: Exception) {
 emit(PokemonResult.Failure(e.cause)) //  
 }
 }.flowOn(Dispatchers.IO) // flowOn経由でioスレッドに切り替える
}
  • リクエストに成功したら PokemonResult.Success(model)
  • エラーが発生した場合は PokemonResult.Failure(e.cause)

これは単純なパッケージですが、2つ目のパッケージの異なるシナリオのために、これをベースにすることができ、次にViewModelで処理する方法を見てみましょう。

PokemonGo/app/src/main/java/com/hi/dhl/pokemon/ui/detail/DetailViewModel.kt

when (result) {
 is PokemonResult.Failure -> {
 _failure.value = result.throwable?.message ?: "failure"
 }
 is PokemonResult.Success -> {
 _pokemon.postValue(result.value)
 }
}

ポケモンプロジェクトでは、ネットがなく詳細ページに移動すると、失敗のトーストがポップアップ表示されます。

when式は強力ですが、問題があります、ネットワーク要求のプロジェクトでは、多くの場所があります、when式を記述するたびに、繰り返しのコードが多くなるので、どのようにそのようなテンプレートコードを減らすには、Kotlinを使用することができます強力な拡張関数を提供し、コードは次のとおりです:

inline fun <reified T> PokemonResult<T>.doSuccess(success: (T) -> Unit) {
 if (this is PokemonResult.Success) {
 success(value)
 }
}
inline fun <reified T> PokemonResult<T>.doFailure(failure: (Throwable?) -> Unit) {
 if (this is PokemonResult.Failure) {
 failure(throwable)
 }
}

さらなるカプセル化のために拡張関数を使う目的は、定型的なコードを減らし、when式の使い方を少し見直すことです。

result.doFailure { throwable ->
 _failure.value = throwable?.message ?: "failure"
}
result.doSuccess { value ->
 _pokemon.postValue(value)
 emit(value)
}

他の場所で成功処理や失敗処理を行う必要がある場合は、対応する拡張関数を呼び出せばよい。 以上で、Flowをベースに成功処理や失敗処理をカプセル化する方法についての分析を終了します。

次のステップでは、 RemoteMediatorをカスタマイズしてデータベースとネットワークローディングのデータを実装する方法を分析します。

  • Jetpack Member Paging3 データベースの実践とソースコードの分析
  • Jetpackの新メンバー、Paging3 ネットワーキングの実践とその理由

RemoteMediatorは、主にロードネットワークのページングデータを達成するために使用され、データベースへの更新は、分析の初めに、まず基本的な概念を理解するために。

Paging3クラスの機能

  • PagingData : ページングデータのコンテナで、データが更新されるたびに対応する別のPagingDataが存在します。
  • PagingDataAdapter : ページングデータを処理するリサイクル可能なビューアダプタで、 AsyncPagingDataDiffer コンポーネントを使用して独自のカスタムアダプタを作成できます。
  • PagingSource :各 PagingSource オブジェクトは、データソースとそのデータソースからデータを検索する方法を定義します。
  • RemoteMediator: RemoteMediator は、ウェブページングデータの読み込みとデータベースへの更新を行います。

RemoteMediatorとPagingSourceはどちらもデータソースからデータをロードするために使用されます。

RemoteMediator PagingSourceとの違い

  • RemoteMediator:ネットワークページングデータのロードとデータベースへの更新を実装しますが、データソースの変更を直接UIにマッピングすることはできません。
  • PagingSource:1つのデータソースと、そのソースからデータを見つける方法(例えばRoom)を実装し、データソースへの変更が直接UIにマッピングされます。

上のイメージはGoogleの公式サイトに掲載されているもので、ご覧の通り、この実装ではRemoteMediatorを使ってウェブからページングデータをロードしてデータベースに更新し、PagingSourceを使ってデータベースからデータをルックアップしてUIに表示しています。

プロジェクトの選択はどのように行われるのですか?

  • RemoteMediator:主にネットワークページングデータをロードし、データベースに更新するために使用され、それ以上のデータがない場合、ネットワークからより多くのデータを要求し、より多くのデータを保存するときにPagingSourceと組み合わせると、直接UIにマッピングされます。

注目してください:

  1. OptInアノテーションを使用する場合は、Appモジュールの下のbuild.gradleファイルに以下のコードを追加する必要があります。

    android {
     kotlinOptions {
     freeCompilerArgs += ["-Xopt-in=kotlin.RequiresOptIn"]
     }
    }
    

基本的な概念を理解した後、RemoteMediatorの実装方法を分析します。ここでは、最新のPokemonGoコードを更新し、プロジェクトのコードと一緒に見ることをお勧めします

3段階の実装 RemoteMediator

上のイメージのように、RemoteMediatorを介してRepositoryでWebページングデータを取得し、データベースに更新し、PagingSourceは、より多くのデータが保存されたときに直接UIにマッピングされます。

実際、RemoteMediatorの実装は、DataSource、Repository、ViewModelにまたがっているので、各レイヤーでRemoteMediatorを実装する方法を3つのステップで分析してみましょう。

データソースの定義

@Dao
interface PokemonDao {
 @Insert(onConflict = OnConflictStrategy.REPLACE)
 suspend fun insertPokemon(pokemonList: List<PokemonEntity>)
 @Query("SELECT * FROM PokemonEntity")
 fun getPokemon(): PagingSource<Int, PokemonEntity>
}
  • PagingSource<Key, Value>getPokemon()メソッドは 、を返すことに注意してください。これは、データソースへの更新がUIにマッピングされることを意味し、後で説明するように、KeyとValueはRemoteMediatorの実装に大きく関係しています。

リポジトリへのRemoteMediatorの実装

注目してください:

PagingSource<Int, PokemonEntity> RemoteMediator<Key, Value> PagingSource<Int, PokemonEntity> 今、データソースにgetPokemon()メソッドを定義しました。

@OptIn(ExperimentalPagingApi::class)
class PokemonRemoteMediator(
 val api: PokemonService,
 val db: AppDataBase
) : RemoteMediator<Int, PokemonEntity>() {
 override suspend fun load(
 loadType: LoadType,
 state: PagingState<Int, PokemonEntity>
 ): MediatorResult {
 /**
 * このメソッドでは3つのことが行われる。
 *
 * 1. パラメータLoadTypeには3つの値がある。
 * LoadType.REFRESH
 * LoadType.PREPEND
 * LoadType.APPEND
 *
 * 2. ネットワークデータについて聞く
 *
 * 3. ウェブデータをローカルデータベースに挿入する
 */
 }
}

load() メソッドには 2 つの重要なパラメータがあります:

  • PagingState: このクラスには2つの重要な変数があります。

    • pages: List<Page<Key, Value>> 前のページから返されたデータは、主に次のページの開始位置として前のページの最後のデータを取得するために使用されます。
    • config: PagingConfig が返す PagingConfig で、 pageSize、prefetchDistance、initialLoadSize などの初期化設定を含みます。
  • LoadType は列挙型クラスで、次の 3 つの値を定義します。

    LoadType.Refreshでの初期化リフレッシュの使用
    LoadType.Appendより多くのロード時に使用
    LoadType.Prepend現在のリストヘッダにデータを追加するときに使用します。

load()の返り値はMediatorResultで、これは結果に応じて異なる値を返すシールされたクラスです。

  • リクエストにエラーが発生しました。 config: PagingConfig
  • リクエストが成功し、データがある場合は、以下を返します。 MediatorResult.Error(e)
  • リクエストは成功しましたが、データがありません。 MediatorResult.Success(endOfPaginationReached = true)
  • パラメータ endOfPaginationReached は、データがまだあるかどうかを示します。

1. LoadType パラメータを決定する方法2. ネットワーク・データを要求する 方法 、および3. ネットワーク・データをローカル・データベースに挿入する方法 です

1.パラメータLoadTypeの決定方法

val pageKey = when (loadType) {
 // 最初のアクセスまたは呼び出し PagingDataAdapter.refresh()
 LoadType.REFRESH -> null
 // 現在ロードされているデータセットの先頭でデータをロードする場合
 LoadType.PREPEND -> return MediatorResult.Success(endOfPaginationReached = true)
 LoadType.APPEND -> { // 読み込みが進むとトリガーがかかる
 /**
 * 方法1:この方法は比較的単純で、現在のページの最後のデータが次のページの開始位置となる。
 * ロード・メソッドのパラメータ状態によって、現在のページの最後のデータを取得する。
 */
// val lastItem = state.lastItemOrNull()
// if (lastItem == null) {
// return MediatorResult.Success(
// endOfPaginationReached = true
// )
// }
// lastItem.page
 /**
 * 方法2:さらに厄介なことに、現在のページングデータはリモートキーに対応していない!
 */
 val remoteKey = db.withTransaction {
 db.remoteKeysDao().getRemoteKeys(remotePokemon)
 }
 if (remoteKey == null || remoteKey.nextKey == null) {
 return MediatorResult.Success(endOfPaginationReached = true)
 }
 remoteKey.nextKey
 }
}
  • LoadType.REFRESH:最初の訪問 または PagingDataAdapter.refresh() トリガを呼び出すと、データの最初のページをロードし、ここで任意の操作を行う必要はありません、nullを返します。
  • MediatorResult.Success(endOfPaginationReached = true) LoadType.PREPEND:現在のリストのヘッダーにデータを追加するときにトリガーされます。LoadType.REFRESHがトリガーされると、LoadType.PREPENDもトリガーされることに注意する必要があります。
  • MediatorResult.Success(endOfPaginationReached = true) LoadType.APPEND:次のページを読み込む際に発生し、ここで次のページのキーを取得します。キーが存在しない場合は、直接戻り、リクエストは行われません。

2.ネットワークデータを要求

val page = pageKey ?: 0
val result = api.fetchPokemonList(
 state.config.pageSize,
 page * state.config.pageSize
).results

withContext(Dispatcher.IO) { ... } Retrofitのコプロセッシングはワーカースレッドで行われるので、ここでコールする必要はありません。

3.ウェブページングデータとデータベースへの更新

remoteKeysDao.insertAll(entity)
pokemonDao.insertPokemon(item) 

RemoteMediator を実装するすべてのクラスは、load() メソッドをオーバーライドし、 load() メソッド内で上記の 3 つのステップを実行する必要があります。

PokemonRemoteMediatorのフルコードは長すぎてここに掲載できませんので、 PokemonRemoteMediatorを クリックして先に進んで確認してください。

リポジトリにページャーを構築

Pager(
 config = pageConfig,
 remoteMediator = PokemonRemoteMediator(api, db)
) {
 db.pokemonDao().getPokemon()
}.flow.map { pagingData ->
 pagingData.map { mapper2ItemMolde.map(it) }
}
  • config : ページャーのパラメータ pageSize, prefetchDistance, initialLoadSize などを初期化します。
  • remoteMediator: RemoteMediatorの実装クラスを提供します。
  • db.pokemonDao().getPokemon()pagingSourceFactory : はラムダ式で、Kotlinでは花かっこで直接表現できます。花かっこの中でページングデータの読み込みを実行し、ここでは直接 .
  • getPokemon()メソッドの呼び出しはPagingSourceを返し、PokemonRemoteMediatorでデータベースの更新時に要求されたWebページングデータを取得するために使用されます。

RemoteMediatorをカスタマイズしてデータベースとネットワークローディングのデータを実装する方法の 分析が完了したので、次のステップはデータを取得するためにViewModelでRepositoryを呼び出すことです。

ViewModelでのデータ取得

fun postOfData(): LiveData<PagingData<PokemonItemModel>> =
 polemonRepository.featchPokemonList().cachedIn(viewModelScope).asLiveData()

ご覧のように、ViewModelではわずか2行のコードで、DataBindingと併用することで、アクティビティやフラグメントでは20行以下のコードで済みます。

注: cachedIn() メソッドは、ViewModel の postOfData メソッドで呼び出されます。

Paging3のcachedInとは何ですか?どのような問題を解決するのですか?

Flow<PagingData> Flow<PagingData> cachedIn()は、.Flowによって返されるコンテンツをキャッシュするために使用される拡張機能です。 cachedIn()は、Flowを使用したマップまたはフィルタ操作の後に呼び出され、それらが再びトリガーされる必要がないようにします。

fun <T : Any> Flow<PagingData<T>>.cachedIn(
 scope: CoroutineScope
) 

Flow<PagingData> androidx.lifecycle.viewModelScope.cachedIn()は.NETの拡張であることがお分かりいただけると思います。 cachedIn()メソッドはCoroutineScopeを受け付けますが、これは連結のスコープを表し、ViewModelでは.NETに相当します。

記事の全文はここで終わりです。Kotlin Flow + DataBinding + Jetpack + MVVMの組み合わせを一度は体験することを強くお勧めします。

PokemonGo Jetpack + MVVM + Repository + Data Mapper + Kotlin Flowをベースにして、PokemonGoプロジェクトのシナリオももっと設計して、Jetpackのメンバーも増やして、PokemonGoプロジェクトのホームページに更新ログを追加しました。以下のリンクからPokemonGoプロジェクトの更新履歴に移動できます。

PokemonGo GitHubアドレス: PokemonGo

結論

は、戦闘プロジェクトや関連コンポーネントの原理分析の記事の中で最も完全かつ最新のAndroidX Jetpack関連コンポーネントを構築する過程で、現在すでにアプリの起動、Paging3、ヒルトなどが含まれ、徐々に他のJetpackの新しいメンバーを追加している、倉庫は更新し続け、あなたがチェックするために行くことができます:AndroidX-。Jetpackの練習は、このリポジトリがあなたに有用である場合、私はのようなポイントを助けるために倉庫の右隅にしてください。

Read next

フロントエンドのセキュリティ(XSS攻撃、CSRF攻撃)

XSS攻撃Cross- XSSと略されるコードインジェクション攻撃です。攻撃者は、悪意のあるスクリプトをターゲットのウェブサイトに注入することで、ユーザーのブラウザ上で実行させます。これらの悪意のあるスクリプトを使用して、攻撃者はユーザーの機密情報を取得することができます。

Apr 27, 2020 · 12 min read