Workaround for crash in RecyclerView dispatchLayout

In some rare cases the RecyclerView dispatchLayout crashes with an IndexOutOfBoundsException. This seems a bug in the androidx library. The provided workaround should fix it.
This commit is contained in:
emanuele-f 2022-01-02 22:18:18 +01:00
parent 52af258a02
commit 12e4975e66
5 changed files with 30 additions and 9 deletions

View File

@ -112,7 +112,7 @@ public class ConnectionsAdapter extends RecyclerView.Adapter<ConnectionsAdapter.
appIcon = ((app != null) && (app.getIcon() != null)) ? Objects.requireNonNull(app.getIcon().getConstantState()).newDrawable() : unknownIcon;
icon.setImageDrawable(appIcon);
if(conn.info.length() > 0)
if((conn.info != null) && (conn.info.length() > 0))
remote.setText(conn.info);
else
remote.setText(conn.dst_ip);
@ -249,12 +249,14 @@ public class ConnectionsAdapter extends RecyclerView.Adapter<ConnectionsAdapter.
@Override
public void connectionsChanges(int num_connetions) {
//Log.d(TAG, "connectionsChanges: " + num_connetions + " connections");
mUnfilteredItemsCount = num_connetions;
refreshFilteredConnections();
}
@Override
public void connectionsAdded(int start, ConnectionDescriptor []conns) {
//Log.d(TAG, "connectionsAdded: at " + start + ", " + conns.length + " connections");
mUnfilteredItemsCount += conns.length;
if(mFilteredConn == null) {
@ -281,6 +283,7 @@ public class ConnectionsAdapter extends RecyclerView.Adapter<ConnectionsAdapter.
@Override
public void connectionsRemoved(int start, ConnectionDescriptor []conns) {
//Log.d(TAG, "connectionsRemoved: at " + start + ", " + conns.length + " connections");
mUnfilteredItemsCount -= conns.length;
if(mFilteredConn == null) {
@ -306,6 +309,8 @@ public class ConnectionsAdapter extends RecyclerView.Adapter<ConnectionsAdapter.
@Override
public void connectionsUpdated(int[] positions) {
//Log.d(TAG, "connectionsUpdated: " + positions.length + " connections");
if(mFilteredConn == null) {
for(int pos : positions)
notifyItemChanged(pos);

View File

@ -35,7 +35,6 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.emanuelef.remote_capture.CaptureService;
import com.emanuelef.remote_capture.ConnectionsRegister;
@ -78,8 +77,7 @@ public class AppsFragment extends Fragment implements ConnectionsListener {
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
mRecyclerView = view.findViewById(R.id.apps_stats_view);
LinearLayoutManager layoutMan = new LinearLayoutManager(getContext());
mRecyclerView.setLayoutManager(layoutMan);
mRecyclerView.setLayoutManager(new EmptyRecyclerView.MyLinearLayoutManager(getContext()));
mAdapter = new AppsStatsAdapter(getContext());
doRefreshApps();

View File

@ -49,7 +49,6 @@ import androidx.appcompat.widget.SearchView;
import androidx.fragment.app.Fragment;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.emanuelef.remote_capture.AppsResolver;
@ -174,7 +173,7 @@ public class ConnectionsFragment extends Fragment implements ConnectionsListener
mFabDown = view.findViewById(R.id.fabDown);
mRecyclerView = view.findViewById(R.id.connections_view);
mOldConnectionsText = view.findViewById(R.id.old_connections_notice);
LinearLayoutManager layoutMan = new LinearLayoutManager(requireContext());
EmptyRecyclerView.MyLinearLayoutManager layoutMan = new EmptyRecyclerView.MyLinearLayoutManager(requireContext());
mRecyclerView.setLayoutManager(layoutMan);
mApps = new AppsResolver(requireContext());
@ -512,7 +511,7 @@ public class ConnectionsFragment extends Fragment implements ConnectionsListener
}
private void recheckScroll() {
final LinearLayoutManager layoutMan = (LinearLayoutManager) mRecyclerView.getLayoutManager();
final EmptyRecyclerView.MyLinearLayoutManager layoutMan = (EmptyRecyclerView.MyLinearLayoutManager) mRecyclerView.getLayoutManager();
assert layoutMan != null;
int first_visibile_pos = layoutMan.findFirstCompletelyVisibleItemPosition();
int last_visible_pos = layoutMan.findLastCompletelyVisibleItemPosition();

View File

@ -27,7 +27,6 @@ import android.widget.Filterable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.SearchView;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.emanuelef.remote_capture.adapters.AppsAdapter;
import com.emanuelef.remote_capture.model.AppDescriptor;
@ -57,7 +56,7 @@ public class AppsListView extends EmptyRecyclerView implements SearchView.OnQuer
private void initialize(Context context) {
mAllApps = null;
setLayoutManager(new LinearLayoutManager(context));
setLayoutManager(new MyLinearLayoutManager(context));
setHasFixedSize(true);
}

View File

@ -24,6 +24,7 @@ import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.SimpleItemAnimator;
@ -31,6 +32,25 @@ import androidx.recyclerview.widget.SimpleItemAnimator;
public class EmptyRecyclerView extends RecyclerView {
private View mEmptyView;
/* Workaround for crash "java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid item position 0(offset:-1)".
* See https://stackoverflow.com/questions/30220771/recyclerview-inconsistency-detected-invalid-item-position .
* It can be reproduced by setting CaptureService.CONNECTIONS_LOG_SIZE = 4 and triggering a rollover right after inserting
* item 3 in the register. It may take several tries to reproduce.
* Possibly related issues:
* - https://issuetracker.google.com/issues?q=componentid:192731%2B%20IndexOutOfBoundsException%20Invalid%20item%20position
* Another way to fix the issue is to disable the item animations via setItemAnimator(null).
*/
public static class MyLinearLayoutManager extends LinearLayoutManager {
public MyLinearLayoutManager(Context context) {
super(context);
}
@Override
public boolean supportsPredictiveItemAnimations() {
return false;
}
}
public EmptyRecyclerView(Context context) {
super(context);
init();