Ağ ve veritabanı sayfası

Uygulamanızın kullanılabilmesini sağlayarak daha iyi bir kullanıcı deneyimi sağlayın ağ bağlantıları güvenilir olmadığında veya kullanıcı çevrimdışı olduğunda. Paydaşların ihtiyaçlarını aynı anda ağdan ve yerel bir veritabanından sayfa oluşturmak için kullanılır. Bu şekilde, uygulamanız kullanıcı arayüzünü yerel veritabanı önbelleğinden alır ve yeni bir istek gönderir.

Bu kılavuzda, Odada devamlılık hakkında bilgi sahibi olduğunuz varsayılır. kitaplığını ve Sayfalama sekmesinin temel kullanımını kitaplığı.

Veri yüklemelerini koordine etme

Sayfalandırma kitaplığı RemoteMediator bileşeni e-tablo kullanmaktır. RemoteMediator, Çağrı kitaplığından sinyal görevi görür Uygulamada önbelleğe alınan veriler kalmadığında. Web sitenizdeki öğeleri yüklemek için ağdan ek veriler toplar ve bu verileri yerel veritabanında saklar. Burada bir PagingSource bunu yükleyebilir ve bunu, görüntülenecek kullanıcı arayüzüne sağlayın.

Ek veri gerektiğinde, Sayfalama kitaplığı load() yöntemi, kaynak: RemoteMediator uygulanması. Bu, güvenli olduğu için bir askıya alma işlevidir bir ekip olarak düşünebilirsiniz. Bu işlev genellikle yeni verileri ve bunu yerel depolama alanına kaydeder.

Bu işlem yeni verilerle çalışır ancak zaman içinde veritabanında depolanan veriler Örneğin, kullanıcının manuel olarak yenilemeyi tetiklemesi gibi durumlarda geçersiz kılma işlemi gerekir. Bu LoadType ile temsil edilir özelliği, load() yöntemine iletildi. LoadType, RemoteMediator: Mevcut verileri yenilemenin mi yoksa getirme mi gerekli? mevcut listenin başına veya sonuna eklenmesi gereken ek veriler.

Bu şekilde RemoteMediator, uygulamanızın uygun sırada görüntülemek istediklerini belirtir.

Sayfalandırma yaşam döngüsü

Şekil 1. PagingSource ile Sayfalama'nın yaşam döngüsü şeması PagingData.

PagingSource, doğrudan ağdan sayfalama yaparken verileri yükler ve şunu döndürür: LoadResult nesnesini tanımlayın. PagingSource uygulaması Pager - pagingSourceFactory parametresi.

Kullanıcı arayüzü tarafından yeni veriler gerektiği için Pager, load() yöntemini kullanarak PagingSource ve şunu akışı olarak döndürür: PagingData nesne yeni verileri kapsamalıdır. Her PagingData nesnesi genellikle Gösterilecek kullanıcı arayüzüne gönderilmeden önce ViewModel.

Şekil 2. PagingSource ile Sayfalama'nın yaşam döngüsü şeması RemoteMediator'u tıklayın.

RemoteMediator bu veri akışını değiştirir. PagingSource, verileri yüklemeye devam eder; ancak sayfalandırılmış veriler tükendiğinde, Sayfalama kitaplığı Ağ kaynağından yeni veri yüklemek için RemoteMediator. RemoteMediator yeni verileri yerel veritabanında saklar. Böylece, ViewModel gerekli değil. Son olarak PagingSource kendini geçersiz kılar ve Pager, veritabanından yeni verileri yüklemek için yeni bir örnek oluşturur.

Temel kullanım

Uygulamanızın, öğe içeren veya öğeler içeren bir reklamdan User öğe içeren sayfaları yüklemesini istediğinizi varsayalım ağ veri kaynağını Oda veritabanında depolanan yerel bir önbelleğe aktarmanızı sağlar.

RemoteMediator, ağdan veri tabanına veri yükler ve
    PagingSource, verileri veritabanından yükler. Çağrı cihazı hem
    RemoteMediator ve PagingSource'u kullanarak sayfalandırılmış verileri yükleyin.
Şekil 3. Katmanlı veri kullanan bir Sayfalandırma uygulamasının şeması kaynak.

RemoteMediator uygulaması, sayfalandırılmış verilerin ağdan ancak verileri doğrudan kullanıcı arayüzüne yüklemez. Bunun yerine, uygulama veri kaynağı olarak doğru. Yani uygulama yalnızca veritabanında önbelleğe alınan verileri gösterir. PagingSource uygulama (örneğin, Oda tarafından oluşturulan bir uygulama) önbelleğe alınan verileri yüklemeyi işler veri tabanından arayüze aktarmasını sağlar.

Oda varlıkları oluşturma

İlk adım, Odada kalıcılık kitaplığını depolayan bir veritabanı ağ veri kaynağındaki sayfalandırılmış verilerin yerel önbelleğidir. Statik web sitelerini RoomDatabase uygulaması Aşağıdakileri kullanarak verileri yerel veritabanına kaydedin: Oda.

Ardından, aşağıda açıklandığı gibi liste öğelerinden oluşan bir tablo temsil edecek bir Room varlığı tanımlayın Oda varlıklarını kullanarak veri tanımlama. Birincil anahtar olarak bir id alanı ve bilgi edinin.

Kotlin

@Entity(tableName = "users")
data class User(val id: String, val label: String)

Java

@Entity(tableName = "users")
public class User {
  public String id;
  public String label;
}

Java

@Entity(tableName = "users")
public class User {
  public String id;
  public String label;
}

Ayrıca bu Oda varlığı için şu şekilde bir veri erişim nesnesi (DAO) tanımlamanız gerekir: Odayı kullanarak verilere erişme DAO'lar. Liste öğesinin DAO'su varlık aşağıdaki yöntemleri içermelidir:

  • Tabloya öğe listesi ekleyen insertAll() yöntemidir.
  • Sorgu dizesini parametre olarak alan ve Sonuç listesi için PagingSource nesnesi. Bu şekilde, bir Pager nesnesi bu tabloyu sayfalı veri kaynağı olarak kullanın.
  • Tablodaki tüm verileri silen bir clearAll() yöntemi.

Kotlin

@Dao
interface UserDao {
  @Insert(onConflict = OnConflictStrategy.REPLACE)
  suspend fun insertAll(users: List<User>)

  @Query("SELECT * FROM users WHERE label LIKE :query")
  fun pagingSource(query: String): PagingSource<Int, User>

  @Query("DELETE FROM users")
  suspend fun clearAll()
}

Java

@Dao
interface UserDao {
  @Insert(onConflict = OnConflictStrategy.REPLACE)
  void insertAll(List<User> users);

  @Query("SELECT * FROM users WHERE mLabel LIKE :query")
  PagingSource<Integer, User> pagingSource(String query);

  @Query("DELETE FROM users")
  int clearAll();
}

Java

@Dao
interface UserDao {
  @Insert(onConflict = OnConflictStrategy.REPLACE)
  void insertAll(List<User> users);

  @Query("SELECT * FROM users WHERE mLabel LIKE :query")
  PagingSource<Integer, User> pagingSource(String query);

  @Query("DELETE FROM users")
  int clearAll();
}

Bir RemoteMediator uygulayın

RemoteMediator ana rolü, aşağıdaki durumlarda ağdan daha fazla veri yüklemektir: ya Pager verileri biter ya da mevcut veriler geçersiz kılınır. Google Yüklemeyi tanımlamak için geçersiz kılmanız gereken bir load() yöntemi içerir gösterir.

Tipik bir RemoteMediator uygulaması aşağıdaki parametreleri içerir:

  • query: Arka uçtan hangi verilerin alınacağını tanımlayan bir sorgu dizesi geliştirmenizi sağlar.
  • database: Yerel önbellek işlevi gören Oda veritabanı.
  • networkService: Arka uç hizmeti için bir API örneği.

Bir RemoteMediator<Key, Value> uygulaması oluşturun. Key türü ve Value türü, PagingSource (aynı ağ veri kaynağı için). Daha fazla bilgi için Tür parametrelerini seçmek için Anahtar ve değer seçme türler olarak tanımlar.

Kotlin

@OptIn(ExperimentalPagingApi::class)
class ExampleRemoteMediator(
  private val query: String,
  private val database: RoomDb,
  private val networkService: ExampleBackendService
) : RemoteMediator<Int, User>() {
  val userDao = database.userDao()

  override suspend fun load(
    loadType: LoadType,
    state: PagingState<Int, User>
  ): MediatorResult {
    // ...
  }
}

Java

@UseExperimental(markerClass = ExperimentalPagingApi.class)
class ExampleRemoteMediator extends RxRemoteMediator<Integer, User> {
  private String query;
  private ExampleBackendService networkService;
  private RoomDb database;
  private UserDao userDao;

  ExampleRemoteMediator(
    String query,
    ExampleBackendService networkService, RoomDb database
  ) {
    query = query;
    networkService = networkService;
    database = database;
    userDao = database.userDao();
  }

  @NotNull
  @Override
  public Single<MediatorResult> loadSingle(
    @NotNull LoadType loadType,
    @NotNull PagingState<Integer, User> state
  ) {
    ...
  }
}

Java

class ExampleRemoteMediator extends ListenableFutureRemoteMediator<Integer, User> {
  private String query;
  private ExampleBackendService networkService;
  private RoomDb database;
  private UserDao userDao;
  private Executor bgExecutor;

  ExampleRemoteMediator(
    String query,
    ExampleBackendService networkService,
    RoomDb database,
    Executor bgExecutor
  ) {
    this.query = query;
    this.networkService = networkService;
    this.database = database;
    this.userDao = database.userDao();
    this.bgExecutor = bgExecutor;
  }

  @NotNull
  @Override
  public ListenableFuture<MediatorResult> loadFuture(
    @NotNull LoadType loadType,
    @NotNull PagingState<Integer, User> state
  ) {
    ...
  }
}

load() yöntemi, yedekleme veri kümesini güncellemekten ve PagingSource öğesi geçersiz kılınıyor. Sayfalara ayırmayı destekleyen bazı kitaplıklar (ör. Oda) yapılandırdıkları PagingSource nesneyi otomatik olarak geçersiz kılacak. yardımcı olur.

load() yöntemi iki parametre alır:

load() yönteminin döndürülen değeri MediatorResult nesnesini tanımlayın. MediatorResult şunlardan biri olabilir: MediatorResult.Error (hata açıklamasını içerir) veya MediatorResult.Success (Yüklenecek daha fazla veri olup olmadığını belirten bir sinyal içerir).

load() yöntemi, aşağıdaki adımları gerçekleştirmelidir:

  1. Bağlantının yüküne ve şekline bağlı olarak ağdan hangi sayfanın yükleneceğini belirleyin yüklenen verilerdir.
  2. Ağ isteğini tetikleyin.
  3. Yükleme işleminin sonucuna göre işlemleri gerçekleştirin:
    • Yükleme başarılı olursa ve alınan öğeler listesi boş değilse liste öğelerini veritabanında depolayıp MediatorResult.Success(endOfPaginationReached = false) Verilerden sonra öğesinin Sayfalama kitaplığını bilgilendirmek için veri kaynağını geçersiz kılın yeni veriler oluşturabilirsiniz.
    • Yükleme başarılıysa ve alınan öğe listesi boşsa olursa, o son sayfa dizini ise MediatorResult.Success(endOfPaginationReached = true) Veriler başladıktan sonra yeni tarayıcının Sayfalama kitaplığını bildirmek için veri kaynağını geçersiz kılın verileri.
    • İstek bir hataya neden olursa MediatorResult.Error değerini döndürün.

Kotlin

override suspend fun load(
  loadType: LoadType,
  state: PagingState<Int, User>
): MediatorResult {
  return try {
    // The network load method takes an optional after=<user.id>
    // parameter. For every page after the first, pass the last user
    // ID to let it continue from where it left off. For REFRESH,
    // pass null to load the first page.
    val loadKey = when (loadType) {
      LoadType.REFRESH -> null
      // In this example, you never need to prepend, since REFRESH
      // will always load the first page in the list. Immediately
      // return, reporting end of pagination.
      LoadType.PREPEND ->
        return MediatorResult.Success(endOfPaginationReached = true)
      LoadType.APPEND -> {
        val lastItem = state.lastItemOrNull()

        // You must explicitly check if the last item is null when
        // appending, since passing null to networkService is only
        // valid for initial load. If lastItem is null it means no
        // items were loaded after the initial REFRESH and there are
        // no more items to load.
        if (lastItem == null) {
          return MediatorResult.Success(
            endOfPaginationReached = true
          )
        }

        lastItem.id
      }
    }

    // Suspending network load via Retrofit. This doesn't need to be
    // wrapped in a withContext(Dispatcher.IO) { ... } block since
    // Retrofit's Coroutine CallAdapter dispatches on a worker
    // thread.
    val response = networkService.searchUsers(
      query = query, after = loadKey
    )

    database.withTransaction {
      if (loadType == LoadType.REFRESH) {
        userDao.deleteByQuery(query)
      }

      // Insert new users into database, which invalidates the
      // current PagingData, allowing Paging to present the updates
      // in the DB.
      userDao.insertAll(response.users)
    }

    MediatorResult.Success(
      endOfPaginationReached = response.nextKey == null
    )
  } catch (e: IOException) {
    MediatorResult.Error(e)
  } catch (e: HttpException) {
    MediatorResult.Error(e)
  }
}

Java

@NotNull
@Override
public Single<MediatorResult> loadSingle(
  @NotNull LoadType loadType,
  @NotNull PagingState<Integer, User> state
) {
  // The network load method takes an optional after=<user.id> parameter. For
  // every page after the first, pass the last user ID to let it continue from
  // where it left off. For REFRESH, pass null to load the first page.
  String loadKey = null;
  switch (loadType) {
    case REFRESH:
      break;
    case PREPEND:
      // In this example, you never need to prepend, since REFRESH will always
      // load the first page in the list. Immediately return, reporting end of
      // pagination.
      return Single.just(new MediatorResult.Success(true));
    case APPEND:
      User lastItem = state.lastItemOrNull();

      // You must explicitly check if the last item is null when appending,
      // since passing null to networkService is only valid for initial load.
      // If lastItem is null it means no items were loaded after the initial
      // REFRESH and there are no more items to load.
      if (lastItem == null) {
        return Single.just(new MediatorResult.Success(true));
      }

      loadKey = lastItem.getId();
      break;
  }

  return networkService.searchUsers(query, loadKey)
    .subscribeOn(Schedulers.io())
    .map((Function<SearchUserResponse, MediatorResult>) response -> {
      database.runInTransaction(() -> {
        if (loadType == LoadType.REFRESH) {
          userDao.deleteByQuery(query);
        }

        // Insert new users into database, which invalidates the current
        // PagingData, allowing Paging to present the updates in the DB.
        userDao.insertAll(response.getUsers());
      });

      return new MediatorResult.Success(response.getNextKey() == null);
    })
    .onErrorResumeNext(e -> {
      if (e instanceof IOException || e instanceof HttpException) {
        return Single.just(new MediatorResult.Error(e));
      }

      return Single.error(e);
    });
}

Java

@NotNull
@Override
public ListenableFuture<MediatorResult> loadFuture(
  @NotNull LoadType loadType,
  @NotNull PagingState<Integer, User> state
) {
  // The network load method takes an optional after=<user.id> parameter. For
  // every page after the first, pass the last user ID to let it continue from
  // where it left off. For REFRESH, pass null to load the first page.
  String loadKey = null;
  switch (loadType) {
    case REFRESH:
      break;
    case PREPEND:
      // In this example, you never need to prepend, since REFRESH will always
      // load the first page in the list. Immediately return, reporting end of
      // pagination.
      return Futures.immediateFuture(new MediatorResult.Success(true));
    case APPEND:
      User lastItem = state.lastItemOrNull();

      // You must explicitly check if the last item is null when appending,
      // since passing null to networkService is only valid for initial load.
      // If lastItem is null it means no items were loaded after the initial
      // REFRESH and there are no more items to load.
      if (lastItem == null) {
        return Futures.immediateFuture(new MediatorResult.Success(true));
      }

      loadKey = lastItem.getId();
      break;
  }

  ListenableFuture<MediatorResult> networkResult = Futures.transform(
    networkService.searchUsers(query, loadKey),
    response -> {
      database.runInTransaction(() -> {
        if (loadType == LoadType.REFRESH) {
          userDao.deleteByQuery(query);
        }

        // Insert new users into database, which invalidates the current
        // PagingData, allowing Paging to present the updates in the DB.
        userDao.insertAll(response.getUsers());
      });

      return new MediatorResult.Success(response.getNextKey() == null);
    }, bgExecutor);

  ListenableFuture<MediatorResult> ioCatchingNetworkResult =
    Futures.catching(
      networkResult,
      IOException.class,
      MediatorResult.Error::new,
      bgExecutor
    );

  return Futures.catching(
    ioCatchingNetworkResult,
    HttpException.class,
    MediatorResult.Error::new,
    bgExecutor
  );
}

İlk kullanıma hazırlama yöntemini tanımlama

RemoteMediator uygulamaları, initialize() yöntemini kullanarak önbelleğe alınan verilerin güncel olup olmadığını kontrol edebilir ve uzaktan yenilemenizi sağlar. Bu yöntem herhangi bir yükleme yapılmadan önce çalışır, böylece herhangi birini tetiklemeden önce veritabanını değiştirmek (örneğin, eski verileri temizlemek için) yerel veya uzak yüklemeler.

initialize() eşzamansız bir fonksiyon olduğundan, Veri tabanındaki mevcut verilerin alaka düzeyini belirlemek. En yaygın önbelleğe alınan veriler yalnızca belirli bir süre için geçerli olur. İlgili içeriği oluşturmak için kullanılan RemoteMediator, geçerlilik süresinin dolup dolmadığını kontrol edebilir. Bu durumda, Sayfalama kitaplığının verileri tamamen yenilemesi gerekir. Kullanım alanları initialize(), şu şekilde bir InitializeAction döndürmelidir:

  • Yerel verilerin tamamen yenilenmesinin gerektiği durumlarda initialize() dönmelidir InitializeAction.LAUNCH_INITIAL_REFRESH Bu durum, RemoteMediator ürününün tamamen yeniden yüklemek için uzaktan yenileme yapmasına neden olur. bahsedeceğim. Uzak APPEND veya PREPEND yüklemeleri REFRESH yüklemesini bekler adım adım başarılı olmanızı sağlar.
  • Yerel verilerin yenilenmesinin gerekmediği durumlarda initialize() dönmelidir InitializeAction.SKIP_INITIAL_REFRESH Bu, RemoteMediator ürününün uzaktan yenilemeyi atlayıp önbelleğe alınmış verileri görebilirsiniz.

Kotlin

override suspend fun initialize(): InitializeAction {
  val cacheTimeout = TimeUnit.MILLISECONDS.convert(1, TimeUnit.HOURS)
  return if (System.currentTimeMillis() - db.lastUpdated() <= cacheTimeout)
  {
    // Cached data is up-to-date, so there is no need to re-fetch
    // from the network.
    InitializeAction.SKIP_INITIAL_REFRESH
  } else {
    // Need to refresh cached data from network; returning
    // LAUNCH_INITIAL_REFRESH here will also block RemoteMediator's
    // APPEND and PREPEND from running until REFRESH succeeds.
    InitializeAction.LAUNCH_INITIAL_REFRESH
  }
}

Java

@NotNull
@Override
public Single<InitializeAction> initializeSingle() {
  long cacheTimeout = TimeUnit.MILLISECONDS.convert(1, TimeUnit.HOURS);
  return mUserDao.lastUpdatedSingle()
    .map(lastUpdatedMillis -> {
      if (System.currentTimeMillis() - lastUpdatedMillis <= cacheTimeout) {
        // Cached data is up-to-date, so there is no need to re-fetch
        // from the network.
        return InitializeAction.SKIP_INITIAL_REFRESH;
      } else {
        // Need to refresh cached data from network; returning
        // LAUNCH_INITIAL_REFRESH here will also block RemoteMediator's
        // APPEND and PREPEND from running until REFRESH succeeds.
        return InitializeAction.LAUNCH_INITIAL_REFRESH;
      }
    });
}

Java

@NotNull
@Override
public ListenableFuture<InitializeAction> initializeFuture() {
  long cacheTimeout = TimeUnit.MILLISECONDS.convert(1, TimeUnit.HOURS);
  return Futures.transform(
    mUserDao.lastUpdated(),
    lastUpdatedMillis -> {
      if (System.currentTimeMillis() - lastUpdatedMillis <= cacheTimeout) {
        // Cached data is up-to-date, so there is no need to re-fetch
        // from the network.
        return InitializeAction.SKIP_INITIAL_REFRESH;
      } else {
        // Need to refresh cached data from network; returning
        // LAUNCH_INITIAL_REFRESH here will also block RemoteMediator's
        // APPEND and PREPEND from running until REFRESH succeeds.
        return InitializeAction.LAUNCH_INITIAL_REFRESH;
      }
    },
    mBgExecutor);
}

Çağrı Cihazı oluşturma

Son olarak, sayfalandırılmış veri akışını ayarlamak için bir Pager örneği oluşturmanız gerekir. Bu, basit bir ağ veri kaynağından Pager oluşturmaya benzer ancak farklı şekilde yapmanız gereken iki şey var:

  • Doğrudan bir PagingSource oluşturucuyu iletmek yerine, DAO'dan bir PagingSource nesnesi döndüren sorgu yöntemi.
  • RemoteMediator uygulamanızın bir örneğini remoteMediator parametresinden yararlanın.

Kotlin

val userDao = database.userDao()
val pager = Pager(
  config = PagingConfig(pageSize = 50)
  remoteMediator = ExampleRemoteMediator(query, database, networkService)
) {
  userDao.pagingSource(query)
}

Java

UserDao userDao = database.userDao();
Pager<Integer, User> pager = Pager(
  new PagingConfig(/* pageSize = */ 20),
  null, // initialKey,
  new ExampleRemoteMediator(query, database, networkService)
  () -> userDao.pagingSource(query));

Java

UserDao userDao = database.userDao();
Pager<Integer, User> pager = Pager(
  new PagingConfig(/* pageSize = */ 20),
  null, // initialKey
  new ExampleRemoteMediator(query, database, networkService, bgExecutor),
  () -> userDao.pagingSource(query));

Yarış koşullarını kontrol edin

Uygulamanızın, birden çok cihazdan veri yüklerken ilgilenmesi gereken kaynaklar, önbelleğe alınmış yerel verilerin uzak veri kaynağı.

RemoteMediator uygulamanızdaki initialize() yöntemi döndüğünde LAUNCH_INITIAL_REFRESH, veriler güncel değil ve yenileriyle değiştirilmeleri gerekiyor verileri. PREPEND veya APPEND yükleme istekleri, uzaktan kumandayı beklemeye zorlanır Başarıyla tamamlanması için REFRESH yükleme. PREPEND veya APPEND talepleri REFRESH isteğinden önce sıraya alındığında, PagingState yüklendikleri zamana kadar güncelliğini yitirir.

Verilerin yerel olarak nasıl depolandığına bağlı olarak uygulamanız gereksiz verileri yoksayabilir istekleri için geçerlidir. Örneğin Oda, veri eklemedeki sorguları geçersiz kılar. Bu durumda, yeni Yenilenen verilere sahip PagingSource nesne, bekleyen yüklemeye verilir istekleri için kullanılır.

Bu veri senkronizasyonu sorununun çözülmesi, kullanıcıların web sitenizdeki en alakalı ve en güncel verileri görebilirsiniz. En iyi çözüm büyük ölçüde Bu işlem, ağ veri kaynağının verileri sayfalama şeklidir. Her durumda, uzaktan tuşları kullanarak en son sayfayla ilgili bilgileri kaydedebilirsiniz için sunucu tarafından istekte bulunuldu. Uygulamanız bu bilgileri kullanarak yüklenmesi için doğru veri sayfasını isteme.

Uzaktan tuşları yönetin

Uzaktan tuşlar, bir RemoteMediator uygulamasının kullanımı algılamak için kullandığı anahtarlardır arka uç hizmetinin nasıl yükleneceğini belirleyin. En basit senaryoda, sayfalandırılmış veriler, kolayca başvurabileceğiniz bir uzak anahtar içerir. Ancak, uzaktaki tuşlar tek tek öğelere karşılık gelmez, bu durumda bunları saklamanız gerekir ve bunları load() yönteminizde yönetebilirsiniz.

Bu bölümde, uzaktan kumanda olarak kullanılan uzaktan kumandaların nasıl toplanacağı, depolanacağı ve güncelleneceği ayrı öğelerde depolanmaz.

Öğe anahtarları

Bu bölümde, uzaktan erişim için uzaktaki öğe oluşturabilirsiniz. Genellikle bir API anahtarı tek tek öğelerin devre dışı bırakıldığında Kimlik, sorgu parametresi olarak iletilir. Parametre adı, sunucusu, sağlanan kimlikten önce veya sonra öğelerle yanıt vermelidir. Örnekte User model sınıfı için sunucudaki id alanı, uzaktan kumanda olarak kullanılır. tuşuna basın.

load() yönteminizle öğeye özel uzaktan tuşların yönetilmesi gerektiğinde bu tuşlar genellikle sunucudan getirilen verilerin kimlikleridir. Yenileme işlemleri en yeni verileri aldıklarından yükleme anahtarına ihtiyaç duymazlar. Benzer şekilde, başına ekleme işlemlerinin herhangi bir ek veri getirmesine gerek yoktur çünkü yenileme her zaman sunucudan en yeni verileri çeker.

Ancak, ekleme işlemleri kimlik gerektirir. Bu işlem, her bir sayfa için en son öğesini kaldırın ve sonraki veri sayfasını yüklemek için kimliğini kullanın. Varsa veritabanında öğe yoksa endOfPaginationReached true olarak ayarlanır. Bu gösterge, verilerin yenilenmesi gerektiğini gösterir.

Kotlin

@OptIn(ExperimentalPagingApi::class)
class ExampleRemoteMediator(
  private val query: String,
  private val database: RoomDb,
  private val networkService: ExampleBackendService
) : RemoteMediator<Int, User>() {
  val userDao = database.userDao()

  override suspend fun load(
    loadType: LoadType,
    state: PagingState<Int, User>
  ): MediatorResult {
    return try {
      // The network load method takes an optional String
      // parameter. For every page after the first, pass the String
      // token returned from the previous page to let it continue
      // from where it left off. For REFRESH, pass null to load the
      // first page.
      val loadKey = when (loadType) {
        LoadType.REFRESH -> null
        // In this example, you never need to prepend, since REFRESH
        // will always load the first page in the list. Immediately
        // return, reporting end of pagination.
        LoadType.PREPEND -> return MediatorResult.Success(
          endOfPaginationReached = true
        )
        // Get the last User object id for the next RemoteKey.
        LoadType.APPEND -> {
          val lastItem = state.lastItemOrNull()

          // You must explicitly check if the last item is null when
          // appending, since passing null to networkService is only
          // valid for initial load. If lastItem is null it means no
          // items were loaded after the initial REFRESH and there are
          // no more items to load.
          if (lastItem == null) {
            return MediatorResult.Success(
              endOfPaginationReached = true
            )
          }

          lastItem.id
        }
      }

      // Suspending network load via Retrofit. This doesn't need to
      // be wrapped in a withContext(Dispatcher.IO) { ... } block
      // since Retrofit's Coroutine CallAdapter dispatches on a
      // worker thread.
      val response = networkService.searchUsers(query, loadKey)

      // Store loaded data, and next key in transaction, so that
      // they're always consistent.
      database.withTransaction {
        if (loadType == LoadType.REFRESH) {
          userDao.deleteByQuery(query)
        }

        // Insert new users into database, which invalidates the
        // current PagingData, allowing Paging to present the updates
        // in the DB.
        userDao.insertAll(response.users)
      }

      // End of pagination has been reached if no users are returned from the
      // service
      MediatorResult.Success(
        endOfPaginationReached = response.users.isEmpty()
      )
    } catch (e: IOException) {
      MediatorResult.Error(e)
    } catch (e: HttpException) {
      MediatorResult.Error(e)
    }
  }
}

Java

@NotNull
@Override
public Single>MediatorResult< loadSingle(
  @NotNull LoadType loadType,
  @NotNull PagingState>Integer, User< state
) {
  // The network load method takes an optional String parameter. For every page
  // after the first, pass the String token returned from the previous page to
  // let it continue from where it left off. For REFRESH, pass null to load the
  // first page.
  Single>String< remoteKeySingle = null;
  switch (loadType) {
    case REFRESH:
      // Initial load should use null as the page key, so you can return null
      // directly.
      remoteKeySingle = Single.just(null);
      break;
    case PREPEND:
      // In this example, you never need to prepend, since REFRESH will always
      // load the first page in the list. Immediately return, reporting end of
      // pagination.
      return Single.just(new MediatorResult.Success(true));
    case APPEND:
      User lastItem = state.lastItemOrNull();

      // You must explicitly check if the last item is null when
      // appending, since passing null to networkService is only
      // valid for initial load. If lastItem is null it means no
      // items were loaded after the initial REFRESH and there are
      // no more items to load.
      if (lastItem == null) {
        return Single.just(new MediatorResult.Success(true));
      }
      remoteKeySingle = Single.just(lastItem.getId());
      break;
  }

  return remoteKeySingle
    .subscribeOn(Schedulers.io())
    .flatMap((Function<String, Single<MediatorResult>>) remoteKey -> {
      return networkService.searchUsers(query, remoteKey)
        .map(response -> {
          database.runInTransaction(() -> {
            if (loadType == LoadType.REFRESH) {
              userDao.deleteByQuery(query);
            }
            // Insert new users into database, which invalidates the current
            // PagingData, allowing Paging to present the updates in the DB.
            userDao.insertAll(response.getUsers());
          });

          return new MediatorResult.Success(response.getUsers().isEmpty());
        });
    })
    .onErrorResumeNext(e -> {
      if (e instanceof IOException || e instanceof HttpException) {
        return Single.just(new MediatorResult.Error(e));
      }

      return Single.error(e);
    });
}

Java

@NotNull
@Override
public ListenableFuture<MediatorResult> loadFuture(
  @NotNull LoadType loadType,
  @NotNull PagingState<Integer, User> state
) {
  // The network load method takes an optional after=<user.id> parameter.
  // For every page after the first, pass the last user ID to let it continue
  // from where it left off. For REFRESH, pass null to load the first page.
  ResolvableFuture<String> remoteKeyFuture = ResolvableFuture.create();
  switch (loadType) {
    case REFRESH:
      remoteKeyFuture.set(null);
      break;
    case PREPEND:
      // In this example, you never need to prepend, since REFRESH will always
      // load the first page in the list. Immediately return, reporting end of
      // pagination.
      return Futures.immediateFuture(new MediatorResult.Success(true));
    case APPEND:
      User lastItem = state.lastItemOrNull();

      // You must explicitly check if the last item is null when appending,
      // since passing null to networkService is only valid for initial load.
      // If lastItem is null it means no items were loaded after the initial
      // REFRESH and there are no more items to load.
      if (lastItem == null) {
        return Futures.immediateFuture(new MediatorResult.Success(true));
      }

      remoteKeyFuture.set(lastItem.getId());
      break;
  }

  return Futures.transformAsync(remoteKeyFuture, remoteKey -> {

    ListenableFuture<MediatorResult> networkResult = Futures.transform(
      networkService.searchUsers(query, remoteKey),
      response -> {
        database.runInTransaction(() -> {
        if (loadType == LoadType.REFRESH) {
          userDao.deleteByQuery(query);
        }

        // Insert new users into database, which invalidates the current
        // PagingData, allowing Paging to present the updates in the DB.
        userDao.insertAll(response.getUsers());
      });

      return new MediatorResult.Success(response.getUsers().isEmpty());
    }, bgExecutor);

    ListenableFuture<MediatorResult> ioCatchingNetworkResult =
      Futures.catching(
        networkResult,
        IOException.class,
        MediatorResult.Error::new,
        bgExecutor
      );

    return Futures.catching(
      ioCatchingNetworkResult,
      HttpException.class,
      MediatorResult.Error::new,
      bgExecutor
    );
  }, bgExecutor);
}

Sayfa anahtarları

Bu bölümde, öğe oluşturabilirsiniz.

Uzak anahtar tablosu ekle

Uzak tuşlar liste öğeleriyle doğrudan ilişkilendirilmediğinde bunları yerel veritabanında ayrı bir tabloda saklamanız gerekir. Aşağıdaki özelliklere sahip bir Room varlığı tanımlayın: uzak tuşların bir tablosunu temsil eder:

Kotlin

@Entity(tableName = "remote_keys")
data class RemoteKey(val label: String, val nextKey: String?)

Java

@Entity(tableName = "remote_keys")
public class RemoteKey {
  public String label;
  public String nextKey;
}

Java

@Entity(tableName = "remote_keys")
public class RemoteKey {
  public String label;
  public String nextKey;
}

Ayrıca, RemoteKey varlığı için bir DAO tanımlamanız gerekir:

Kotlin

@Dao
interface RemoteKeyDao {
  @Insert(onConflict = OnConflictStrategy.REPLACE)
  suspend fun insertOrReplace(remoteKey: RemoteKey)

  @Query("SELECT * FROM remote_keys WHERE label = :query")
  suspend fun remoteKeyByQuery(query: String): RemoteKey

  @Query("DELETE FROM remote_keys WHERE label = :query")
  suspend fun deleteByQuery(query: String)
}

Java

@Dao
interface RemoteKeyDao {
  @Insert(onConflict = OnConflictStrategy.REPLACE)
  void insertOrReplace(RemoteKey remoteKey);

  @Query("SELECT * FROM remote_keys WHERE label = :query")
  Single<RemoteKey> remoteKeyByQuerySingle(String query);

  @Query("DELETE FROM remote_keys WHERE label = :query")
  void deleteByQuery(String query);
}

Java

@Dao
interface RemoteKeyDao {
  @Insert(onConflict = OnConflictStrategy.REPLACE)
  void insertOrReplace(RemoteKey remoteKey);

  @Query("SELECT * FROM remote_keys WHERE label = :query")
  ListenableFuture<RemoteKey> remoteKeyByQueryFuture(String query);

  @Query("DELETE FROM remote_keys WHERE label = :query")
  void deleteByQuery(String query);
}

Uzaktan anahtarlarla yükle

load() yönteminizin uzak sayfa anahtarlarını yönetmesi gerektiğinde bunu tanımlamanız gerekir temel kullanımına kıyasla şu farklılıklara sahip RemoteMediator:

  • Sizin için DAO'ya referans veren ek bir mülk ekleyin: uzaktan tuş tablosunu kullanın.
  • Bunun yerine uzak anahtar tablosunu sorgulayarak bir sonraki anahtarın yükleneceğini belirleyin PagingState kullanılıyor.
  • Ağ veri kaynağından döndürülen uzak anahtarı ek olarak sunar.

Kotlin

@OptIn(ExperimentalPagingApi::class)
class ExampleRemoteMediator(
  private val query: String,
  private val database: RoomDb,
  private val networkService: ExampleBackendService
) : RemoteMediator<Int, User>() {
  val userDao = database.userDao()
  val remoteKeyDao = database.remoteKeyDao()

  override suspend fun load(
    loadType: LoadType,
    state: PagingState<Int, User>
  ): MediatorResult {
    return try {
      // The network load method takes an optional String
      // parameter. For every page after the first, pass the String
      // token returned from the previous page to let it continue
      // from where it left off. For REFRESH, pass null to load the
      // first page.
      val loadKey = when (loadType) {
        LoadType.REFRESH -> null
        // In this example, you never need to prepend, since REFRESH
        // will always load the first page in the list. Immediately
        // return, reporting end of pagination.
        LoadType.PREPEND -> return MediatorResult.Success(
          endOfPaginationReached = true
        )
        // Query remoteKeyDao for the next RemoteKey.
        LoadType.APPEND -> {
          val remoteKey = database.withTransaction {
            remoteKeyDao.remoteKeyByQuery(query)
          }

          // You must explicitly check if the page key is null when
          // appending, since null is only valid for initial load.
          // If you receive null for APPEND, that means you have
          // reached the end of pagination and there are no more
          // items to load.
          if (remoteKey.nextKey == null) {
            return MediatorResult.Success(
              endOfPaginationReached = true
            )
          }

          remoteKey.nextKey
        }
      }

      // Suspending network load via Retrofit. This doesn't need to
      // be wrapped in a withContext(Dispatcher.IO) { ... } block
      // since Retrofit's Coroutine CallAdapter dispatches on a
      // worker thread.
      val response = networkService.searchUsers(query, loadKey)

      // Store loaded data, and next key in transaction, so that
      // they're always consistent.
      database.withTransaction {
        if (loadType == LoadType.REFRESH) {
          remoteKeyDao.deleteByQuery(query)
          userDao.deleteByQuery(query)
        }

        // Update RemoteKey for this query.
        remoteKeyDao.insertOrReplace(
          RemoteKey(query, response.nextKey)
        )

        // Insert new users into database, which invalidates the
        // current PagingData, allowing Paging to present the updates
        // in the DB.
        userDao.insertAll(response.users)
      }

      MediatorResult.Success(
        endOfPaginationReached = response.nextKey == null
      )
    } catch (e: IOException) {
      MediatorResult.Error(e)
    } catch (e: HttpException) {
      MediatorResult.Error(e)
    }
  }
}

Java

@NotNull
@Override
public Single<MediatorResult> loadSingle(
  @NotNull LoadType loadType,
  @NotNull PagingState<Integer, User> state
) {
  // The network load method takes an optional String parameter. For every page
  // after the first, pass the String token returned from the previous page to
  // let it continue from where it left off. For REFRESH, pass null to load the
  // first page.
  Single<RemoteKey> remoteKeySingle = null;
  switch (loadType) {
    case REFRESH:
      // Initial load should use null as the page key, so you can return null
      // directly.
      remoteKeySingle = Single.just(new RemoteKey(mQuery, null));
      break;
    case PREPEND:
      // In this example, you never need to prepend, since REFRESH will always
      // load the first page in the list. Immediately return, reporting end of
      // pagination.
      return Single.just(new MediatorResult.Success(true));
    case APPEND:
      // Query remoteKeyDao for the next RemoteKey.
      remoteKeySingle = mRemoteKeyDao.remoteKeyByQuerySingle(mQuery);
      break;
  }

  return remoteKeySingle
    .subscribeOn(Schedulers.io())
    .flatMap((Function<RemoteKey, Single<MediatorResult>>) remoteKey -> {
      // You must explicitly check if the page key is null when appending,
      // since null is only valid for initial load. If you receive null
      // for APPEND, that means you have reached the end of pagination and
      // there are no more items to load.
      if (loadType != REFRESH && remoteKey.getNextKey() == null) {
        return Single.just(new MediatorResult.Success(true));
      }

      return networkService.searchUsers(query, remoteKey.getNextKey())
        .map(response -> {
          database.runInTransaction(() -> {
            if (loadType == LoadType.REFRESH) {
              userDao.deleteByQuery(query);
              remoteKeyDao.deleteByQuery(query);
            }

            // Update RemoteKey for this query.
            remoteKeyDao.insertOrReplace(new RemoteKey(query, response.getNextKey()));

            // Insert new users into database, which invalidates the current
            // PagingData, allowing Paging to present the updates in the DB.
            userDao.insertAll(response.getUsers());
          });

          return new MediatorResult.Success(response.getNextKey() == null);
        });
    })
    .onErrorResumeNext(e -> {
      if (e instanceof IOException || e instanceof HttpException) {
        return Single.just(new MediatorResult.Error(e));
      }

      return Single.error(e);
    });
}

Java

@NotNull
@Override
public ListenableFuture<MediatorResult> loadFuture(
  @NotNull LoadType loadType,
  @NotNull PagingState<Integer, User> state
) {
  // The network load method takes an optional after=<user.id> parameter. For
  // every page after the first, pass the last user ID to let it continue from
  // where it left off. For REFRESH, pass null to load the first page.
  ResolvableFuture<RemoteKey> remoteKeyFuture = ResolvableFuture.create();
  switch (loadType) {
    case REFRESH:
      remoteKeyFuture.set(new RemoteKey(query, null));
      break;
    case PREPEND:
      // In this example, you never need to prepend, since REFRESH will always
      // load the first page in the list. Immediately return, reporting end of
      // pagination.
      return Futures.immediateFuture(new MediatorResult.Success(true));
    case APPEND:
      User lastItem = state.lastItemOrNull();

      // You must explicitly check if the last item is null when appending,
      // since passing null to networkService is only valid for initial load.
      // If lastItem is null it means no items were loaded after the initial
      // REFRESH and there are no more items to load.
      if (lastItem == null) {
        return Futures.immediateFuture(new MediatorResult.Success(true));
      }

      // Query remoteKeyDao for the next RemoteKey.
      remoteKeyFuture.setFuture(
        remoteKeyDao.remoteKeyByQueryFuture(query));
      break;
  }

  return Futures.transformAsync(remoteKeyFuture, remoteKey -> {
    // You must explicitly check if the page key is null when appending,
    // since null is only valid for initial load. If you receive null
    // for APPEND, that means you have reached the end of pagination and
    // there are no more items to load.
    if (loadType != LoadType.REFRESH && remoteKey.getNextKey() == null) {
      return Futures.immediateFuture(new MediatorResult.Success(true));
    }

    ListenableFuture<MediatorResult> networkResult = Futures.transform(
      networkService.searchUsers(query, remoteKey.getNextKey()),
      response -> {
        database.runInTransaction(() -> {
        if (loadType == LoadType.REFRESH) {
          userDao.deleteByQuery(query);
          remoteKeyDao.deleteByQuery(query);
        }

        // Update RemoteKey for this query.
        remoteKeyDao.insertOrReplace(new RemoteKey(query, response.getNextKey()));

        // Insert new users into database, which invalidates the current
        // PagingData, allowing Paging to present the updates in the DB.
        userDao.insertAll(response.getUsers());
      });

      return new MediatorResult.Success(response.getNextKey() == null);
    }, bgExecutor);

    ListenableFuture<MediatorResult> ioCatchingNetworkResult =
      Futures.catching(
        networkResult,
        IOException.class,
        MediatorResult.Error::new,
        bgExecutor
      );

    return Futures.catching(
      ioCatchingNetworkResult,
      HttpException.class,
      MediatorResult.Error::new,
      bgExecutor
    );
  }, bgExecutor);
}

Yerinde yenile

Uygulamanızın yalnızca listenin üst kısmından ağ yenilemelerini desteklemesi gerekiyorsa olduğu durumlarda, RemoteMediator öğesinin başa yükleme davranışını ekleyin.

Ancak uygulamanızın ağdan artımlı yüklemeyi desteklemesi gerekiyorsa sonra sayfalandırma işlemini devam ettirmek için destek sağlamanız gerekir. kullanıcının kaydırma konumunda. Odanın PagingSource cihazı uygulama bunu sizin için halleder, ancak Odayı kullanmıyorsanız geçersiz kılarak bunu PagingSource.getRefreshKey() getRefreshKey() ile ilgili bir örnek uygulama için Sayfa Kaynağı.

Şekil 4'te, öncelikle yerel veritabanından veri yükleme işlemi gösterilmektedir. ve veritabanındaki veriler tükendiğinde ağdan veri gönderir.

PagingSource, veritabanından kullanıcı arayüzüne veri tabanından
    veri kalmadı. Daha sonra RemoteMediator, ağdan
    ve sonrasında PagingSource yüklenmeye devam eder.
4. Şekil. PagingSource ve RemoteMediator'ın nasıl çalıştığını gösteren şema bir araya getirir.

Ek kaynaklar

Sayfalama kitaplığı hakkında daha fazla bilgi edinmek için aşağıdaki ek kaynaklara bakın:

Codelab'ler

Örnekler

ziyaret edin. ziyaret edin.