Recycler View Adapter with EditText - android

Ok, my problem is saving inputs of edit text.
I have a recycler view with some edit text inside. I have an adapter and button "add" to add another new edit text. There is a problem because if I write something inside one edit text it doesn't always save when I change focus or I cick on "add" button.
Here is my hosting fragment:
private RecyclerView mRecyclerView;
private RecyclerView.Adapter mAdapter;
mRecyclerView = (RecyclerView) view.findViewById(R.id.recyclerView);
mRecyclerView.setHasFixedSize(true);
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(view.getContext());
mRecyclerView.setLayoutManager(mLayoutManager);
mAdapter = new AddPlayerAdapter(players);
mRecyclerView.setAdapter(mAdapter);
addButton.setOnClickListener {
// overrides
players.add(new Player(""));
mAdapter.notifyDataSetChanged();
}
So now, after click on "add" button new Player class is added to class Players (item players) and adapter is notified, I see on my screen that new item is added (declared in AddPlayerViewHolder). It works fine.
Here are fragments of my adapter class:
public AddPlayerAdapter(Players players) {
this.players = players;
}
#Override
public AddPlayerViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.add_player, parent, false);
return new AddPlayerViewHolder(v);
}
#Override
public void onBindViewHolder(final AddPlayerViewHolder holder, final int position) {
holder.name.setText(players.get(position).getName());
holder.name.setHint("Player " + Integer.toString(position+1));
holder.name.setOnFocusChangeListener(new View.OnFocusChangeListener() {
#Override
public void onFocusChange(View view, boolean b) {
if (view != null) players.get(position).setName(holder.name.getText().toString());
}
});
}
So each time focus is changed field is updated and it should contain actual data.
But, when I put some data in one edit text, then press "start" button (which open another fragment with "players" data) the focus don't lose and data is not updated. So, I think the idea with OnFocusChangeListener is wrong, but I don't see here anything better.
Could anyone of you make me an example of proper update data inside edit text boxes like here?

I think it is common problem, hope someone will be interested in solution.
You can simply add TextWatcher to save entered player name.
public class EditTextSaverWatcher implements TextWatcher {
private Player player;
public MyTextWatcher(Player player) {
this.player= player;
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// Another option is to save name here
}
#Override
public void afterTextChanged(Editable s) {
player.setName(s.toString());
}
}
To add the text watcher, you should use
editText.addTextChangedListener(new EditTextSaverWatcher(concretePlayer));

Related

RecyclerView data move to other items when I scroll or add new item

I am trying to make a recyclerView that I can add items and each item has EditText and CheckBox, ImageView, etc.
I successfully created the feature of adding new Item and it holds the data with TextWatcher in onBindViewHolder(). And the data of TextView stays well.
This is onBindViewHolder():
#Override
public void onBindViewHolder(#NonNull final EditMessageViewHolder holder, final int position) {
MessageItem item = items.get(position);
holder.messageET.setText(item.getMessage());
holder.messageET.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
#Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
items.get(position).setMessage(charSequence.toString());
}
#Override
public void afterTextChanged(Editable editable) {
}
});
}
This is addItem() and getMessageItem() in the Adapter:
public void addItem(MessageItem messageItem) {
items.add(messageItem);
notifyItemInserted(items.size());
}
public ArrayList<MessageItem> getMessageItems() {
return items;
}
However, the problem is when I add more items or scroll up or down. The data mix in wrong orders and wrong data. How can I handle this problem and How can I prevent this problem with CheckBox and ImageView as well?
I think instead of using position in your onBindViewHolder(), try using adapter position. Which might solve your problem.
MessageItem item = items.get(position);
Instead use
MessageItem item = items.get(holder.adapterPosition);
Let me know if this works.
If this doesn't work try using notifyItemInsertedRange instead notifyiteminserted.
Thanks
Did you try setHasFixedSized(true) or setHasStableIds(true) method?
First method will allow RecyclerView to avoid invalidating the whole layout when its adapter contents change.
Second method set stable ids to each items.

setText() of EditText not working inside onTextChanged method

From a newbie in android, I have a custom ListView of food items, where each item has item name, quantity and amount. quantity is Edit text and amount is text view. Initially in amount field i'm displaying price for one quantity of item. when the user enters the quantity (Integer) i'm trying to multiply the quantity with amount and display the total amount in the same text view(i.e textView amount)
So i'm using onTextChanged of TextWatcher .
When the user enters the quantity i'm able to receive it and multiply with original amount to get the total amount but when i'm trying to set it using textviewAmount.setText(total amount) inside onTextChanged method its not working.
While searching for the solution I came across this this .which says
"The EditText appears to have an issue with resetting text in onCreateView"
Below is
the sample code of what i have done
Fragment
public class FoodListFragment extends Fragment {
List<FoodListCell> foodListCells ;
ListView listView ;
HotelApp hotelApp;
FragmentTransaction ft;
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, Bundle savedInstanceState) {
hotelApp = (HotelApp)getActivity().getApplicationContext();
View view = inflater.inflate(R.layout.food_listview,container,false);
Log.d("FoodListActivity" ," onCreate");
foodListCells = new ArrayList<>();
listView = (ListView)view.findViewById(R.id.list_view);
foodListCells.add(new FoodListCell("Diet-Vegetable Soup","75.0"));
foodListCells.add(new FoodListCell("Bean-Bacon ","95.0"));
foodListCells.add(new FoodListCell("Chicken Noodle Soup","75.0"));
FoodListAdopter adapter = new FoodListAdopter(hotelApp, R.layout.foodbox_layout, foodListCells);
listView.setAdapter(adapter);
return view;
}}
Adopter
public class MenuAdopter extends ArrayAdapter<FoodListCell> {
Context context;
int resource;
List<FoodListCell> foodListCells;
HotelApp hotelApp;
EditText quantity ;
TextView foodBoxAmt ;
Double finalAmt = 0.0, amtForOne = 0.0;
public View getView(int position, #Nullable View convertView, #NonNull ViewGroup parent) {
hotelApp = (HotelApp)this.context;
LayoutInflater layoutInflater = LayoutInflater.from(context);
View view = layoutInflater.inflate(R.layout.foodbox_layout,null);
TextView foodBoxDesc = (TextView) view.findViewById(R.id.foodBoxDesc);
foodBoxAmt = (TextView) view.findViewById(R.id.foodBoxAmt);
Button button = (Button) view.findViewById(R.id.addToCartButton);
quantity = (EditText) view.findViewById(R.id.qnty);
final FoodListCell foodListCell = foodListCells.get(position);
foodBoxDesc.setText(foodListCell.getDesc());
foodBoxAmt.setText("Rs." +foodListCell.getPrice());
amtForOne = Double.parseDouble(foodListCell.getPrice().toString());
quantity.addTextChangedListener(new EditTextListener(){
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
finalAmt=Double.parseDouble(foodListCell.getPrice().toString())*Double.parseDouble(s.toString());
foodBoxAmt.setText("Rs." +finalAmt.toString());
}}
);
return view;
}}
TextWatcher
public class EditTextListener implements 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) {
}}
Thanks for your help :)
Well, This is not the right way to handle this, Even if you figure out a way to set the text on textview, After you scroll the page the view is getting destroyed and next time when it gets created it uses the default value and not the one you specified!
I suggest two things, First do not use ListView, Instead use RecylerView. RecyclerView is a replacement for ListView since 2 years ago. Second and more importantly whenever your list changes don't update the view, update the model itself, then ask the adapter to recreate the view. It will create the view with updated values. You just have to call notifyDataSetChanged() on the adapter.
Please use String.valueOf(value)
If still value does not set than their is some problem in adapter view .

Update TextView in selected RecyclerView rows

I have a RecyclerView inside a fragment where each line has an adapter which inflates a layout which looks as follows:
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);
}
}
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);
}

Custom ListView adapter. TextChangedListener calls for wrong EditText

I have the list of travelers with custom adapter what consist two EditText - edtFirstName and edtLastName. I want when user enters text save changes to List, and when next button click send this List to another activity.
My code:
public class TravellersAdapter extends BaseAdapter {
private List<Traveler> itemsList;
private LayoutInflater inflater;
private Activity context;
public TravellersAdapter(Activity context, List<Traveler> itemsList) {
super();
this.itemsList = itemsList;
this.context = context;
inflater = LayoutInflater.from(context);
}
public int getCount() { return itemsList.size(); }
public Object getItem(int i) { return itemsList.get(i); }
public View getView(final int position, View view, ViewGroup viewGroup) {
if (view == null) {
view = inflater.inflate(R.layout.traveller_item, null);
}
Traveler currentItem = (Traveler) getItem(position);
EditText firstNameView = (EditText) view.findViewById(R.id.edtFirstName);
firstNameView.addTextChangedListener(new TextWatcher() {
public void afterTextChanged(Editable editable) {
currentItem.setFirstName(editable.toString());
}
});
return view;
}
}
For exemple List itemsList consist 5 items. When I edit 2-4 element all ok, but when I edit first or last element edited value assigned to all element in List. In dubugger i saw that method afterTextChanged calls 5 times with different values of position.
How to fix it?
in getView method, the parameter position gives the position of the newly created childView, not the clicked childView's position.
use this to get the correct position:
final int actual_position = myList.getPositionForView((View) v.getParent());
in onClick(View v); of the onClickListener of any View. In you case, you must implement onTextChangedListener for that EditText.
here:
myList is the ListView
v is the View you clicked, in this case the childView of the parent(myList).
The issue happens because views are reusable (that is by design in Android API). So eventually you may assign more than 1 text watcher to the same text view. And all of the assigned watchers are fired when text inside of the text view is changed.
A quick fix (and non-optimal if the list is really long, say, of 1000+ items) would be to have a map of Traweller -> TextWatcher.
Then inside of getView() you can do this (pseudo-code):
check the map if there is a TextWatcher for this Traweller
if map does not have any, then create a new TextWatcher, put in the map and assign to EditText
otherwise detach the TextWatcher from the EditText and remove from the map
create a new TextWatcher, put in the map and assign to EditText
Create one more EditText in the screen that is invisible with name invivisbleEt.
And do the following thing in the addTextChangedListener
firstNameView.addTextChangedListener(new TextWatcher() {
#Override
public void afterTextChanged(Editable editable) {
if(!firstNameView.isFocused())
currentItem.setFirstName(editable.toString());
}
});
Also add this code in the onCreate method for ListView object.
lv.setOnScrollListener(new AbsListView.OnScrollListener() {
//public boolean scrolling;
#Override
public void onScrollStateChanged(AbsListView absListView, int scrollState) {
invivisbleEt.requestFocus();
}
#Override
public void onScroll(AbsListView absListView, int i, int i1, int i2) {
}
});

How to get the Value out of an RecyclerView item

I have a Fragment which carries a Button and a RecyclerView, set up by an RecyclerView Adapter. In the RecyclerView are several Items, one of it is a EditText. Now I want that when the Button is clicked(which is NOT in the RecyclerView object), that I get the values of the EditTexts.
I already tried to get the recyclerView.getItemAtPosition() but there is no function like that, also tried the same for the adapter. So I would need something like
ArrayList s.add(recyclerView.getItemAtPosition(position).getEditText().getText().toString());
This is my Adapter:
public class RVSetAdapter extends RecyclerView.Adapter<RVSetAdapter.ViewHolder> {
private Exercise exercise;
public static class ViewHolder extends RecyclerView.ViewHolder {
public EditText et_weight;
public TextView tv_sets,tv_indication;
public ViewHolder(#NonNull final View itemView) {
super(itemView);
tv_sets = itemView.findViewById(R.id.tv_sets);
tv_indication = itemView.findViewById(R.id.tv_indication);
et_weight = itemView.findViewById(R.id.et_weight);
}
}
public RVSetAdapter(Exercise exercise) {
this.exercise = exercise;
}
#NonNull
#Override
public RVSetAdapter.ViewHolder onCreateViewHolder(#NonNull ViewGroup viewGroup, int i) {
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.rv_set,viewGroup,false);
RVSetAdapter.ViewHolder vh_set = new RVSetAdapter.ViewHolder(view);
return vh_set;
}
#Override
public void onBindViewHolder(#NonNull final RVSetAdapter.ViewHolder viewHolder,final int i) {
if(exercise.getKind() == 80) {
viewHolder.tv_sets.setText("");
viewHolder.tv_indication.setText("sec.");
}else if(exercise.getKind() == 90) {
viewHolder.tv_sets.setText("");
viewHolder.tv_indication.setText("min.");
}else {
viewHolder.tv_sets.setText(Integer.toString(i + 1) + ".");
}
viewHolder.et_weight.setText(Integer.toString(exercise.getWeights().get(i)));
}
#Override
public int getItemCount() {
return exercise.getWeights().size();
}
}
this is my Fragment:
final View view = layoutInflater.inflate(R.layout.fragment_exercise, container,false);
ImageView iv_exercise = view.findViewById(R.id.iv_exercise);
ImageView iv_musclekind = view.findViewById(R.id.iv_musclekind);
ImageView iv_save = view.findViewById(R.id.iv_save);
TextView tv_exercisename = view.findViewById(R.id.tv_exercisename);
TextView tv_exercisedescription = view.findViewById(R.id.tv_exercisedescription);
iv_exercise.setImageResource(exercises.get(position).getImage());
iv_musclekind.setImageResource(exercises.get(position).getMusclekindImage());
tv_exercisename.setText(exercises.get(position).getName());
tv_exercisedescription.setText(exercises.get(position).getDescription());
iv_save.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//here I want to get the Values of the EditTexts and put them into an Array
}
});
recyclerView = view.findViewById(R.id.rv_sets);
recyclerView.setHasFixedSize(true); //maybe change this
layoutManager = new LinearLayoutManager(view.getContext());
adapter = new RVSetAdapter(exercises.get(position));
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(adapter);
container.addView(view);
return view;
I don't have any ideas to go on so I would appreciate your help. If there is any uncertainty with my description of the problem please don't hesitate to ask.
Greetings Alexander
With RecyclerView, you have to understand that your EditTexts will be recycled. For example, if you have a list of 200 items, and it shows 2 items at one time, you will only ever have 2 EditText. They will reuse the higher EditText for the lower elements.
For example, here is a list that contains EditText showing only 2 at a time, and as the user scrolls, it will recycle and reuse them.
EditText A
Edittext B
EditText A (recycled)
EditText B (recycled)
....
This means you cannot just loop over all the elements later and get the values, as they don't store their values.
So, what you want to do, is when the user modifies an EditText, you want to store that value right away. You can do this by adding a TextWatcher to your EditText.
Note - I did assume you store your weights as String values, so I just took the value from the EditText and stored it into your Exercise Object. You may want to convert it before that.
public class RVSetAdapter extends RecyclerView.Adapter<RVSetAdapter.ViewHolder> {
private Exercise exercise;
// ...
#Override
public void onBindViewHolder(#NonNull final RVSetAdapter.ViewHolder viewHolder,final int i) {
// ...
viewHolder.et_weight.setText(Integer.toString(exercise.getWeights().get(i)));
viewHolder.et_weight..addTextChangedListener(new TextWatcher() {
public void afterTextChanged(Editable s) {
// This will be the text from the EditText
String text = s.toString();
// Store the value back into your exercise Object.
exercise.getWeights().get(i).setWeight(text);
}
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
public void onTextChanged(CharSequence s, int start, int before, int count) {}
});
}
// ...
// Add a method for easy access to your weights.
public ArrayList<String> getWeights() {
return exercise.getWeights();
}
}
And now, within your Fragment, you can easily get the values out of your RVSetAdapter.
public View onCreateView() {
final View view = layoutInflater.inflate(R.layout.fragment_exercise, container,false);
// ...
iv_save.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// Use the method we added to your adapter to return the weights.
ArrayList<String> weights = adapter.getWeights();
}
});
// ...
return view;
}
I think you should use ArrayList in Adapter class to keep your items (or just Strings of EditText components). Add String to ArrayList in your onBindViewHolder() after you set text for editext. Then make a function which will get item from your ArrayList like:
public String getItem(int position){
arrayList.get(position);
}
and call it from your onClick() function in Fragment.
I think you can create static button and you can then access that button in your adapter then implement the functionality on the onclick of your button.
static Button btn;
Then implement like this in your adapter...
btn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
for(int i=0;i<arraylist.size();i++)
{
arr[i]= holder.edit_Text.getText().toString();
}
}
});
and put this onclick in your onbindviewholder method.

Categories

Resources