From e758b5f890f3f792e89a1279394b121d02cf47af Mon Sep 17 00:00:00 2001 From: whistlingwoods <72640314+whistlingwoods@users.noreply.github.com> Date: Sat, 9 Aug 2025 20:57:42 +0530 Subject: [PATCH 1/2] Adapt header handling changes from other recyclerview adapters to fix... ... Crash in lists (ViewHolder views not attached) in StatisticsPlaylistFragment Co-Authored-By: j-haldane <200528955+j-haldane@users.noreply.github.com> --- .../newpipe/local/BaseLocalListFragment.java | 10 +++-- .../newpipe/local/LocalItemListAdapter.java | 45 +++++++++++-------- .../history/StatisticsPlaylistFragment.java | 6 +-- .../local/playlist/LocalPlaylistFragment.java | 8 ++-- 4 files changed, 40 insertions(+), 29 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java b/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java index 90ef8c352b8..00a721bcf88 100644 --- a/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java @@ -28,6 +28,8 @@ import static org.schabi.newpipe.ktx.ViewUtils.animateHideRecyclerViewAllowingScrolling; import static org.schabi.newpipe.util.ThemeHelper.getItemViewMode; +import java.util.function.Supplier; + /** * This fragment is design to be used with persistent data such as * {@link org.schabi.newpipe.database.LocalItem}, and does not cache the data contained @@ -100,7 +102,7 @@ private void refreshItemViewMode() { //////////////////////////////////////////////////////////////////////////*/ @Nullable - protected ViewBinding getListHeader() { + protected Supplier getListHeaderSupplier() { return null; } @@ -131,9 +133,9 @@ protected void initViews(final View rootView, final Bundle savedInstanceState) { itemsList = rootView.findViewById(R.id.items_list); refreshItemViewMode(); - headerRootBinding = getListHeader(); - if (headerRootBinding != null) { - itemListAdapter.setHeader(headerRootBinding.getRoot()); + final Supplier listHeaderSupplier = getListHeaderSupplier(); + if (listHeaderSupplier != null) { + itemListAdapter.setHeaderSupplier(listHeaderSupplier); } footerRootBinding = getListFooter(); itemListAdapter.setFooter(footerRootBinding.getRoot()); diff --git a/app/src/main/java/org/schabi/newpipe/local/LocalItemListAdapter.java b/app/src/main/java/org/schabi/newpipe/local/LocalItemListAdapter.java index b33619dea7a..3f9cc48aba7 100644 --- a/app/src/main/java/org/schabi/newpipe/local/LocalItemListAdapter.java +++ b/app/src/main/java/org/schabi/newpipe/local/LocalItemListAdapter.java @@ -2,6 +2,7 @@ import android.content.Context; import android.util.Log; +import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -37,6 +38,7 @@ import java.time.format.FormatStyle; import java.util.ArrayList; import java.util.List; +import java.util.function.Supplier; /* * Created by Christian Schabesberger on 01.08.16. @@ -82,13 +84,14 @@ public class LocalItemListAdapter extends RecyclerView.Adapter localItems; private final HistoryRecordManager recordManager; private final DateTimeFormatter dateTimeFormatter; private boolean showFooter = false; - private View header = null; + private Supplier headerSupplier = null; private View footer = null; private ItemViewMode itemViewMode = ItemViewMode.LIST; private boolean useItemHandle = false; @@ -97,6 +100,8 @@ public LocalItemListAdapter(final Context context) { recordManager = new HistoryRecordManager(context); localItemBuilder = new LocalItemBuilder(context); localItems = new ArrayList<>(); + layoutInflater = LayoutInflater.from(context); + dateTimeFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT) .withLocale(Localization.getPreferredLocale(context)); } @@ -124,7 +129,7 @@ public void addItems(@Nullable final List data) { if (DEBUG) { Log.d(TAG, "addItems() after > offsetStart = " + offsetStart + ", " + "localItems.size() = " + localItems.size() + ", " - + "header = " + header + ", footer = " + footer + ", " + + "header = " + hasHeader() + ", footer = " + footer + ", " + "showFooter = " + showFooter); } notifyItemRangeInserted(offsetStart, data.size()); @@ -144,7 +149,7 @@ public void removeItem(final LocalItem data) { final int index = localItems.indexOf(data); if (index != -1) { localItems.remove(index); - notifyItemRemoved(index + (header != null ? 1 : 0)); + notifyItemRemoved(index + (hasHeader() ? 1 : 0)); } else { // this happens when // 1) removeItem is called on infoItemDuplicate as in showStreamItemDialog of @@ -189,9 +194,9 @@ public void setUseItemHandle(final boolean useItemHandle) { this.useItemHandle = useItemHandle; } - public void setHeader(final View header) { - final boolean changed = header != this.header; - this.header = header; + public void setHeaderSupplier(@Nullable final Supplier headerSupplier) { + final boolean changed = headerSupplier != this.headerSupplier; + this.headerSupplier = headerSupplier; if (changed) { notifyDataSetChanged(); } @@ -201,6 +206,10 @@ public void setFooter(final View view) { this.footer = view; } + protected boolean hasHeader() { + return this.headerSupplier != null; + } + public void showFooter(final boolean show) { if (DEBUG) { Log.d(TAG, "showFooter() called with: show = [" + show + "]"); @@ -218,11 +227,11 @@ public void showFooter(final boolean show) { } private int adapterOffsetWithoutHeader(final int offset) { - return offset - (header != null ? 1 : 0); + return offset - (hasHeader() ? 1 : 0); } private int sizeConsideringHeader() { - return localItems.size() + (header != null ? 1 : 0); + return localItems.size() + (hasHeader() ? 1 : 0); } public ArrayList getItemsList() { @@ -232,7 +241,7 @@ public ArrayList getItemsList() { @Override public int getItemCount() { int count = localItems.size(); - if (header != null) { + if (hasHeader()) { count++; } if (footer != null && showFooter) { @@ -242,7 +251,7 @@ public int getItemCount() { if (DEBUG) { Log.d(TAG, "getItemCount() called, count = " + count + ", " + "localItems.size() = " + localItems.size() + ", " - + "header = " + header + ", footer = " + footer + ", " + + "header = " + hasHeader() + ", footer = " + footer + ", " + "showFooter = " + showFooter); } return count; @@ -255,9 +264,9 @@ public int getItemViewType(int position) { Log.d(TAG, "getItemViewType() called with: position = [" + position + "]"); } - if (header != null && position == 0) { + if (hasHeader() && position == 0) { return HEADER_TYPE; - } else if (header != null) { + } else if (hasHeader()) { position--; } if (footer != null && position == localItems.size() && showFooter) { @@ -318,7 +327,7 @@ public RecyclerView.ViewHolder onCreateViewHolder(@NonNull final ViewGroup paren } switch (type) { case HEADER_TYPE: - return new HeaderFooterHolder(header); + return new HeaderFooterHolder(headerSupplier.get()); case FOOTER_TYPE: return new HeaderFooterHolder(footer); case LOCAL_PLAYLIST_HOLDER_TYPE: @@ -366,14 +375,14 @@ public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, int if (holder instanceof LocalItemHolder) { // If header isn't null, offset the items by -1 - if (header != null) { + if (hasHeader()) { position--; } ((LocalItemHolder) holder) .updateFromItem(localItems.get(position), recordManager, dateTimeFormatter); - } else if (holder instanceof HeaderFooterHolder && position == 0 && header != null) { - ((HeaderFooterHolder) holder).view = header; + } else if (holder instanceof HeaderFooterHolder && position == 0 && hasHeader()) { + ((HeaderFooterHolder) holder).view = headerSupplier.get(); } else if (holder instanceof HeaderFooterHolder && position == sizeConsideringHeader() && footer != null && showFooter) { ((HeaderFooterHolder) holder).view = footer; @@ -387,10 +396,10 @@ public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, fina for (final Object payload : payloads) { if (payload instanceof StreamStateEntity) { ((LocalItemHolder) holder).updateState(localItems - .get(header == null ? position : position - 1), recordManager); + .get(hasHeader() ? position - 1 : position), recordManager); } else if (payload instanceof Boolean) { ((LocalItemHolder) holder).updateState(localItems - .get(header == null ? position : position - 1), recordManager); + .get(hasHeader() ? position - 1 : position), recordManager); } } } else { diff --git a/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java index 3302e387ec5..43b7f1c0db2 100644 --- a/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java @@ -13,7 +13,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.viewbinding.ViewBinding; import com.evernote.android.state.State; import com.google.android.material.snackbar.Snackbar; @@ -45,6 +44,7 @@ import java.util.Comparator; import java.util.List; import java.util.Objects; +import java.util.function.Supplier; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.disposables.CompositeDisposable; @@ -126,12 +126,12 @@ protected void initViews(final View rootView, final Bundle savedInstanceState) { } @Override - protected ViewBinding getListHeader() { + protected Supplier getListHeaderSupplier() { headerBinding = StatisticPlaylistControlBinding.inflate(activity.getLayoutInflater(), itemsList, false); playlistControlBinding = headerBinding.playlistControl; - return headerBinding; + return headerBinding::getRoot; } @Override diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java index f5562549cf5..5f02a5467c2 100644 --- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java @@ -29,7 +29,6 @@ import androidx.appcompat.app.AlertDialog; import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.RecyclerView; -import androidx.viewbinding.ViewBinding; import com.evernote.android.state.State; import org.reactivestreams.Subscriber; @@ -67,6 +66,7 @@ import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Supplier; import java.util.stream.Collectors; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; @@ -158,14 +158,14 @@ protected void initViews(final View rootView, final Bundle savedInstanceState) { } @Override - protected ViewBinding getListHeader() { + protected Supplier getListHeaderSupplier() { headerBinding = LocalPlaylistHeaderBinding.inflate(activity.getLayoutInflater(), itemsList, - false); + false); playlistControlBinding = headerBinding.playlistControl; headerBinding.playlistTitleView.setSelected(true); - return headerBinding; + return headerBinding::getRoot; } @Override From 738338d0920f72d670337e3c9533cd6935250fc0 Mon Sep 17 00:00:00 2001 From: whistlingwoods <72640314+whistlingwoods@users.noreply.github.com> Date: Sat, 9 Aug 2025 21:01:55 +0530 Subject: [PATCH 2/2] Remove unneeded LayoutInflater from LocalItemListAdapter Co-Authored-By: j-haldane <200528955+j-haldane@users.noreply.github.com> --- .../java/org/schabi/newpipe/local/LocalItemListAdapter.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/LocalItemListAdapter.java b/app/src/main/java/org/schabi/newpipe/local/LocalItemListAdapter.java index 3f9cc48aba7..3bb84e2de3c 100644 --- a/app/src/main/java/org/schabi/newpipe/local/LocalItemListAdapter.java +++ b/app/src/main/java/org/schabi/newpipe/local/LocalItemListAdapter.java @@ -2,7 +2,6 @@ import android.content.Context; import android.util.Log; -import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -84,7 +83,6 @@ public class LocalItemListAdapter extends RecyclerView.Adapter localItems; private final HistoryRecordManager recordManager; @@ -100,7 +98,6 @@ public LocalItemListAdapter(final Context context) { recordManager = new HistoryRecordManager(context); localItemBuilder = new LocalItemBuilder(context); localItems = new ArrayList<>(); - layoutInflater = LayoutInflater.from(context); dateTimeFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT) .withLocale(Localization.getPreferredLocale(context));