নেটওয়ার্ক এবং ডাটাবেস থেকে পৃষ্ঠা

নেটওয়ার্ক সংযোগগুলি অবিশ্বস্ত হলে বা ব্যবহারকারী অফলাইনে থাকলে আপনার অ্যাপ ব্যবহার করা যেতে পারে তা নিশ্চিত করে একটি উন্নত ব্যবহারকারীর অভিজ্ঞতা প্রদান করুন৷ এটি করার একটি উপায় হল নেটওয়ার্ক থেকে এবং একই সময়ে স্থানীয় ডাটাবেস থেকে পৃষ্ঠা করা। এইভাবে, আপনার অ্যাপ স্থানীয় ডাটাবেস ক্যাশে থেকে UI চালায় এবং ডাটাবেসে আর কোনো ডেটা না থাকলে শুধুমাত্র নেটওয়ার্কে অনুরোধ করে।

এই গাইডটি অনুমান করে যে আপনি রুম পারসিসটেন্স লাইব্রেরির সাথে পরিচিত এবং পেজিং লাইব্রেরির মৌলিক ব্যবহারের সাথে।

তথ্য লোড সমন্বয়

পেজিং লাইব্রেরি এই ব্যবহারের ক্ষেত্রে RemoteMediator উপাদান প্রদান করে। অ্যাপটির ক্যাশে ডেটা শেষ হয়ে গেলে RemoteMediator পেজিং লাইব্রেরি থেকে একটি সংকেত হিসেবে কাজ করে। আপনি নেটওয়ার্ক থেকে অতিরিক্ত ডেটা লোড করতে এবং স্থানীয় ডাটাবেসে এটি সংরক্ষণ করতে এই সংকেতটি ব্যবহার করতে পারেন, যেখানে একটি PagingSource এটি লোড করতে পারে এবং প্রদর্শনের জন্য UI এ সরবরাহ করতে পারে।

অতিরিক্ত ডেটার প্রয়োজন হলে, পেজিং লাইব্রেরি RemoteMediator বাস্তবায়ন থেকে load() পদ্ধতিতে কল করে। এটি একটি স্থগিত ফাংশন তাই এটি দীর্ঘ-চলমান কাজ সম্পাদন করা নিরাপদ। এই ফাংশনটি সাধারণত একটি নেটওয়ার্ক উত্স থেকে নতুন ডেটা আনে এবং স্থানীয় স্টোরেজে সংরক্ষণ করে।

এই প্রক্রিয়াটি নতুন ডেটার সাথে কাজ করে, কিন্তু সময়ের সাথে সাথে ডাটাবেসে সংরক্ষিত ডেটার অবৈধকরণের প্রয়োজন হয়, যেমন ব্যবহারকারী ম্যানুয়ালি একটি রিফ্রেশ ট্রিগার করে। এটি load() পদ্ধতিতে পাস করা LoadType বৈশিষ্ট্য দ্বারা প্রতিনিধিত্ব করা হয়। LoadType RemoteMediator জানিয়ে দেয় যে এটি বিদ্যমান ডেটা রিফ্রেশ করতে হবে বা অতিরিক্ত ডেটা আনতে হবে যা বিদ্যমান তালিকায় যুক্ত বা প্রিপেন্ড করতে হবে।

এইভাবে, RemoteMediator নিশ্চিত করে যে আপনার অ্যাপ সেই ডেটা লোড করে যা ব্যবহারকারীরা উপযুক্ত ক্রমে দেখতে চায়।

পেজিং জীবনচক্র

চিত্র 1. পেজিং সোর্স এবং পেজিং ডেটা সহ পেজিংয়ের জীবনচক্রের চিত্র।

নেটওয়ার্ক থেকে সরাসরি পেজিং করার সময়, PagingSource ডেটা লোড করে এবং একটি LoadResult অবজেক্ট প্রদান করে। PagingSource ইমপ্লিমেন্টেশন Pager pagingSourceFactory প্যারামিটারের মাধ্যমে পাঠানো হয়।

যেহেতু UI দ্বারা নতুন ডেটার প্রয়োজন হয়, Pager PagingSource থেকে load() পদ্ধতিতে কল করে এবং PagingData অবজেক্টের একটি স্ট্রীম ফেরত দেয় যা নতুন ডেটা এনক্যাপসুলেট করে। প্রতিটি PagingData অবজেক্ট সাধারণত প্রদর্শনের জন্য UI-তে পাঠানোর আগে ViewModel এ ক্যাশে করা হয়।

চিত্র 2. পেজিং সোর্স এবং রিমোটমিডিয়েটরের সাথে পেজিংয়ের জীবনচক্রের চিত্র।

RemoteMediator এই ডেটা প্রবাহ পরিবর্তন করে। একটি PagingSource এখনও ডেটা লোড করে; কিন্তু পেজ করা ডেটা শেষ হয়ে গেলে, পেজিং লাইব্রেরি RemoteMediator নেটওয়ার্ক উৎস থেকে নতুন ডেটা লোড করতে ট্রিগার করে। RemoteMediator নতুন ডেটা স্থানীয় ডাটাবেসে সঞ্চয় করে, তাই ViewModel এ একটি ইন-মেমরি ক্যাশে অপ্রয়োজনীয়। অবশেষে, PagingSource নিজেই অবৈধ হয়ে যায়, এবং Pager ডাটাবেস থেকে নতুন ডেটা লোড করার জন্য একটি নতুন উদাহরণ তৈরি করে।

মৌলিক ব্যবহার

ধরুন আপনি আপনার অ্যাপটি একটি আইটেম-কীড নেটওয়ার্ক ডেটা উৎস থেকে User আইটেমগুলির পৃষ্ঠাগুলিকে একটি রুম ডাটাবেসে সঞ্চিত একটি স্থানীয় ক্যাশে লোড করতে চান৷

RemoteMediator নেটওয়ার্ক থেকে ডাটাবেসে ডাটা লোড করে এবং পেজিং সোর্স ডাটাবেস থেকে ডাটা লোড করে। একটি পেজার পৃষ্ঠাযুক্ত ডেটা লোড করতে রিমোটমিডিয়েটর এবং পেজিং সোর্স উভয়ই ব্যবহার করে।
চিত্র 3. একটি পেজিং বাস্তবায়নের ডায়াগ্রাম যা একটি স্তরযুক্ত ডেটা উৎস ব্যবহার করে।

একটি RemoteMediator বাস্তবায়ন নেটওয়ার্ক থেকে ডাটাবেসে পৃষ্ঠাযুক্ত ডেটা লোড করতে সাহায্য করে, কিন্তু সরাসরি UI-তে ডেটা লোড করে না। পরিবর্তে, অ্যাপটি সত্যের উৎস হিসেবে ডাটাবেস ব্যবহার করে। অন্য কথায়, অ্যাপটি শুধুমাত্র ডাটাবেসে ক্যাশে করা ডেটা প্রদর্শন করে। একটি PagingSource বাস্তবায়ন (উদাহরণস্বরূপ, রুম দ্বারা উত্পন্ন একটি) ডাটাবেস থেকে UI-তে ক্যাশে করা ডেটা লোড করা পরিচালনা করে।

রুম সত্তা তৈরি করুন

প্রথম ধাপ হল রুম পারসিসটেন্স লাইব্রেরি ব্যবহার করে একটি ডাটাবেস সংজ্ঞায়িত করা যা নেটওয়ার্ক ডেটা সোর্স থেকে পেজড ডেটার স্থানীয় ক্যাশে ধারণ করে। Room ব্যবহার করে স্থানীয় ডাটাবেসে ডেটা সংরক্ষণে বর্ণিত RoomDatabase বাস্তবায়ন দিয়ে শুরু করুন।

এর পরে, রুম সত্তা ব্যবহার করে ডেটা সংজ্ঞায়িত করাতে বর্ণিত তালিকা আইটেমগুলির একটি সারণী উপস্থাপন করতে একটি রুম সত্তাকে সংজ্ঞায়িত করুন৷ এটিকে একটি প্রাথমিক কী হিসাবে একটি id ক্ষেত্র দিন, সেইসাথে আপনার তালিকার আইটেমগুলিতে থাকা অন্যান্য তথ্যের জন্য ক্ষেত্রগুলি দিন৷

কোটলিন

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

জাভা

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

জাভা

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

আপনাকে অবশ্যই এই রুম সত্তার জন্য একটি ডেটা অ্যাক্সেস অবজেক্ট (DAO) সংজ্ঞায়িত করতে হবে যেমনটি রুম DAO ব্যবহার করে ডেটা অ্যাক্সেস করাতে বর্ণিত হয়েছে। তালিকা আইটেম সত্তা জন্য DAO নিম্নলিখিত পদ্ধতি অন্তর্ভুক্ত করা আবশ্যক:

  • একটি insertAll() পদ্ধতি যা টেবিলে আইটেমগুলির একটি তালিকা সন্নিবেশ করায়।
  • একটি পদ্ধতি যা একটি প্যারামিটার হিসাবে ক্যোয়ারী স্ট্রিং নেয় এবং ফলাফলের তালিকার জন্য একটি PagingSource অবজেক্ট প্রদান করে। এইভাবে, একটি Pager অবজেক্ট এই টেবিলটিকে পেজড ডেটার উৎস হিসেবে ব্যবহার করতে পারে।
  • একটি clearAll() পদ্ধতি যা টেবিলের সমস্ত ডেটা মুছে দেয়।

কোটলিন

@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()
}

জাভা

@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();
}

জাভা

@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();
}

একটি RemoteMediator প্রয়োগ করুন

RemoteMediator এর প্রধান ভূমিকা হল নেটওয়ার্ক থেকে আরও ডেটা লোড করা যখন হয় Pager ডেটা শেষ হয়ে যায় বা বিদ্যমান ডেটা অবৈধ হয়ে যায়। এটিতে একটি load() পদ্ধতি রয়েছে যা আপনাকে লোডিং আচরণ সংজ্ঞায়িত করতে ওভাররাইড করতে হবে।

একটি সাধারণ RemoteMediator বাস্তবায়নে নিম্নলিখিত পরামিতিগুলি অন্তর্ভুক্ত থাকে:

  • query : ব্যাকএন্ড পরিষেবা থেকে কোন ডেটা পুনরুদ্ধার করতে হবে তা নির্ধারণ করে একটি ক্যোয়ারী স্ট্রিং।
  • database : রুম ডাটাবেস যা স্থানীয় ক্যাশে হিসাবে কাজ করে।
  • networkService : ব্যাকএন্ড পরিষেবার জন্য একটি API উদাহরণ।

একটি RemoteMediator<Key, Value> বাস্তবায়ন তৈরি করুন। আপনি যদি একই নেটওয়ার্ক ডেটা উৎসের বিপরীতে একটি PagingSource সংজ্ঞায়িত করেন তবে Key টাইপ এবং Value ধরন একই হওয়া উচিত। টাইপ প্যারামিটার নির্বাচন সম্পর্কে আরও তথ্যের জন্য, কী এবং মান প্রকার নির্বাচন করুন দেখুন।

কোটলিন

@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 {
    // ...
  }
}

জাভা

@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
  ) {
    ...
  }
}

জাভা

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() পদ্ধতি ব্যাকিং ডেটাসেট আপডেট করার জন্য এবং PagingSource অবৈধ করার জন্য দায়ী। কিছু লাইব্রেরি যেগুলি পেজিং সমর্থন করে (যেমন রুম) স্বয়ংক্রিয়ভাবে অবৈধ PagingSource অবজেক্টগুলি পরিচালনা করবে যা তারা প্রয়োগ করে।

load() পদ্ধতি দুটি পরামিতি নেয়:

  • PagingState , যেটিতে এখন পর্যন্ত লোড হওয়া পৃষ্ঠাগুলি, সম্প্রতি অ্যাক্সেস করা সূচী এবং পেজিং স্ট্রীম শুরু করার জন্য আপনি যে পেজিং PagingConfig অবজেক্ট ব্যবহার করেছেন সে সম্পর্কে তথ্য রয়েছে৷
  • LoadType , যা লোডের ধরন নির্দেশ করে: REFRESH , APPEND , বা PREPEND

load() পদ্ধতির রিটার্ন মান হল একটি MediatorResult অবজেক্ট। MediatorResult হতে পারে MediatorResult.Error (যার মধ্যে ত্রুটির বিবরণ রয়েছে) অথবা MediatorResult.Success (যাতে আরও ডেটা লোড করা আছে কিনা তা উল্লেখ করে একটি সংকেত রয়েছে)।

load() পদ্ধতিটি অবশ্যই নিম্নলিখিত পদক্ষেপগুলি সম্পাদন করবে:

  1. নেটওয়ার্ক থেকে কোন পৃষ্ঠাটি লোড করা হবে তা নির্ধারণ করুন লোডের ধরন এবং এখন পর্যন্ত লোড করা ডেটার উপর নির্ভর করে।
  2. নেটওয়ার্ক অনুরোধ ট্রিগার.
  3. লোড অপারেশনের ফলাফলের উপর নির্ভর করে কর্ম সম্পাদন করুন:
    • যদি লোড সফল হয় এবং আইটেমগুলির প্রাপ্ত তালিকা খালি না হয়, তাহলে তালিকা আইটেমগুলি ডাটাবেসে সংরক্ষণ করুন এবং MediatorResult.Success(endOfPaginationReached = false) ফেরত দিন। ডেটা সংরক্ষণ করার পরে, নতুন ডেটার পেজিং লাইব্রেরিকে অবহিত করতে ডেটা উত্সটিকে অবৈধ করুন৷
    • যদি লোড সফল হয় এবং হয় আইটেমগুলির প্রাপ্ত তালিকা খালি থাকে বা এটি শেষ পৃষ্ঠার সূচী হয়, তাহলে MediatorResult.Success(endOfPaginationReached = true) ফেরত দিন। ডেটা সংরক্ষণ করার পরে, নতুন ডেটার পেজিং লাইব্রেরিকে অবহিত করতে ডেটা উত্সটিকে অবৈধ করুন৷
    • যদি অনুরোধটি একটি ত্রুটি সৃষ্টি করে, তাহলে MediatorResult.Error ফেরত দিন।

কোটলিন

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)
  }
}

জাভা

@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);
    });
}

জাভা

@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
  );
}

আরম্ভ করার পদ্ধতি সংজ্ঞায়িত করুন

RemoteMediator প্রয়োগগুলি ক্যাশে করা ডেটা পুরানো হয়েছে কিনা তা পরীক্ষা করতে এবং একটি দূরবর্তী রিফ্রেশ ট্রিগার করতে হবে কিনা তা পরীক্ষা করতে initialize() পদ্ধতিকে ওভাররাইড করতে পারে। কোনো লোডিং সঞ্চালিত হওয়ার আগে এই পদ্ধতিটি চলে, তাই আপনি কোনো স্থানীয় বা দূরবর্তী লোড ট্রিগার করার আগে ডাটাবেস (উদাহরণস্বরূপ, পুরানো ডেটা সাফ করতে) ম্যানিপুলেট করতে পারেন।

যেহেতু initialize() একটি অ্যাসিঙ্ক্রোনাস ফাংশন, আপনি ডাটাবেসে বিদ্যমান ডেটার প্রাসঙ্গিকতা নির্ধারণ করতে ডেটা লোড করতে পারেন। সবচেয়ে সাধারণ ক্ষেত্রে ক্যাশে করা ডেটা শুধুমাত্র একটি নির্দিষ্ট সময়ের জন্য বৈধ। RemoteMediator এই মেয়াদ শেষ হয়ে গেছে কিনা তা পরীক্ষা করতে পারে, এই ক্ষেত্রে পেজিং লাইব্রেরির ডেটা সম্পূর্ণরূপে রিফ্রেশ করতে হবে। initialize() এর প্রয়োগগুলি নিম্নলিখিত হিসাবে একটি InitializeAction ফেরত দেবে:

  • যে ক্ষেত্রে স্থানীয় ডেটা সম্পূর্ণরূপে রিফ্রেশ করা প্রয়োজন, initialize() InitializeAction.LAUNCH_INITIAL_REFRESH ফেরত দেওয়া উচিত।LAUNCH_INITIAL_REFRESH। এর ফলে RemoteMediator একটি রিমোট রিফ্রেশ করে ডেটা সম্পূর্ণরূপে পুনরায় লোড করতে। যেকোনো দূরবর্তী APPEND বা PREPEND লোড এগিয়ে যাওয়ার আগে REFRESH লোড সফল হওয়ার জন্য অপেক্ষা করে।
  • যে ক্ষেত্রে স্থানীয় ডেটা রিফ্রেশ করার প্রয়োজন নেই, initialize() কে InitializeAction.SKIP_INITIAL_REFRESH ফেরত দেওয়া উচিত।SKIP_INITIAL_REFRESH। এর ফলে RemoteMediator রিমোট রিফ্রেশ এড়িয়ে যায় এবং ক্যাশে করা ডেটা লোড করে।

কোটলিন

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
  }
}

জাভা

@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;
      }
    });
}

জাভা

@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);
}

একটি পেজার তৈরি করুন

অবশেষে, পেজড ডেটার স্ট্রীম সেট আপ করতে আপনাকে অবশ্যই একটি Pager ইনস্ট্যান্স তৈরি করতে হবে। এটি একটি সাধারণ নেটওয়ার্ক ডেটা উত্স থেকে একটি Pager তৈরি করার মতো, তবে দুটি জিনিস আপনাকে আলাদাভাবে করতে হবে:

  • একটি PagingSource কনস্ট্রাক্টরকে সরাসরি পাস করার পরিবর্তে, আপনাকে অবশ্যই ক্যোয়ারী পদ্ধতি প্রদান করতে হবে যা DAO থেকে একটি PagingSource অবজেক্ট রিটার্ন করে।
  • আপনাকে অবশ্যই আপনার RemoteMediator বাস্তবায়নের একটি উদাহরণ প্রদান করতে হবে remoteMediator প্যারামিটার হিসাবে।

কোটলিন

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

জাভা

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

জাভা

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

রেস অবস্থা হ্যান্ডেল

একাধিক উত্স থেকে ডেটা লোড করার সময় আপনার অ্যাপটিকে একটি পরিস্থিতি যা পরিচালনা করতে হবে তা হল যে ক্ষেত্রে স্থানীয় ক্যাশে করা ডেটা দূরবর্তী ডেটা উত্সের সাথে সিঙ্কের বাইরে হয়ে যায়৷

যখন আপনার RemoteMediator বাস্তবায়ন থেকে initialize() পদ্ধতি LAUNCH_INITIAL_REFRESH রিটার্ন করে, তখন ডেটা পুরানো হয়ে যায় এবং নতুন ডেটা দিয়ে প্রতিস্থাপন করতে হবে। যেকোন PREPEND বা APPEND লোডের অনুরোধগুলি দূরবর্তী REFRESH লোড সফল হওয়ার জন্য অপেক্ষা করতে বাধ্য হয়৷ যেহেতু PREPEND বা APPEND অনুরোধগুলি REFRESH অনুরোধের আগে সারিবদ্ধ ছিল, এটি সম্ভব যে এই লোড কলগুলিতে পাস করা PagingState চালানোর সময় পুরানো হয়ে যাবে৷

স্থানীয়ভাবে ডেটা কীভাবে সংরক্ষণ করা হয় তার উপর নির্ভর করে, ক্যাশ করা ডেটাতে পরিবর্তনের ফলে অবৈধ এবং নতুন ডেটা আনা হলে আপনার অ্যাপটি অপ্রয়োজনীয় অনুরোধগুলিকে উপেক্ষা করতে পারে। উদাহরণস্বরূপ, রুম যেকোনো ডেটা সন্নিবেশের প্রশ্নগুলিকে বাতিল করে। এর মানে হল যে নতুন PagingSource অবজেক্টগুলি রিফ্রেশ করা ডেটা সহ মুলতুবি লোড অনুরোধগুলিতে প্রদান করা হয় যখন ডাটাবেসে নতুন ডেটা ঢোকানো হয়।

ব্যবহারকারীরা সবচেয়ে প্রাসঙ্গিক, আপ-টু-ডেট ডেটা দেখতে পান তা নিশ্চিত করার জন্য এই ডেটা সিঙ্ক্রোনাইজেশন সমস্যার সমাধান করা অপরিহার্য। সর্বোত্তম সমাধানটি নির্ভর করে যেভাবে নেটওয়ার্ক ডেটা উৎস পৃষ্ঠাগুলি ডেটার উপর। যাই হোক না কেন, রিমোট কী আপনাকে সার্ভার থেকে অনুরোধ করা সাম্প্রতিকতম পৃষ্ঠা সম্পর্কে তথ্য সংরক্ষণ করতে দেয়। পরবর্তী লোড করার জন্য ডেটার সঠিক পৃষ্ঠা সনাক্ত করতে এবং অনুরোধ করতে আপনি এই তথ্যটি ব্যবহার করতে পারেন৷

দূরবর্তী কীগুলি পরিচালনা করুন

রিমোট কী এমন কী যা একটি RemoteMediator বাস্তবায়ন ব্যাকএন্ড পরিষেবাকে পরবর্তীতে কোন ডেটা লোড করতে হবে তা জানাতে ব্যবহার করে। সহজ ক্ষেত্রে, পৃষ্ঠাযুক্ত ডেটার প্রতিটি আইটেমে একটি দূরবর্তী কী অন্তর্ভুক্ত থাকে যা আপনি সহজেই উল্লেখ করতে পারেন। যাইহোক, যদি দূরবর্তী কীগুলি পৃথক আইটেমগুলির সাথে সামঞ্জস্য না করে, তাহলে আপনাকে অবশ্যই সেগুলিকে আলাদাভাবে সংরক্ষণ করতে হবে এবং আপনার load() পদ্ধতিতে সেগুলি পরিচালনা করতে হবে৷

এই বিভাগটি বর্ণনা করে যে কীভাবে পৃথক আইটেমগুলিতে সংরক্ষিত নয় এমন দূরবর্তী কীগুলি সংগ্রহ, সঞ্চয় এবং আপডেট করতে হয়।

আইটেম কী

এই বিভাগটি বর্ণনা করে যে কীভাবে পৃথক আইটেমের সাথে সম্পর্কিত দূরবর্তী কীগুলির সাথে কাজ করতে হয়। সাধারণত, যখন একটি API পৃথক আইটেম বন্ধ করে, আইটেম আইডি একটি ক্যোয়ারী প্যারামিটার হিসাবে পাস করা হয়। পরামিতি নাম নির্দেশ করে যে সার্ভারের প্রদত্ত আইডির আগে বা পরে আইটেমগুলির সাথে প্রতিক্রিয়া জানানো উচিত। User মডেল ক্লাসের উদাহরণে, অতিরিক্ত ডেটা অনুরোধ করার সময় সার্ভার থেকে id ক্ষেত্রটি একটি দূরবর্তী কী হিসাবে ব্যবহৃত হয়।

যখন আপনার load() পদ্ধতিতে আইটেম-নির্দিষ্ট দূরবর্তী কীগুলি পরিচালনা করার প্রয়োজন হয়, তখন এই কীগুলি সাধারণত সার্ভার থেকে আনা ডেটার আইডি হয়। রিফ্রেশ অপারেশনগুলির জন্য লোড কী প্রয়োজন হয় না, কারণ তারা কেবল সাম্প্রতিক ডেটা পুনরুদ্ধার করে। একইভাবে, প্রিপেন্ড ক্রিয়াকলাপগুলির জন্য কোনও অতিরিক্ত ডেটা আনার প্রয়োজন নেই কারণ রিফ্রেশ সর্বদা সার্ভার থেকে নতুন ডেটা টেনে আনে।

যাইহোক, সংযুক্ত অপারেশন একটি আইডি প্রয়োজন. এর জন্য আপনাকে ডাটাবেস থেকে শেষ আইটেমটি লোড করতে হবে এবং ডেটার পরবর্তী পৃষ্ঠা লোড করতে এর ID ব্যবহার করতে হবে। যদি ডাটাবেসে কোনো আইটেম না থাকে, তাহলে endOfPaginationReached সত্যে সেট করা হয়, এটি নির্দেশ করে যে একটি ডেটা রিফ্রেশ প্রয়োজন।

কোটলিন

@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)
    }
  }
}

জাভা

@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);
    });
}

জাভা

@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);
}

পৃষ্ঠা কী

এই বিভাগটি বর্ণনা করে কিভাবে দূরবর্তী কীগুলির সাথে কাজ করতে হয় যা পৃথক আইটেমগুলির সাথে সঙ্গতিপূর্ণ নয়৷

রিমোট কী টেবিল যোগ করুন

যখন দূরবর্তী কীগুলি তালিকা আইটেমগুলির সাথে সরাসরি যুক্ত না হয়, তখন স্থানীয় ডাটাবেসের একটি পৃথক টেবিলে তাদের সংরক্ষণ করা ভাল। দূরবর্তী কীগুলির একটি টেবিলের প্রতিনিধিত্ব করে এমন একটি রুম সত্তাকে সংজ্ঞায়িত করুন:

কোটলিন

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

জাভা

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

জাভা

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

আপনাকে অবশ্যই RemoteKey সত্তার জন্য একটি DAO সংজ্ঞায়িত করতে হবে:

কোটলিন

@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)
}

জাভা

@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);
}

জাভা

@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);
}

রিমোট কী দিয়ে লোড করুন

যখন আপনার load() পদ্ধতিটি দূরবর্তী পৃষ্ঠা কীগুলি পরিচালনা করতে হবে, তখন আপনাকে অবশ্যই এটিকে নিম্নোক্ত উপায়ে RemoteMediator এর মৌলিক ব্যবহারের তুলনায় আলাদাভাবে সংজ্ঞায়িত করতে হবে:

  • আপনার দূরবর্তী কী টেবিলের জন্য DAO-এর একটি রেফারেন্স ধারণ করে এমন একটি অতিরিক্ত সম্পত্তি অন্তর্ভুক্ত করুন।
  • PagingState ব্যবহার না করে দূরবর্তী কী টেবিলটি অনুসন্ধান করে পরবর্তী কোন কীটি লোড করা হবে তা নির্ধারণ করুন।
  • পৃষ্ঠাযুক্ত ডেটা ছাড়াও নেটওয়ার্ক ডেটা উত্স থেকে প্রত্যাবর্তিত রিমোট কী সন্নিবেশ বা সংরক্ষণ করুন৷

কোটলিন

@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)
    }
  }
}

জাভা

@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);
    });
}

জাভা

@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);
}

জায়গায় রিফ্রেশ করুন

যদি আপনার অ্যাপটিকে শুধুমাত্র পূর্ববর্তী উদাহরণের মতো তালিকার শীর্ষ থেকে নেটওয়ার্ক রিফ্রেশ সমর্থন করতে হয়, তাহলে আপনার RemoteMediator প্রিপেন্ড লোড আচরণ সংজ্ঞায়িত করতে হবে না।

যাইহোক, যদি আপনার অ্যাপটিকে নেটওয়ার্ক থেকে স্থানীয় ডাটাবেসে ক্রমবর্ধমানভাবে লোডিং সমর্থন করতে হয়, তাহলে আপনাকে অবশ্যই অ্যাঙ্কর থেকে শুরু করে পৃষ্ঠা সংখ্যা পুনরায় শুরু করার জন্য সমর্থন প্রদান করতে হবে, ব্যবহারকারীর স্ক্রোল অবস্থান। রুমের PagingSource ইমপ্লিমেন্টেশন আপনার জন্য এটি পরিচালনা করে, কিন্তু আপনি যদি রুম ব্যবহার না করেন তবে আপনি PagingSource.getRefreshKey() ওভাররাইড করে এটি করতে পারেন। getRefreshKey() এর একটি উদাহরণ বাস্তবায়নের জন্য, পেজিংসোর্স সংজ্ঞায়িত করুন দেখুন।

চিত্র 4 প্রথমে স্থানীয় ডাটাবেস থেকে ডেটা লোড করার প্রক্রিয়াটি চিত্রিত করে এবং তারপরে ডাটাবেসটি ডেটার বাইরে হয়ে গেলে নেটওয়ার্ক থেকে।

পেজিং সোর্স ডাটাবেস থেকে UI-তে লোড হয় যতক্ষণ না ডাটাবেস ডেটার বাইরে থাকে। তারপরে রিমোটমিডিয়েটর নেটওয়ার্ক থেকে ডাটাবেসে লোড হয় এবং তারপরে পেজিংসোর্স লোড হতে থাকে।
চিত্র 4. ডাটা লোড করার জন্য পেজিংসোর্স এবং রিমোটমিডিয়েটর একসাথে কীভাবে কাজ করে তা দেখানো চিত্র।

অতিরিক্ত সম্পদ

পেজিং লাইব্রেরি সম্পর্কে আরও জানতে, নিম্নলিখিত অতিরিক্ত সংস্থানগুলি দেখুন:

কোডল্যাব

নমুনা

{% শব্দার্থে %} {% endverbatim %} {% শব্দার্থে %} {% endverbatim %}