डेटा स्ट्रीम (व्यू) में बदलाव करना

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

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

आम तौर पर, डेटा स्ट्रीम पर सीधे तौर पर बदलाव लागू करने से, आपको अपनी रिपॉज़िटरी कंस्ट्रक्ट और यूज़र इंटरफ़ेस (यूआई) कंस्ट्रक्ट को अलग-अलग रखने में मदद मिलती है.

इस पेज पर यह माना गया है कि आपको पेजिंग लाइब्रेरी के बुनियादी इस्तेमाल के बारे में पता है.

बेसिक ट्रांसफ़ॉर्मेशन लागू करना

PagingData को रिएक्टिव स्ट्रीम में शामिल किया जाता है. इसलिए, डेटा लोड करने और उसे दिखाने के बीच, डेटा में धीरे-धीरे बदलाव किया जा सकता है.

स्ट्रीम में मौजूद हर PagingData ऑब्जेक्ट पर ट्रांसफ़ॉर्मेशन लागू करने के लिए, स्ट्रीम पर map() ऑपरेशन के अंदर ट्रांसफ़ॉर्मेशन रखें:

Java

PagingRx.getFlowable(pager) // Type is Flowable<PagingData<User>>.
  // Map the outer stream so that the transformations are applied to
  // each new generation of PagingData.
  .map(pagingData -> {
    // Transformations in this block are applied to the items
    // in the paged data.
  });

Java

// Map the outer stream so that the transformations are applied to
// each new generation of PagingData.
Transformations.map(
  // Type is LiveData<PagingData<User>>.
  PagingLiveData.getLiveData(pager),
  pagingData -> {
    // Transformations in this block are applied to the items
    // in the paged data.
  });

डेटा को बदलना

डेटा की स्ट्रीम पर सबसे बुनियादी कार्रवाई, उसे किसी दूसरे टाइप में बदलना है. PagingData ऑब्जेक्ट का ऐक्सेस मिलने के बाद, PagingData ऑब्जेक्ट में मौजूद पेज वाली सूची में मौजूद हर आइटम पर map() ऑपरेशन किया जा सकता है.

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

Java

// Type is Flowable<PagingData<User>>.
PagingRx.getFlowable(pager)
  .map(pagingData ->
    pagingData.map(UiModel.UserModel::new)
  )

Java

Transformations.map(
  // Type is LiveData<PagingData<User>>.
  PagingLiveData.getLiveData(pager),
  pagingData ->
    pagingData.map(UiModel.UserModel::new)
)

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

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

Java

private BehaviorSubject<String> querySubject = BehaviorSubject.create("");

public void onQueryChanged(String query) {
  queryFlow.onNext(query)
}

Java

private MutableLiveData<String> queryLiveData = new MutableLiveData("");

public void onQueryChanged(String query) {
  queryFlow.setValue(query)
}

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

Java

Observable<User> querySearchResults =
  querySubject.switchMap(query -> userDatabase.searchBy(query));

Java

LiveData<User> querySearchResults = Transformations.switchMap(
  queryLiveData,
  query -> userDatabase.searchBy(query)
);

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

डेटा फ़िल्टर करना

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

आपको फ़िल्टर करने की इन कार्रवाइयों को map() कॉल के अंदर रखना होगा, क्योंकि फ़िल्टर PagingData ऑब्जेक्ट पर लागू होता है. डेटा को PagingData से फ़िल्टर करने के बाद, नए PagingData इंस्टेंस को यूज़र इंटरफ़ेस (यूआई) लेयर को दिखाया जाता है.

Java

// Type is Flowable<PagingData<User>>.
PagingRx.getFlowable(pager)
  .map(pagingData ->
    pagingData.filter(user -> !user.isHiddenFromUi())
  )
}

Java

Transformations.map(
  // Type is LiveData<PagingData<User>>.
  PagingLiveData.getLiveData(pager),
  pagingData ->
    pagingData.filter(user -> !user.isHiddenFromUi())
)

सूची सेपरेटर जोड़ना

पेजिंग लाइब्रेरी, डाइनैमिक लिस्ट सेपरेटर के साथ काम करती है. डेटा स्ट्रीम में सीधे तौर पर सेपरेटर डालकर, सूची को ज़्यादा आसानी से पढ़ा जा सकता है. इसके लिए, RecyclerView सूची के आइटम के तौर पर सेपरेटर डालें. इस वजह से, सेपरेटर पूरी तरह से काम करने वाले ViewHolder ऑब्जेक्ट होते हैं. इनसे इंटरैक्टिविटी, ऐक्सेसिबिलिटी फ़ोकस, और View की ओर से उपलब्ध कराई जाने वाली अन्य सभी सुविधाओं का इस्तेमाल किया जा सकता है.

पेज वाली सूची में सेपरेटर डालने के लिए, ये तीन चरण पूरे करें:

  1. सेपरेटर आइटम को शामिल करने के लिए, यूज़र इंटरफ़ेस (यूआई) मॉडल को बदलें.
  2. डेटा स्ट्रीम को इस तरह से बदलें कि डेटा लोड होने और डेटा दिखने के बीच सेपरेटर अपने-आप जुड़ जाएं.
  3. सेपरेटर आइटम को मैनेज करने के लिए, यूज़र इंटरफ़ेस (यूआई) को अपडेट करें.

यूज़र इंटरफ़ेस (यूआई) मॉडल को बदलना

Paging लाइब्रेरी, RecyclerView में सूची सेपरेटर को सूची आइटम के तौर पर डालती है. हालांकि, सेपरेटर आइटम को सूची में मौजूद डेटा आइटम से अलग होना चाहिए, ताकि उन्हें अलग यूज़र इंटरफ़ेस (यूआई) वाले किसी दूसरे ViewHolder टाइप से बाइंड किया जा सके. इसके लिए, आपको Kotlin sealed class बनानी होगी. साथ ही, अपने डेटा और सेपरेटर को दिखाने के लिए, सबक्लास बनानी होंगी. इसके अलावा, एक बेस क्लास बनाई जा सकती है. इसे आपकी सूची के आइटम क्लास और सेपरेटर क्लास, दोनों के लिए बढ़ाया जा सकता है.

मान लें कि आपको User आइटम की पेज वाली सूची में सेपरेटर जोड़ने हैं. यहां दिए गए स्निपेट में, एक बेस क्लास बनाने का तरीका बताया गया है. इसमें इंस्टेंस, UserModel या SeparatorModel हो सकते हैं:

Java

class UiModel {
  private UiModel() {}

  static class UserModel extends UiModel {
    @NonNull
    private String mId;
    @NonNull
    private String mLabel;

    UserModel(@NonNull String id, @NonNull String label) {
      mId = id;
      mLabel = label;
    }

    UserModel(@NonNull User user) {
      mId = user.id;
      mLabel = user.label;
    }

    @NonNull
    public String getId() {
      return mId;
    }

    @NonNull
    public String getLabel() {
      return mLabel;
      }
    }

    static class SeparatorModel extends UiModel {
    @NonNull
    private String mDescription;

    SeparatorModel(@NonNull String description) {
      mDescription = description;
    }

    @NonNull
    public String getDescription() {
      return mDescription;
    }
  }
}

Java

class UiModel {
  private UiModel() {}

  static class UserModel extends UiModel {
    @NonNull
    private String mId;
    @NonNull
    private String mLabel;

    UserModel(@NonNull String id, @NonNull String label) {
      mId = id;
      mLabel = label;
    }

    UserModel(@NonNull User user) {
      mId = user.id;
      mLabel = user.label;
    }

    @NonNull
    public String getId() {
      return mId;
    }

    @NonNull
    public String getLabel() {
      return mLabel;
      }
    }

    static class SeparatorModel extends UiModel {
    @NonNull
    private String mDescription;

    SeparatorModel(@NonNull String description) {
      mDescription = description;
    }

    @NonNull
    public String getDescription() {
      return mDescription;
    }
  }
}

डेटा स्ट्रीम में बदलाव करना

डेटा स्ट्रीम को लोड करने के बाद और उसे दिखाने से पहले, आपको उस पर ट्रांसफ़ॉर्मेशन लागू करने होंगे. ट्रांसफ़ॉर्मेशन से ये काम होने चाहिए:

  • लोड किए गए सूची आइटम को नए बेस आइटम टाइप के हिसाब से बदलें.
  • सेपरेटर जोड़ने के लिए, PagingData.insertSeparators() तरीके का इस्तेमाल करें.

ट्रांसफ़ॉर्मेशन के बारे में ज़्यादा जानने के लिए, बुनियादी ट्रांसफ़ॉर्मेशन लागू करना लेख पढ़ें.

यहां दिए गए उदाहरण में, PagingData<User> स्ट्रीम को अपडेट करके PagingData<UiModel> स्ट्रीम में बदलने के लिए ट्रांसफ़ॉर्मेशन ऑपरेशन दिखाए गए हैं. इसमें सेपरेटर जोड़े गए हैं:

Java

// Map outer stream, so you can perform transformations on each
// paging generation.
PagingRx.getFlowable(pager).map(pagingData -> {
  // First convert items in stream to UiModel.UserModel.
  PagingData<UiModel> uiModelPagingData = pagingData.map(
    UiModel.UserModel::new);

  // Insert UiModel.SeparatorModel, which produces PagingData of
  // generic type UiModel.
  return PagingData.insertSeparators(uiModelPagingData,
    (@Nullable UiModel before, @Nullable UiModel after) -> {
      if (before == null) {
        return new UiModel.SeparatorModel("HEADER");
      } else if (after == null) {
        return new UiModel.SeparatorModel("FOOTER");
      } else if (shouldSeparate(before, after)) {
        return new UiModel.SeparatorModel("BETWEEN ITEMS "
          + before.toString() + " AND " + after.toString());
      } else {
        // Return null to avoid adding a separator between two
        // items.
        return null;
      }
    });
});

Java

// Map outer stream, so you can perform transformations on each
// paging generation.
Transformations.map(PagingLiveData.getLiveData(pager),
  pagingData -> {
    // First convert items in stream to UiModel.UserModel.
    PagingData<UiModel> uiModelPagingData = pagingData.map(
      UiModel.UserModel::new);

    // Insert UiModel.SeparatorModel, which produces PagingData of
    // generic type UiModel.
    return PagingData.insertSeparators(uiModelPagingData,
      (@Nullable UiModel before, @Nullable UiModel after) -> {
        if (before == null) {
          return new UiModel.SeparatorModel("HEADER");
        } else if (after == null) {
          return new UiModel.SeparatorModel("FOOTER");
        } else if (shouldSeparate(before, after)) {
          return new UiModel.SeparatorModel("BETWEEN ITEMS "
            + before.toString() + " AND " + after.toString());
        } else {
          // Return null to avoid adding a separator between two
          // items.
          return null;
        }
      });
  });

यूज़र इंटरफ़ेस में सेपरेटर मैनेज करना

आखिरी चरण में, सेपरेटर आइटम टाइप को शामिल करने के लिए यूज़र इंटरफ़ेस (यूआई) में बदलाव करना होता है. अपने सेपरेटर आइटम के लिए, लेआउट और व्यू होल्डर बनाएं. साथ ही, सूची अडैप्टर को बदलकर, उसके व्यू होल्डर टाइप के तौर पर RecyclerView.ViewHolder का इस्तेमाल करें, ताकि वह एक से ज़्यादा तरह के व्यू होल्डर को मैनेज कर सके. इसके अलावा, एक सामान्य बेस क्लास तय की जा सकती है. इससे आपके आइटम और सेपरेटर व्यू होल्डर क्लास, दोनों को बढ़ाया जा सकता है.

आपको अपने लिस्ट अडैप्टर में ये बदलाव भी करने होंगे:

  • onCreateViewHolder() और onBindViewHolder() तरीकों में केस जोड़ें, ताकि सेपरेटर सूची के आइटम शामिल किए जा सकें.
  • नया तुलना करने वाला टूल लागू करें.

Java

class UiModelAdapter extends PagingDataAdapter<UiModel, RecyclerView.ViewHolder> {
  UiModelAdapter() {
    super(new UiModelComparator(), Dispatchers.getMain(),
      Dispatchers.getDefault());
  }

  @NonNull
  @Override
  public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent,
    int viewType) {
    if (viewType == R.layout.item) {
      return new UserModelViewHolder(parent);
    } else {
      return new SeparatorModelViewHolder(parent);
    }
  }

  @Override
  public int getItemViewType(int position) {
    // Use peek over getItem to avoid triggering page fetch / drops, since
    // recycling views is not indicative of the user's current scroll position.
    UiModel item = peek(position);
    if (item instanceof UiModel.UserModel) {
      return R.layout.item;
    } else if (item instanceof UiModel.SeparatorModel) {
      return R.layout.separator_item;
    } else {
      throw new IllegalStateException("Unknown view");
    }
  }

  @Override
  public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder,
    int position) {
    if (holder instanceOf UserModelViewHolder) {
      UserModel userModel = (UserModel) getItem(position);
      ((UserModelViewHolder) holder).bind(userModel);
    } else {
      SeparatorModel separatorModel = (SeparatorModel) getItem(position);
      ((SeparatorModelViewHolder) holder).bind(separatorModel);
    }
  }
}

class UiModelComparator extends DiffUtil.ItemCallback<UiModel> {
  @Override
  public boolean areItemsTheSame(@NonNull UiModel oldItem,
    @NonNull UiModel newItem) {
    boolean isSameRepoItem = oldItem instanceof UserModel
      && newItem instanceof UserModel
      && ((UserModel) oldItem).getId().equals(((UserModel) newItem).getId());

    boolean isSameSeparatorItem = oldItem instanceof SeparatorModel
      && newItem instanceof SeparatorModel
      && ((SeparatorModel) oldItem).getDescription().equals(
      ((SeparatorModel) newItem).getDescription());

    return isSameRepoItem || isSameSeparatorItem;
  }

  @Override
  public boolean areContentsTheSame(@NonNull UiModel oldItem,
    @NonNull UiModel newItem) {
    return oldItem.equals(newItem);
  }
}

Java

class UiModelAdapter extends PagingDataAdapter<UiModel, RecyclerView.ViewHolder> {
  UiModelAdapter() {
    super(new UiModelComparator(), Dispatchers.getMain(),
      Dispatchers.getDefault());
  }

  @NonNull
  @Override
  public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent,
    int viewType) {
    if (viewType == R.layout.item) {
      return new UserModelViewHolder(parent);
    } else {
      return new SeparatorModelViewHolder(parent);
    }
  }

  @Override
  public int getItemViewType(int position) {
    // Use peek over getItem to avoid triggering page fetch / drops, since
    // recycling views is not indicative of the user's current scroll position.
    UiModel item = peek(position);
    if (item instanceof UiModel.UserModel) {
      return R.layout.item;
    } else if (item instanceof UiModel.SeparatorModel) {
      return R.layout.separator_item;
    } else {
      throw new IllegalStateException("Unknown view");
    }
  }

  @Override
  public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder,
    int position) {
    if (holder instanceOf UserModelViewHolder) {
      UserModel userModel = (UserModel) getItem(position);
      ((UserModelViewHolder) holder).bind(userModel);
    } else {
      SeparatorModel separatorModel = (SeparatorModel) getItem(position);
      ((SeparatorModelViewHolder) holder).bind(separatorModel);
    }
  }
}

class UiModelComparator extends DiffUtil.ItemCallback<UiModel> {
  @Override
  public boolean areItemsTheSame(@NonNull UiModel oldItem,
    @NonNull UiModel newItem) {
    boolean isSameRepoItem = oldItem instanceof UserModel
      && newItem instanceof UserModel
      && ((UserModel) oldItem).getId().equals(((UserModel) newItem).getId());

    boolean isSameSeparatorItem = oldItem instanceof SeparatorModel
      && newItem instanceof SeparatorModel
      && ((SeparatorModel) oldItem).getDescription().equals(
      ((SeparatorModel) newItem).getDescription());

    return isSameRepoItem || isSameSeparatorItem;
  }

  @Override
  public boolean areContentsTheSame(@NonNull UiModel oldItem,
    @NonNull UiModel newItem) {
    return oldItem.equals(newItem);
  }
}

डुप्लीकेट काम से बचें

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

cachedIn() ऑपरेशन, इससे पहले होने वाले सभी ट्रांसफ़ॉर्मेशन के नतीजों को कैश मेमोरी में सेव करता है. इसलिए, cachedIn() को आपके ViewModel में आखिरी कॉल होना चाहिए.

Java

// CoroutineScope helper provided by the lifecycle-viewmodel-ktx artifact.
CoroutineScope viewModelScope = ViewModelKt.getViewModelScope(viewModel);
PagingRx.cachedIn(
  // Type is Flowable<PagingData<User>>.
  PagingRx.getFlowable(pager)
    .map(pagingData -> pagingData
      .filter(user -> !user.isHiddenFromUi())
      .map(UiModel.UserModel::new)),
  viewModelScope);
}

Java

// CoroutineScope helper provided by the lifecycle-viewmodel-ktx artifact.
CoroutineScope viewModelScope = ViewModelKt.getViewModelScope(viewModel);
PagingLiveData.cachedIn(
  Transformations.map(
    // Type is LiveData<PagingData<User>>.
    PagingLiveData.getLiveData(pager),
    pagingData -> pagingData
      .filter(user -> !user.isHiddenFromUi())
      .map(UiModel.UserModel::new)),
  viewModelScope);

PagingData की स्ट्रीम के साथ cachedIn() का इस्तेमाल करने के बारे में ज़्यादा जानने के लिए, PagingData की स्ट्रीम सेट अप करना लेख पढ़ें.

अन्य संसाधन

पेजिंग लाइब्रेरी के बारे में ज़्यादा जानने के लिए, यहां दिए गए अन्य संसाधन देखें:

कोडलैब