Android: detecting removed item in ObservableList - java

I am using ObservableList in my model for binding. When the item is removed from ObservableList, I want to know about what item has been removed. So I am using ObservableList.OnListChangedCallback.
When the item is removed, it will called onItemRangeRemoved(). But the problem is the item has been removed from the ObservableList in onItemRangeRemoved().
catDatas.addOnListChangedCallback(new ObservableList.OnListChangedCallback<ObservableList<CatData>>()
{
#Override
public void onChanged(ObservableList<CatData> sender)
{
}
#Override
public void onItemRangeChanged(ObservableList<CatData> sender, int positionStart, int itemCount)
{
}
#Override
public void onItemRangeInserted(ObservableList<CatData> sender, int positionStart, int itemCount)
{
}
#Override
public void onItemRangeMoved(ObservableList<XROrderItemData> sender, int fromPosition, int toPosition, int itemCount)
{
}
#Override
public void onItemRangeRemoved(ObservableList<CatData> sender, int positionStart, int itemCount)
{
for (int idx = itemCount - 1; idx >= 0; idx--)
{
CatData data = sender.get(idx + positionStart); //this line will throw index out of bounds exception
...
...
...
uninitData(data);
}
}
});
Any idea how to capture the items that are removed?
Thanks...

The folks at Google overlooked the removeAll() method, so it does not notify observers. To fix this, use this custom class:
class MyObservableArrayList<T>: ObservableArrayList<T>() {
override fun removeAll(elements: Collection<T>): Boolean {
var success = false
elements.forEach { if (super.remove(it)) success = true }
return success
}
}

Related

Saving JTable Values in an ArrayList

I'm currently programming Yahtzee and I'm in the process of changing the player names. For this I use an extra form in which there is a JTable and a JButton.
Depending on the variable number of players in the table, an entry will be created where you can change the name. Only the second column should be editable - this also works.
However, I have no idea how to make it possible to add the contents from the second column to an ArrayList at the push of a button so that I can continue to use them.
Here is the implementation of my custom TableModel
public class SpielerBenennungTableModel implements TableModel {
private int spielerAnzahl = 0;
private ArrayList<TableModelListener> Listener = new ArrayList<TableModelListener>();
public SpielerBenennungTableModel(int spielerAnzahl){
this.spielerAnzahl = spielerAnzahl;
}
#Override
public int getRowCount() {
return spielerAnzahl;
}
#Override
public int getColumnCount() {
return 2;
}
#Override
public String getColumnName(int arg0) {
if(arg0 == 0){
return "Spieler";
}else{
return "Eigener Name";
}
}
#Override
public Class<?> getColumnClass(int arg0) {
return String.class;
}
#Override
public boolean isCellEditable(int arg0, int arg1) {
if(arg1 == 1){
return true;
}else{
return false;
}
}
#Override
public Object getValueAt(int arg0, int arg1) {
if(arg1 == 0){
return "Spieler: " + (arg0+1);
}else{
return rowData[arg0][arg1];
}
}
#Override
public void setValueAt(Object arg0, int arg1, int arg2) {
}
#Override
public void addTableModelListener(TableModelListener arg0) {
Listener.add(arg0);
}
#Override
public void removeTableModelListener(TableModelListener arg0) {
Listener.remove(arg0);
}
}
Try this out:
In your SpielerBenennungTableModel you need an object to hold the data you display. We will be using a List<String[]> that should look like this (I named it rows):
[
["Spieler: 1", "Bob"],
["Spieler: 2", "John"]
]
every time you change a value, the setValueAt method is called and will update the List<String[]> with the correct value.
Then when you use the getValueAt method, it will read from this same List<String[]>
class SpielerBenennungTableModel implements TableModel {
private int spielerAnzahl = 0;
private ArrayList<TableModelListener> Listener = new ArrayList<TableModelListener>();
// this will hold the data for every rows
private List<String[]> rows;
public SpielerBenennungTableModel(int spielerAnzahl){
this.spielerAnzahl = spielerAnzahl;
// initialize the list so that all rows are
// ["Spieler: n", ""]
rows = new ArrayList<>();
for(int i = 0; i<spielerAnzahl; i++) {
this.rows.add(new String[] { "Spieler: " + (i+1), "" });
}
}
#Override
public int getRowCount() {
return spielerAnzahl;
}
#Override
public int getColumnCount() {
return 2;
}
#Override
public String getColumnName(int col) {
return col == 0 ? "Spieler" : "Eigener Name";
}
#Override
public Class<?> getColumnClass(int col) {
return String.class;
}
#Override
public boolean isCellEditable(int row, int col) {
return col == 1;
}
#Override
public Object getValueAt(int row, int col) {
return rows.get(row)[col];
}
#Override
public void setValueAt(Object value, int row, int col) {
rows.get(row)[col] = value.toString();
}
#Override
public void addTableModelListener(TableModelListener arg0) {
Listener.add(arg0);
}
#Override
public void removeTableModelListener(TableModelListener arg0) {
Listener.remove(arg0);
}
}

Recyclerview DiffUtil scrolls my list to top when updated

I've decided to give DiffUtil a try instead of notifyDataSetChanged in my EndlessRecyclerView.
The problem is, after the first or second fling my list is scrolled to top, but the items are added to the list.
Here's my DiffUtil:
public class MovieDiffCallback extends DiffUtil.Callback {
List<Movie> mOldMovieList;
List<Movie> mNewMovieList;
public MovieDiffCallback(List<Movie> oldMovieList, List<Movie> newMovieList) {
this.mOldMovieList = oldMovieList;
this.mNewMovieList = newMovieList;
}
#Override
public int getOldListSize() {
return mOldMovieList != null ? mOldMovieList.size() : 0;
}
#Override
public int getNewListSize() {
return mNewMovieList != null ? mNewMovieList.size() : 0;
}
#Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
return mOldMovieList.get(oldItemPosition).getId().equals(mNewMovieList.get(newItemPosition).getId());
}
#Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
return mOldMovieList.get(oldItemPosition).equals(mNewMovieList.get(newItemPosition));
}
#Nullable
#Override
public Object getChangePayload(int oldItemPosition, int newItemPosition) {
return super.getChangePayload(oldItemPosition, newItemPosition);
}
}
And this is where I use it in my Fragment:
#Override
public void getMovies(List<Movie> moviesList) {
mDiffCallback = new MovieDiffCallback(mMoviesList, moviesList);
mDiffResult = DiffUtil.calculateDiff(mDiffCallback);
mMoviesList.addAll(moviesList);
mDiffResult.dispatchUpdatesTo(mAdapter);
isLoadedLandscape = true;
}
And this is in the EndlessRecyclerScroll:
#Override
public void onLoadMore(int page, int totalItemsCount, RecyclerView view) {
scrollPage++;
populateMap(scrollPage);
mPresenter.getSuggestedMovies(searchParamsMap);
}

EditText's onTextChanged being called with empty value on row recycle

I have a RecyclerView with an EditText in each row. Each EditText has a TextWatcher attached to it and an object in an array to store information about the row and the value of the EditText.
The problem I'm facing is that when the row goes offscreen, it gets recycled, but in that process, onTextChanged is called with an empty string, meaning an empty string is being saved to the row's information object. When that row is loaded again, it just loads a blank value, effectively deleting whatever content was in the EditText before. This seems like very useless functionality, because (as far as I can tell) there's no way to differentiate between a recycle clear and the user actually clearing the value in the EditText, making EditTexts in a RecyclerView completely useless.
my onBindViewHolder method is this:
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
int value = indicators.get(position).getValue();
System.out.println("VALUE: " + indicator.getValue());
if (value == -1) {
viewHolder.inputBox.setText("");
} else {
viewHolder.inputBox.setText(Integer.toString(value));
}
viewHolder.editListener.updatePosition(position);
}
My TextWatcher is this:
private class EditBoxListener implements TextWatcher {
private int position;
private CharSequence sequence;
private boolean initialCall = true;
public void updatePosition(int _position) {
position = _position;
}
#Override
public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) {}
#Override
public void onTextChanged(CharSequence charSequence, int start, int before, int count) {
System.out.println("TEXT CHANGED TO: " + charSequence.toString());
if (!initialCall) {
int value = -1;
try {
value = Integer.parseInt(charSequence.toString());
} catch (Exception e) {}
if (indicators.size() > 0) {
indicators.get(position).setValue(value);
}
} else {
initialCall = false;
}
}
#Override
public void afterTextChanged(Editable editable) {}
}
Well one way to combat this would be to simply check if the text is blank or not before doing anything with it, perhaps something like this (forgive my poor formatting):
#Override
public void onTextChanged(CharSequence charSequence, int start, int before, int count) {
if (!charSequence.equals("")) {
System.out.println("TEXT CHANGED TO: " + charSequence.toString());
if (!initialCall) {
int value = -1;
try {
value = Integer.parseInt(charSequence.toString());
} catch (Exception e) {}
if (indicators.size() > 0) {
indicators.get(position).setValue(value);
}
} else {
initialCall = false;
}
}
}

Get position of element in listview when TextWatcher triggers

I'm having problems to get the position of my View inside a ListView when TextWatcher triggers for changes in EditText.
Each CardView has two EditTexts and two Spinners. When I make some change in the values for the name of the product (the EditText in the left) and for the spinners, my code get the correctly the position of the CardView in the list.
However, when I change the value of the price by typing it, my code cannot get it's position.
The position of the CardView is gotten in the line...
final int posicao = Integer.parseInt(consumableInfo.getName()), which consumableInfo is the class listed in my Adapater, and consumableInfo.getName gets the name of the card, which is equal to the position of the card. Like "0", "1", "2"...
This happens because everytime I call...
holder.mAutoCompleteTextView.setOnItemClickListener for the AutoCompleteEditText on the left;
holder.mDivideConsumableSpinner.setOnItemSelectedListener for each spinner;
...my code iterates again over BindData. However, when I call...
holder.mConsumablePriceTextView.addTextChangedListener(priceTextWatcher) for the EditText on the right;
... my code DO NOT iterates again.
I'm trying to find another way to get it's position, but I'm having problems with that. Maybe forcing a way to posicao get the value, or creating a customTextWatcher that implements TextWatcher and gets consumableInfo as a parameter.
public class ConsumableAdapter extends RecyclerView.Adapter<ConsumableAdapter.ConsumableViewHolder> {
/*...some code ommited...*/
int posicaoGlobal;
public ConsumableAdapter(Context context, List<ConsumableInfo> contactList) {...}/*...some code ommited...*/
}
public class ConsumableViewHolder extends RecyclerView.ViewHolder {
public AutoCompleteTextView mAutoCompleteTextView;
public Spinner mDivideConsumableSpinner;
public Spinner mUnitsConsumableSpinner;
public EditText mConsumablePriceTextView;
public ConsumableViewHolder(View itemView) {
/*...*/
}
public void bindData(ConsumableInfo consumableInfo, ConsumableViewHolder holder, Context context) {
final int posicao = Integer.parseInt(consumableInfo.getName());
posicaoGlobal = posicao;
ArrayAdapter adapter = new ArrayAdapter(mContext, android.R.layout.select_dialog_item,
Constants.CONSUMABLE_CONSTANTS);
holder.mAutoCompleteTextView.setAdapter(adapter);
/* position is updated withmAutoCompleteTextView.setOnItemClickListener */
holder.mAutoCompleteTextView.setOnItemClickListener(new AdapterView.OnItemClickListener({
updateTotalPrice(posicao);
/*...*/
});
/*position is NOT updated with addTextChangedListener*/
holder.mConsumablePriceTextView.addTextChangedListener(priceTextWatcher);
/*position is updated with setOnItemSelectedListener in both Spinners*/
holder.mDivideConsumableSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
updateTotalPrice(posicao);
/*...*/
});
//product units
holder.mUnitsConsumableSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
updateTotalPrice(posicao);
/*...*/
});
}
private void updateTotalPrice(int posicao) {
/*...*/
mTotalPrice = getTotalPrice(BotequimActivity.mProductList, mPercent);
BotequimActivity.mTotalPriceTextView.setText(getTotalPriceString());
FormatStringAndText.setPriceTextViewSize(mTotalPrice, BotequimActivity.mTotalPriceTextView);
}
}
private void updateTotalPrice(int posicao, String priceString) {
/*...*/
BotequimActivity.mTotalPriceTextView.setText(getTotalPriceString());
FormatStringAndText.setPriceTextViewSize(mTotalPrice, BotequimActivity.mTotalPriceTextView);
}
private final TextWatcher priceTextWatcher = new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
if (count != 0) {
if (FormatStringAndText.isNumeric(s.toString())) {
mProductPriceBeforeChange = Double.parseDouble(s.toString());
}
}
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
Toast.makeText(mContext, "posicao =" + posicaoGlobal, Toast.LENGTH_SHORT).show();
if (s.toString().length() == 0) {
updateTotalPrice(posicaoGlobal, "0.00");
} else {
if (!isAutoCompleteClicked) {
if (FormatStringAndText.isNumeric(s.toString())) {
mProductPriceAfterChange = Double.parseDouble(s.toString());BotequimActivity.mTotalPriceTextView.setText(getTotalPriceString());
// FormatStringAndText.setPriceTextViewSize(mTotalPrice, BotequimActivity.mTotalPriceTextView);
updateTotalPrice(posicaoGlobal, s.toString());
} else {
}
} else {
isAutoCompleteClicked = false;
}
}
}
#Override
public void afterTextChanged(Editable s) {
}
};
public Double getTotalPrice(ArrayList<Product> productList, Double percent) {
mTotalPrice = 0;
for (Product product : productList) {
mTotalPrice = mTotalPrice + percent * (product.getUnits() * (product.getDoublePrice()) / product.getDividedBy());
}
return mTotalPrice;
}
}
You need to save the position when you create the TextWatcher. I would do this with an inner subclass:
// this is an inner class so it will have an implicit reference to
// the adapter (ConsumableAdapter.this)
public class PriceTextWatcher implements TextWatcher {
private int mPos;
public PriceTextWatcher(int position) {
super();
mPos = position;
}
// now add your TextWatcher implementation here and use mPos for position
#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) {}
}
Now you can initialize the position when you create the TextWatcher:
holder.mConsumablePriceTextView.addTextChangedListener(new PriceTextWatcher(posicao));
You will have multiple TextWatchers instead of the single final TextWatcher you currently have, but that's the trade-off for getting the position value where it needs to be.
Solved. I had to call priceTextWatcher as an argument with it's constructor, just like AdapterView.OnItemSelectedListener(). The correct one is:
holder.mConsumablePriceTextView.addTextChangedListener(new TextWatcher() {
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
//...
}
//...methods inside here
}

How to search in ArrayList of Custom Objects and then Update ListView with results?

I want to make a search from an ArrayList of custom objects and from those objects populate a ListView but I really don't know where to start....
Here's what I've got.
I get the search from a EditText and in an EditorActionListener I know when to start looking:
edittext.setOnEditorActionListener(new OnEditorActionListener() {
#Override
public boolean onEditorAction(TextView arg0, int arg1, KeyEvent arg2) {
//I got this from this site, another question
int searchListLength = medicos.size();
for (int i = 0; i < searchListLength; i++) {
if (medicos.get(i).getNombre().contains(edittext.getText().toString()))) {
//This is where Im suposed to do something but I dont know what to use to fill the ListView
}
}
return false;
}
});
Following is the structure of the custom object:
public class Medico implements Serializable{
private static final long serialVersionUID = 1L;
String nombre, especialidad1,especialidad2,especialidad3,celular,mail,telefono1,telefono2,ext,hospital,direccion;
double latitud,longitud;
public Medico(){}
public Medico(String nombre, String esp1,String esp2, String esp3, String cel,String mail,String tel1,String tel2, String ext,String hospital, String direccion, double lat, double lon){
this.nombre=nombre; //and so on.....
}
public void setNombre(String s){
this.nombre=s;
}
public void setEspecialidad1(String s){
this.especialidad1=s;
}
public String getNombre(){
return this.nombre;
}
public String getEspecialidad1(){
return this.especialidad1;
}
}
and so on.....
Thanks!
UPDATE (Code to update Adapter):
public void updatedData(ArrayList<Medico> itemsArrayList) {
myAdapter.clear();
if (itemsArrayList != null){
for (Medico object : itemsArrayList) {
//myAdapter.insert((Medico) object, myAdapter.getCount());
myAdapter.addAll(object);
//myAdapter.addAll(itemsArrayList);
}
}
myAdapter.notifyDataSetChanged();
}
You need to update the Adapter that you are using to to populate the ListView.
edittext.setOnEditorActionListener(new OnEditorActionListener() {
#Override
public boolean onEditorAction(TextView arg0, int arg1, KeyEvent arg2) {
//I will store the matches on this list.
List<Medico> resultado = new ArrayList<Medico>();
int searchListLength = medicos.size();
for (int i = 0; i < searchListLength; i++) {
if (medicos.get(i).getNombre().contains(searchsds)) {
// I found a match, I will add it to results
resultado.add(medicos.get(i));
}
}
//At the end, update the Adapter. I will assume that you have something like this.
myAdapter.setValues(resultado);
//You must write the code to set the values on your adapter. If you could post the way you are populating the list view I could help you out with more information.
return false;
}
});

Categories