Each item in my RecyclerView consists of 4 EditTexts. As long as no item is recycled, my app can change the text in any of these EditTexts.

However, as soon as items are recycled, only the first (I mean the leftmost) Edittext in the item can be updated correctly, whatever the selection.

Any idea of what could go wrong?

This is the top of the stack:

    E/AndroidRuntime: FATAL EXCEPTION: main
Process: be.ema.moles, PID: 8968
java.lang.NullPointerException: Attempt to invoke virtual method 'int androidx.recyclerview.widget.RecyclerView$ViewHolder.getAdapterPosition()' on a null object reference
    at androidx.recyclerview.selection.StableIdKeyProvider.onDetached(StableIdKeyProvider.java:90)
    at androidx.recyclerview.selection.StableIdKeyProvider$1.onChildViewDetachedFromWindow(StableIdKeyProvider.java:69)
    at androidx.recyclerview.widget.RecyclerView.dispatchChildDetached(RecyclerView.java:7261)
    at androidx.recyclerview.widget.RecyclerView.removeDetachedView(RecyclerView.java:4139)
    at androidx.recyclerview.widget.RecyclerView$LayoutManager.removeAndRecycleScrapInt(RecyclerView.java:8978)
    at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep3(RecyclerView.java:3997)
    at androidx.recyclerview.widget.RecyclerView.dispatchLayout(RecyclerView.java:3652)
    at androidx.recyclerview.widget.RecyclerView.onLayout(RecyclerView.java:4194)
    at android.view.View.layout(View.java:20672)
...

Here is the Fragment controlling the contents of the RecyclerView

public class MoleFragment extends Fragment {
RecyclerView moleRecycler = null;
TextView addBtn = null;
Cursor moleCursor = null;
static int moleAdapterPosition = 0;
static String moleId = "";
static String contactId = "";
static String rawContactId = "";

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    return inflater.inflate(R.layout.moles_layout, container, false);
}

@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);

    final Cursor moleCursor = readMoles();

    moleRecycler = (RecyclerView) getActivity().findViewById(R.id.moles);
    moleRecycler.setHasFixedSize(true);
    moleRecycler.setLayoutManager(new LinearLayoutManager(getActivity()));
    moleRecycler.setAdapter(new MolesCursorAdapter(getActivity(), moleCursor));

    OnItemActivatedListener<Long> moleItemActivatedListener = new OnItemActivatedListener() {
        @Override
        public boolean onItemActivated(@NonNull ItemDetailsLookup.ItemDetails item, @NonNull MotionEvent e) {
            moleAdapterPosition = item.getPosition();
            moleCursor.moveToPosition(item.getPosition());

            moleId = Long.toString(moleCursor.getLong(0));

            return true;
        }
    };

    SelectionTracker moleTracker = new SelectionTracker.Builder<Long>("selectedMoleId",
            moleRecycler,
            new StableIdKeyProvider(moleRecycler),
            new MoleDetailsLookup(),
            StorageStrategy.createLongStorage())
            .withSelectionPredicate(SelectionPredicates.<Long>createSelectSingleAnything())
            .withOnItemActivatedListener(moleItemActivatedListener)
            .build();

    return ;
}

public Cursor readMoles() {
    String[] contactIds = getShownCustomer();

    String selection = Data.RAW_CONTACT_ID + "=? AND " + Data.CONTACT_ID + "=? AND " + Data.MIMETYPE + "=?";
    String[] selectionArgs = new String[]{contactIds[0], contactIds[1], MOLE_MIME_TYPE};
    return moleCursor = getActivity().getContentResolver().query(
            Data.CONTENT_URI,
            new String[] {
                    Data._ID,
                    Data.DATA1,
                    Data.DATA2,
                    Data.DATA3,
                    Data.DATA4},
            selection,
            selectionArgs,
            null
    );
}

/**
 * Create a new instance of DetailsFragment, initialized to
 * show the catches for customer 'contactId'.
 */
public static MoleFragment newInstance(String rawContactId, String contactId) {
    MoleFragment f = new MoleFragment();

    // Supply index input as an argument.
    Bundle args = new Bundle();
    args.putString("rawContactId", rawContactId);
    args.putString("contactId", contactId);
    f.setArguments(args);

    return f;
}

public String[] getShownCustomer() {
    rawContactId = getArguments().getString("rawContactId", "");
    contactId = getArguments().getString("contactId", "");

    String[] contactIds = new String[2];
    contactIds[0] = getArguments().getString("rawContactId", "");
    contactIds[1] = getArguments().getString("contactId", "");
    return contactIds;
}

private class MoleDetailsLookup extends ItemDetailsLookup<Long> {

    @Nullable
    @Override
    public ItemDetails<Long> getItemDetails(@NonNull MotionEvent event) {
        View view = moleRecycler.findChildViewUnder(event.getX(), event.getY());
        if (view != null) {
            final RecyclerView.ViewHolder viewHolder = moleRecycler.getChildViewHolder(view);
            if (viewHolder instanceof MolesCursorAdapter.ViewHolder) {
                final MolesCursorAdapter.ViewHolder moleViewHolder = (MolesCursorAdapter.ViewHolder) viewHolder;
                return new ItemDetailsLookup.ItemDetails<Long>() {
                    @Override
                    public int getPosition() {
                        return viewHolder.getAdapterPosition();
                    }

                    @Nullable
                    @Override
                    public Long getSelectionKey() {
                        return Long.valueOf(moleViewHolder.position);
                    }
                };
            }
        }
        return null;
    }
}
}

And this is the RecyclerView.Adapater

public class MolesCursorAdapter extends RecyclerView.Adapter<MolesCursorAdapter.ViewHolder> {
private CursorAdapter mCursorAdapter;
private Context mContext;
private ViewHolder holder;

public MolesCursorAdapter(Context context, Cursor c) {
    mContext = context;
    mCursorAdapter = new CursorAdapter(mContext, c, 0) {

        @Override
        public View newView(Context context, Cursor cursor, ViewGroup parent) {
            View v = LayoutInflater.from(context).inflate(R.layout.moles, parent, false);
            return v;
        }

        @Override
        public void bindView(View view, final Context context, final Cursor cursor) {
            String s = cursor.getString(1);
            if (s != null) {
                holder.startDate.setText(s.substring(6, 8) + "/" + s.substring(4, 6) + "/" + s.substring(0, 4));
            } else {
                holder.startDate.setText("");
            }

            s = cursor.getString(2);
            if (s != null) {
                s = s.substring(6,8) + "/" + s.substring(4,6) + "/" + s.substring(0,4);
                holder.endDate.setText(s);
            } else {
                holder.endDate.setText("");
            }

            holder.round.setText(cursor.getString(3));

            holder.moleType.setText(cursor.getString(4));

        }
    };

}

public class ViewHolder extends RecyclerView.ViewHolder implements TextWatcher  {
    public int position;
    public EditText startDate;
    public EditText endDate;
    public EditText round;
    public EditText moleType;
    public TextView deleteBtn;

    public ViewHolder(View itemView) {
        super(itemView);
        startDate = (EditText) itemView.findViewById(R.id.startDate);
        endDate = (EditText) itemView.findViewById(R.id.endDate);
        round = (EditText) itemView.findViewById(R.id.round);
        moleType = (EditText) itemView.findViewById(R.id.moleType);
        deleteBtn = (TextView) itemView.findViewById(R.id.deleteBtn);
    }

    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
    }

    @Override
    public void afterTextChanged(Editable s) {
        String value = s.toString();
        ContentValues newValues = new ContentValues();

        if (TextUtils.isDigitsOnly(s)) {
            newValues.put(ContactsContract.Data.DATA3, value);
        } else {
            newValues.put(ContactsContract.Data.DATA4, value);
        }

        updateRepository(newValues);
    }
}

@Override
public int getItemCount() {
    return mCursorAdapter.getCount();
}

@Override
public long getItemId(int position) {
    return Long.valueOf(position);
}

@Override
public void onBindViewHolder(final ViewHolder holder, int position) {
    // Passing the binding operation to cursor loader
    mCursorAdapter.getCursor().moveToPosition(position);
    mCursorAdapter.bindView(holder.itemView, mContext, mCursorAdapter.getCursor());

}

@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    TextWatcher tw = new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        }

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
        }

        @Override
        public void afterTextChanged(Editable s) {
            String value = s.toString();
            ContentValues newValues = new ContentValues();

            if (TextUtils.isDigitsOnly(s)) {
                newValues.put(ContactsContract.Data.DATA3, value);
            } else {
                newValues.put(ContactsContract.Data.DATA4, value);
            }

            updateRepository(newValues);
        }
    };

    View v = mCursorAdapter.newView(mContext, mCursorAdapter.getCursor(), parent);
    holder = new ViewHolder(v);

    holder.startDate.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(final View v) {
            // business logic
        }
    });

    holder.endDate.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(final View v) {
            // business logic
      }
  });

    holder.round.addTextChangedListener(tw);

    holder.moleType.addTextChangedListener(tw);


    holder.deleteBtn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            String selectionClause = ContactsContract.Data._ID + "=?";
            String[] selectionArgs = new String[] {mCursorAdapter.getCursor().getString(0)};
            int rowsDeleted = mContext.getContentResolver().delete(
                    ContactsContract.Data.CONTENT_URI,
                    selectionClause,
                    selectionArgs
            );
        }
    });

    return holder;
}

}

0 Answers