1

I have a RecyclerView inside a fragment where each line has an adapter which inflates a layout which looks as follows: enter image description here


I want to access to the value of the EditText (in the following code numberET) of each row and pick the value if EditText is not empty.
How can I cycle on each element of the RecyclerView (I think inside the adapter) to have this behaviour? How can I access the EditText for each element to retrieve the value and use them inside the fragment?

Adapter: `

public class UserFBEditTextAdapter <T extends UserFBEditTextAdapter.ViewHolder> extends UserFBAdapter<UserFBEditTextAdapter.ViewHolder>{
public UserFBEditTextAdapter(List<UserFB> users,int layoutId, Context context) {
    super(users, layoutId, context);
}

@Override
public UserFBEditTextAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    View view = LayoutInflater.from(parent.getContext()).inflate(layoutId, parent, false);
    return new UserFBEditTextAdapter.ViewHolder(view);
}

@Override
public void onBindViewHolder(UserFBAdapter.ViewHolder holder, int position) {
    holder.userFB = users.get(position);
    holder.usernameTV.setText(holder.userFB.getName());
}

public class ViewHolder extends UserFBAdapter.ViewHolder {
    protected EditText numberET;

    public ViewHolder(View itemView) {
        super(itemView);
        numberET = (EditText) itemView.findViewById(R.id.number_et);
    }
    }
}`

Fragment:

public class ExpenseCustomFragment extends Fragment {

private OnFragmentInteractionListener mListener;
private UserFBAdapter adapter;
private RecyclerView userCustomList;

public ExpenseCustomFragment() {
    // Required empty public constructor
}

public static ExpenseCustomFragment newInstance() {
    ExpenseCustomFragment fragment = new ExpenseCustomFragment();

    return fragment;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    // Inflate the layout for this fragment
    View view = inflater.inflate(R.layout.fragment_expense_custom, container, false);

    userCustomList = (RecyclerView) view.findViewById(R.id.amountlist_rv);
    userCustomList.setLayoutManager(new LinearLayoutManager(getContext()));

    NewExpenseDescriptionActivity activity = (NewExpenseDescriptionActivity) getActivity();
    adapter = new UserFBEditTextAdapter(activity.getUsersGroup(),  R.layout.listitem_expensecustom, getContext());

    userCustomList.setAdapter(adapter);


    return view;
}
    public interface OnFragmentInteractionListener {
        // TODO: Update argument type and name
        void onFragmentInteraction(Uri uri);
    }
}
A. Wolf
  • 1,309
  • 17
  • 39

1 Answers1

1

You have to retain that data in some map-based data structure, and then, whenever those values are needed, iterate over that data structure.

You cannot rely on saving that data in a ViewHolder, because ViewHolders are being reused as soon as you perform scrolling. If you currently do not save the data that is filled in EditText, then you'll lose that data if you have many items and perform scrolling (i.e. screen fits 10 items, but your adapter is 20 items, as soon as you scroll to 15th item, the EditText value for the first item will be lost).

private Map<Integer, String> map = new ArrayMap<>(adapterSize);

...

@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
    String text = map.get(holder.getAdapterPosition());
    // maybe we haven't yet saved text for this position
    holder.editText.setText(text != null ? text : "");
    // updated value in map as soon as the `EditText` in this position changes
    holder.editText.addTextChangedListener(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) {
            map.put(holder.getAdapterPosition(), s.toString());
        }

        @Override
        public void afterTextChanged(Editable s) {
        }
    });
}

Now you'll have access to all EditText values in your RecyclerView. The only change that you can consider is updating map after user stops typing. Currently if user types "123456789" the map will be updated 9 times, whereas we need only once. An easy solution to this can be using RxJava's debounce operator combined with RxBinding library. This maybe sounds complicated, but you can see how plain it is in this answer.

This will work. But after you perform scrolling up and forth, soon you'll find out that some mess is going on there. That's because each time onBindViewHolder() gets called a new TextWatcher is being added to the EditText that already has a TextWatcher attached to it. Thus, you also have to take care of removing the TextWatcher after your ViewHolder is being recycled.

But there is no an API to remove all TextWatcher of the EditText. You can use a custom EditText implementation shown in this answer which will clear all TextWatcher attached to this EditText:

@Override
public void onViewRecycled(MyViewHolder holder) {
    holder.editText.clearTextChangeListeners();
    super.onViewRecycled(holder);
}
Community
  • 1
  • 1
azizbekian
  • 60,783
  • 13
  • 169
  • 249
  • Thank you! So if retrieve the holder from the `RecyclerView` I'll have the same issue due to a big number of the item in the `RecyclerView`. – A. Wolf Apr 23 '17 at 15:53
  • Couldn't understand what you mean. Can you perephrase that? – azizbekian Apr 23 '17 at 15:59
  • I mean, I get from the `RecyclerView` the holder associated at a given position through `recyclerView.findViewHolderForLayoutPosition(position)`. If I cycle on the `ViewHolder` contained in the `RecyclerView`and the `RecyclerView` has for example 20 items, while my `Fragment` can display only 4 items at a time, I will lose the content of `EditText` which are not displayed. Have I got it right? – A. Wolf Apr 23 '17 at 16:09
  • 1
    Thank you for your explanation! – A. Wolf Apr 23 '17 at 16:15