लोडिंग की स्थितियां (व्यू) मैनेज करना और उन्हें दिखाना

सिद्धांत और Jetpack Compose में लागू करना

पेजिंग लाइब्रेरी, पेज किए गए डेटा के लिए लोड करने के अनुरोधों की स्थिति को ट्रैक करती है. साथ ही, इसे LoadState क्लास के ज़रिए दिखाती है. आपका ऐप्लिकेशन, PagingDataAdapter के साथ लिसनर रजिस्टर कर सकता है, ताकि उसे मौजूदा स्थिति के बारे में जानकारी मिल सके. इसके बाद, वह यूज़र इंटरफ़ेस (यूआई) को अपडेट कर सके. ये स्टेट, अडैप्टर से मिलती हैं, क्योंकि ये यूज़र इंटरफ़ेस (यूआई) के साथ सिंक होती हैं. इसका मतलब है कि जब पेज लोड को यूज़र इंटरफ़ेस (यूआई) पर लागू किया जाता है, तब आपके लिसनर को अपडेट मिलते हैं.

हर LoadType और डेटा सोर्स टाइप (PagingSource या RemoteMediator) के लिए, एक अलग LoadState सिग्नल दिया जाता है. लिसनर से मिला CombinedLoadStates ऑब्जेक्ट, इन सभी सिग्नल से लोडिंग की स्थिति के बारे में जानकारी देता है. इस जानकारी का इस्तेमाल करके, उपयोगकर्ताओं को सही लोडिंग इंडिकेटर दिखाए जा सकते हैं.

राज्य लोड हो रहे हैं

Paging library, LoadState ऑब्जेक्ट के ज़रिए यूज़र इंटरफ़ेस (यूआई) में इस्तेमाल करने के लिए, लोडिंग की स्थिति को दिखाता है. LoadState ऑब्जेक्ट, लोड होने की मौजूदा स्थिति के आधार पर तीन में से किसी एक फ़ॉर्म में होते हैं:

  • अगर कोई लोड ऑपरेशन चालू नहीं है और कोई गड़बड़ी नहीं है, तो LoadState एक LoadState.NotLoading ऑब्जेक्ट है. इस सबक्लास में endOfPaginationReached प्रॉपर्टी भी शामिल होती है. इससे पता चलता है कि पेज नंबर खत्म हो गए हैं या नहीं.
  • अगर लोड करने की कोई कार्रवाई चालू है, तो LoadState एक LoadState.Loading ऑब्जेक्ट है.
  • अगर कोई गड़बड़ी है, तो LoadState एक LoadState.Error ऑब्जेक्ट है.

अपने यूज़र इंटरफ़ेस में LoadState का इस्तेमाल दो तरीकों से किया जा सकता है: लिसनर का इस्तेमाल करके या खास सूची अडैप्टर का इस्तेमाल करके, सीधे RecyclerView सूची में लोडिंग की स्थिति दिखाने के लिए.

लिसनर की मदद से, लोड होने की स्थिति को ऐक्सेस करना

अपने यूज़र इंटरफ़ेस (यूआई) में सामान्य इस्तेमाल के लिए, लोडिंग की स्थिति पाने के लिए, PagingDataAdapter की ओर से उपलब्ध कराई गई loadStateFlow स्ट्रीम या addLoadStateListener() तरीके का इस्तेमाल करें. इन तरीकों से, CombinedLoadStates ऑब्जेक्ट को ऐक्सेस किया जा सकता है. इसमें हर लोड टाइप के लिए, LoadState के व्यवहार के बारे में जानकारी शामिल होती है.

यहां दिए गए उदाहरण में, PagingDataAdapter अलग-अलग यूज़र इंटरफ़ेस (यूआई) कॉम्पोनेंट दिखाता है. ये कॉम्पोनेंट, रीफ़्रेश लोड की मौजूदा स्थिति के हिसाब से दिखते हैं:

Kotlin

// Activities can use lifecycleScope directly, but Fragments should instead use
// viewLifecycleOwner.lifecycleScope.
lifecycleScope.launch {
  pagingAdapter.loadStateFlow.collectLatest { loadStates ->
    progressBar.isVisible = loadStates.refresh is LoadState.Loading
    retry.isVisible = loadState.refresh !is LoadState.Loading
    errorMsg.isVisible = loadState.refresh is LoadState.Error
  }
}

Java

pagingAdapter.addLoadStateListener(loadStates -> {
  progressBar.setVisibility(loadStates.refresh instanceof LoadState.Loading
    ? View.VISIBLE : View.GONE);
  retry.setVisibility(loadStates.refresh instanceof LoadState.Loading
    ? View.GONE : View.VISIBLE);
  errorMsg.setVisibility(loadStates.refresh instanceof LoadState.Error
    ? View.VISIBLE : View.GONE);
});

Java

pagingAdapter.addLoadStateListener(loadStates -> {
  progressBar.setVisibility(loadStates.refresh instanceof LoadState.Loading
    ? View.VISIBLE : View.GONE);
  retry.setVisibility(loadStates.refresh instanceof LoadState.Loading
    ? View.GONE : View.VISIBLE);
  errorMsg.setVisibility(loadStates.refresh instanceof LoadState.Error
    ? View.VISIBLE : View.GONE);
});

CombinedLoadStates के बारे में ज़्यादा जानकारी के लिए, लोडिंग की स्थिति के बारे में अतिरिक्त जानकारी ऐक्सेस करना लेख पढ़ें.

अडैप्टर से चार्ज करने की स्थिति दिखाएं

Paging library, एक और लिस्ट अडैप्टर उपलब्ध कराती है. इसे LoadStateAdapter कहा जाता है. इसका इस्तेमाल, पेज किए गए डेटा की दिखाई गई सूची में सीधे तौर पर लोडिंग की स्थिति दिखाने के लिए किया जाता है. यह अडैप्टर, सूची की मौजूदा लोड स्थिति का ऐक्सेस देता है. इसे कस्टम व्यू होल्डर को पास किया जा सकता है, जो जानकारी दिखाता है.

सबसे पहले, एक व्यू होल्डर क्लास बनाएं. यह क्लास, आपकी स्क्रीन पर मौजूद लोडिंग और गड़बड़ी वाले व्यू के रेफ़रंस सेव करके रखती है. ऐसा bind() फ़ंक्शन बनाएं जो LoadState को पैरामीटर के तौर पर स्वीकार करे. इस फ़ंक्शन को, लोड स्टेट पैरामीटर के आधार पर व्यू दिखने की सेटिंग को टॉगल करना चाहिए:

Kotlin

class LoadStateViewHolder(
  parent: ViewGroup,
  retry: () -> Unit
) : RecyclerView.ViewHolder(
  LayoutInflater.from(parent.context)
    .inflate(R.layout.load_state_item, parent, false)
) {
  private val binding = LoadStateItemBinding.bind(itemView)
  private val progressBar: ProgressBar = binding.progressBar
  private val errorMsg: TextView = binding.errorMsg
  private val retry: Button = binding.retryButton
    .also {
      it.setOnClickListener { retry() }
    }

  fun bind(loadState: LoadState) {
    if (loadState is LoadState.Error) {
      errorMsg.text = loadState.error.localizedMessage
    }

    progressBar.isVisible = loadState is LoadState.Loading
    retry.isVisible = loadState is LoadState.Error
    errorMsg.isVisible = loadState is LoadState.Error
  }
}

Java

class LoadStateViewHolder extends RecyclerView.ViewHolder {
  private ProgressBar mProgressBar;
  private TextView mErrorMsg;
  private Button mRetry;

  LoadStateViewHolder(
    @NonNull ViewGroup parent,
    @NonNull View.OnClickListener retryCallback) {
    super(LayoutInflater.from(parent.getContext())
      .inflate(R.layout.load_state_item, parent, false));

    LoadStateItemBinding binding = LoadStateItemBinding.bind(itemView);
    mProgressBar = binding.progressBar;
    mErrorMsg = binding.errorMsg;
    mRetry = binding.retryButton;
  }

  public void bind(LoadState loadState) {
    if (loadState instanceof LoadState.Error) {
      LoadState.Error loadStateError = (LoadState.Error) loadState;
      mErrorMsg.setText(loadStateError.getError().getLocalizedMessage());
    }
    mProgressBar.setVisibility(loadState instanceof LoadState.Loading
      ? View.VISIBLE : View.GONE);
    mRetry.setVisibility(loadState instanceof LoadState.Error
      ? View.VISIBLE : View.GONE);
    mErrorMsg.setVisibility(loadState instanceof LoadState.Error
      ? View.VISIBLE : View.GONE);
  }
}

Java

class LoadStateViewHolder extends RecyclerView.ViewHolder {
  private ProgressBar mProgressBar;
  private TextView mErrorMsg;
  private Button mRetry;

  LoadStateViewHolder(
    @NonNull ViewGroup parent,
    @NonNull View.OnClickListener retryCallback) {
    super(LayoutInflater.from(parent.getContext())
      .inflate(R.layout.load_state_item, parent, false));

    LoadStateItemBinding binding = LoadStateItemBinding.bind(itemView);
    mProgressBar = binding.progressBar;
    mErrorMsg = binding.errorMsg;
    mRetry = binding.retryButton;
  }

  public void bind(LoadState loadState) {
    if (loadState instanceof LoadState.Error) {
      LoadState.Error loadStateError = (LoadState.Error) loadState;
      mErrorMsg.setText(loadStateError.getError().getLocalizedMessage());
    }
    mProgressBar.setVisibility(loadState instanceof LoadState.Loading
      ? View.VISIBLE : View.GONE);
    mRetry.setVisibility(loadState instanceof LoadState.Error
      ? View.VISIBLE : View.GONE);
    mErrorMsg.setVisibility(loadState instanceof LoadState.Error
      ? View.VISIBLE : View.GONE);
  }
}

इसके बाद, एक ऐसी क्लास बनाएं जो LoadStateAdapter को लागू करती हो. साथ ही, onCreateViewHolder() और onBindViewHolder() तरीकों के बारे में बताएं. इन तरीकों से, कस्टम व्यू होल्डर का इंस्टेंस बनाया जाता है और उससे जुड़े लोड स्टेट को बाइंड किया जाता है.

Kotlin

// Adapter that displays a loading spinner when
// state is LoadState.Loading, and an error message and retry
// button when state is LoadState.Error.
class ExampleLoadStateAdapter(
  private val retry: () -> Unit
) : LoadStateAdapter<LoadStateViewHolder>() {

  override fun onCreateViewHolder(
    parent: ViewGroup,
    loadState: LoadState
  ) = LoadStateViewHolder(parent, retry)

  override fun onBindViewHolder(
    holder: LoadStateViewHolder,
    loadState: LoadState
  ) = holder.bind(loadState)
}

Java

// Adapter that displays a loading spinner when
// state is LoadState.Loading, and an error message and retry
// button when state is LoadState.Error.
class ExampleLoadStateAdapter extends LoadStateAdapter<LoadStateViewHolder> {
  private View.OnClickListener mRetryCallback;

  ExampleLoadStateAdapter(View.OnClickListener retryCallback) {
    mRetryCallback = retryCallback;
  }

  @NotNull
  @Override
  public LoadStateViewHolder onCreateViewHolder(@NotNull ViewGroup parent,
    @NotNull LoadState loadState) {
    return new LoadStateViewHolder(parent, mRetryCallback);
  }

  @Override
  public void onBindViewHolder(@NotNull LoadStateViewHolder holder,
    @NotNull LoadState loadState) {
    holder.bind(loadState);
  }
}

Java

// Adapter that displays a loading spinner when
// state is LoadState.Loading, and an error message and retry
// button when state is LoadState.Error.
class ExampleLoadStateAdapter extends LoadStateAdapter<LoadStateViewHolder> {
  private View.OnClickListener mRetryCallback;

  ExampleLoadStateAdapter(View.OnClickListener retryCallback) {
    mRetryCallback = retryCallback;
  }

  @NotNull
  @Override
  public LoadStateViewHolder onCreateViewHolder(@NotNull ViewGroup parent,
    @NotNull LoadState loadState) {
    return new LoadStateViewHolder(parent, mRetryCallback);
  }

  @Override
  public void onBindViewHolder(@NotNull LoadStateViewHolder holder,
    @NotNull LoadState loadState) {
    holder.bind(loadState);
  }
}

हेडर और फ़ुटर में लोडिंग की प्रोग्रेस दिखाने के लिए, अपने PagingDataAdapter ऑब्जेक्ट से withLoadStateHeaderAndFooter() तरीके को कॉल करें:

Kotlin

pagingAdapter
  .withLoadStateHeaderAndFooter(
    header = ExampleLoadStateAdapter(adapter::retry),
    footer = ExampleLoadStateAdapter(adapter::retry)
  )

Java

pagingAdapter
  .withLoadStateHeaderAndFooter(
    new ExampleLoadStateAdapter(pagingAdapter::retry),
    new ExampleLoadStateAdapter(pagingAdapter::retry));

Java

pagingAdapter
  .withLoadStateHeaderAndFooter(
    new ExampleLoadStateAdapter(pagingAdapter::retry),
    new ExampleLoadStateAdapter(pagingAdapter::retry));

अगर आपको RecyclerView सूची में, लोडिंग की स्थिति सिर्फ़ हेडर या फ़ुटर में दिखानी है, तो इसके बजाय withLoadStateHeader() या withLoadStateFooter() को कॉल करें.

लोड होने की स्थिति के बारे में ज़्यादा जानकारी ऐक्सेस करना

PagingDataAdapter का CombinedLoadStates ऑब्जेक्ट, PagingSource और RemoteMediator के लोड होने की स्थितियों के बारे में जानकारी देता है. हालांकि, यह जानकारी सिर्फ़ तब मिलती है, जब RemoteMediator लागू किया गया हो.

आसानी के लिए, CombinedLoadStates से refresh, append, और prepend प्रॉपर्टी का इस्तेमाल करके, लोड टाइप के हिसाब से LoadState ऑब्जेक्ट को ऐक्सेस किया जा सकता है. आम तौर पर, ये प्रॉपर्टी RemoteMediator के लोड होने की स्थिति के हिसाब से काम करती हैं. अगर ऐसा नहीं होता है, तो इनमें RemoteMediator के लोड होने की स्थिति के हिसाब से सही जानकारी होती है.PagingSource इसके पीछे काम करने वाले लॉजिक के बारे में ज़्यादा जानकारी के लिए, CombinedLoadStates का रेफ़रंस दस्तावेज़ देखें.

Kotlin

lifecycleScope.launch {
  pagingAdapter.loadStateFlow.collectLatest { loadStates ->
    // Observe refresh load state from RemoteMediator if present, or
    // from PagingSource otherwise.
    refreshLoadState: LoadState = loadStates.refresh
    // Observe prepend load state from RemoteMediator if present, or
    // from PagingSource otherwise.
    prependLoadState: LoadState = loadStates.prepend
    // Observe append load state from RemoteMediator if present, or
    // from PagingSource otherwise.
    appendLoadState: LoadState = loadStates.append
  }
}

Java

pagingAdapter.addLoadStateListener(loadStates -> {
  // Observe refresh load state from RemoteMediator if present, or
  // from PagingSource otherwise.
  LoadState refreshLoadState = loadStates.refresh;
  // Observe prepend load state from RemoteMediator if present, or
  // from PagingSource otherwise.
  LoadState prependLoadState = loadStates.prepend;
  // Observe append load state from RemoteMediator if present, or
  // from PagingSource otherwise.
  LoadState appendLoadState = loadStates.append;
});

Java

pagingAdapter.addLoadStateListener(loadStates -> {
  // Observe refresh load state from RemoteMediator if present, or
  // from PagingSource otherwise.
  LoadState refreshLoadState = loadStates.refresh;
  // Observe prepend load state from RemoteMediator if present, or
  // from PagingSource otherwise.
  LoadState prependLoadState = loadStates.prepend;
  // Observe append load state from RemoteMediator if present, or
  // from PagingSource otherwise.
  LoadState appendLoadState = loadStates.append;
});

हालांकि, यह याद रखना ज़रूरी है कि सिर्फ़ PagingSource लोड स्टेट, यूज़र इंटरफ़ेस (यूआई) अपडेट के साथ सिंक्रोनस होती हैं. refresh, append, और prepend प्रॉपर्टी, लोड की स्थिति को PagingSource या RemoteMediator से ले सकती हैं. इसलिए, यह ज़रूरी नहीं है कि ये यूज़र इंटरफ़ेस (यूआई) अपडेट के साथ सिंक हों. इससे यूज़र इंटरफ़ेस (यूआई) से जुड़ी समस्याएं हो सकती हैं. जैसे, यूज़र इंटरफ़ेस (यूआई) में नया डेटा जोड़े जाने से पहले ही, लोड होने की प्रोसेस पूरी हो जाती है.

इस वजह से, हेडर या फ़ुटर में लोड होने की स्थिति दिखाने के लिए, सुविधा देने वाले ऐक्सेसर अच्छी तरह से काम करते हैं. हालांकि, इस्तेमाल के अन्य मामलों के लिए, आपको PagingSource या RemoteMediator से लोड होने की स्थिति को खास तौर पर ऐक्सेस करना पड़ सकता है. CombinedLoadStates इस काम के लिए, source और mediator प्रॉपर्टी उपलब्ध कराता है. इन प्रॉपर्टी से, LoadStates ऑब्जेक्ट दिखता है. इसमें PagingSource या RemoteMediator के लिए LoadState ऑब्जेक्ट शामिल होते हैं:

Kotlin

lifecycleScope.launch {
  pagingAdapter.loadStateFlow.collectLatest { loadStates ->
    // Directly access the RemoteMediator refresh load state.
    mediatorRefreshLoadState: LoadState? = loadStates.mediator.refresh
    // Directly access the RemoteMediator append load state.
    mediatorAppendLoadState: LoadState? = loadStates.mediator.append
    // Directly access the RemoteMediator prepend load state.
    mediatorPrependLoadState: LoadState? = loadStates.mediator.prepend
    // Directly access the PagingSource refresh load state.
    sourceRefreshLoadState: LoadState = loadStates.source.refresh
    // Directly access the PagingSource append load state.
    sourceAppendLoadState: LoadState = loadStates.source.append
    // Directly access the PagingSource prepend load state.
    sourcePrependLoadState: LoadState = loadStates.source.prepend
  }
}

Java

pagingAdapter.addLoadStateListener(loadStates -> {
  // Directly access the RemoteMediator refresh load state.
  LoadState mediatorRefreshLoadState = loadStates.mediator.refresh;
  // Directly access the RemoteMediator append load state.
  LoadState mediatorAppendLoadState = loadStates.mediator.append;
  // Directly access the RemoteMediator prepend load state.
  LoadState mediatorPrependLoadState = loadStates.mediator.prepend;
  // Directly access the PagingSource refresh load state.
  LoadState sourceRefreshLoadState = loadStates.source.refresh;
  // Directly access the PagingSource append load state.
  LoadState sourceAppendLoadState = loadStates.source.append;
  // Directly access the PagingSource prepend load state.
  LoadState sourcePrependLoadState = loadStates.source.prepend;
});

Java

pagingAdapter.addLoadStateListener(loadStates -> {
  // Directly access the RemoteMediator refresh load state.
  LoadState mediatorRefreshLoadState = loadStates.mediator.refresh;
  // Directly access the RemoteMediator append load state.
  LoadState mediatorAppendLoadState = loadStates.mediator.append;
  // Directly access the RemoteMediator prepend load state.
  LoadState mediatorPrependLoadState = loadStates.mediator.prepend;
  // Directly access the PagingSource refresh load state.
  LoadState sourceRefreshLoadState = loadStates.source.refresh;
  // Directly access the PagingSource append load state.
  LoadState sourceAppendLoadState = loadStates.source.append;
  // Directly access the PagingSource prepend load state.
  LoadState sourcePrependLoadState = loadStates.source.prepend;
});

LoadState पर चेन ऑपरेटर

CombinedLoadStates ऑब्जेक्ट, लोड होने की स्थिति में हुए सभी बदलावों को ऐक्सेस करने की सुविधा देता है. इसलिए, लोड होने की स्थिति की स्ट्रीम को खास इवेंट के आधार पर फ़िल्टर करना ज़रूरी है. इससे यह पक्का होता है कि यूज़र इंटरफ़ेस (यूआई) को सही समय पर अपडेट किया जाए, ताकि यूज़र इंटरफ़ेस (यूआई) में रुकावटें न आएं और उसे बेवजह अपडेट न किया जाए.

उदाहरण के लिए, मान लें कि आपको शुरुआती डेटा लोड होने के बाद ही, खाली व्यू दिखाना है. इस इस्तेमाल के उदाहरण में, आपको यह पुष्टि करनी होगी कि डेटा रीफ़्रेश लोड शुरू हो गया है. इसके बाद,  स्थिति का इंतज़ार करें, ताकि यह पुष्टि की जा सके कि रीफ़्रेश पूरा हो गया है.NotLoading आपको उन सिग्नल को छोड़कर बाकी सभी सिग्नल फ़िल्टर करने होंगे जिनकी आपको ज़रूरत है:

Kotlin

lifecycleScope.launchWhenCreated {
  adapter.loadStateFlow
    // Only emit when REFRESH LoadState for RemoteMediator changes.
    .distinctUntilChangedBy { it.refresh }
    // Only react to cases where REFRESH completes, such as NotLoading.
    .filter { it.refresh is LoadState.NotLoading }
    // Scroll to top is synchronous with UI updates, even if remote load was
    // triggered.
    .collect { binding.list.scrollToPosition(0) }
}

Java

PublishSubject<CombinedLoadStates> subject = PublishSubject.create();
Disposable disposable =
  subject.distinctUntilChanged(CombinedLoadStates::getRefresh)
  .filter(
    combinedLoadStates -> combinedLoadStates.getRefresh() instanceof LoadState.NotLoading)
  .subscribe(combinedLoadStates -> binding.list.scrollToPosition(0));

pagingAdapter.addLoadStateListener(loadStates -> {
  subject.onNext(loadStates);
});

Java

LiveData<CombinedLoadStates> liveData = new MutableLiveData<>();
LiveData<LoadState> refreshLiveData =
  Transformations.map(liveData, CombinedLoadStates::getRefresh);
LiveData<LoadState> distinctLiveData =
  Transformations.distinctUntilChanged(refreshLiveData);

distinctLiveData.observeForever(loadState -> {
  if (loadState instanceof LoadState.NotLoading) {
    binding.list.scrollToPosition(0);
  }
});

इस उदाहरण में, रीफ़्रेश लोड की स्थिति अपडेट होने तक इंतज़ार किया जाता है. हालांकि, यह सिर्फ़ तब ट्रिगर होता है, जब स्थिति NotLoading हो. इससे यह पक्का होता है कि यूज़र इंटरफ़ेस (यूआई) अपडेट होने से पहले, रिमोट रीफ़्रेश पूरी तरह से हो गया हो.

स्ट्रीम एपीआई की मदद से, इस तरह की कार्रवाई की जा सकती है. आपका ऐप्लिकेशन, लोड होने वाले इवेंट तय कर सकता है. साथ ही, ज़रूरी शर्तें पूरी होने पर नए डेटा को मैनेज कर सकता है.