I have same basic Expandable list in Xamarin with items that have EditText inside. So basically every item in list has "add 1" or "subtract 1" and EditText that shows value. Its basically a shopping cart where you have group of items and inside a group you can purchase multiple items.
Problem is when I click on "add 1" (+1) button I get completely different view and I update some other EditText instead EditText that is in same child view.
public override View GetChildView(int groupPosition, int childPosition, bool isLastChild, View convertView, ViewGroup parent)
{
Holder<HolderObject> holder;
var child = (JavaObjectWrapper<MobilePOSSubItem>)GetChild(groupPosition, childPosition);
if (convertView == null)
{
convertView = context.LayoutInflater.Inflate(Resource.Layout.mobilePOSTicketsSubItem, null);
holder = new Holder<HolderObject>(
new HolderObject {
GroupPosition = groupPosition,
ChildPosition = childPosition,
Amount = convertView.FindViewById<EditText>(Resource.Id.etAmount),
Tariff = convertView.FindViewById<TextView>(Resource.Id.tariff),
Name = convertView.FindViewById<TextView>(Resource.Id.name),
Rule = convertView.FindViewById<TextView>(Resource.Id.rule),
IconAllotment = convertView.FindViewById<ImageView>(Resource.Id.iconAllotment),
AddButton = convertView.FindViewById<ImageButton>(Resource.Id.add),
RemoveButton = convertView.FindViewById<ImageButton>(Resource.Id.remove)
});
convertView.Tag = holder;
convertView.SetTag(Resource.Id.etAmount, holder.Value.Amount);
convertView.SetTag(Resource.Id.tariff, holder.Value.Tariff);
convertView.SetTag(Resource.Id.name, holder.Value.Name);
convertView.SetTag(Resource.Id.rule, holder.Value.Rule);
convertView.SetTag(Resource.Id.iconAllotment, holder.Value.IconAllotment);
convertView.SetTag(Resource.Id.add, holder.Value.AddButton);
convertView.SetTag(Resource.Id.remove, holder.Value.RemoveButton);
} else
{
holder = (Holder<HolderObject>)convertView.Tag;
holder.Value.Amount.AfterTextChanged -= EtAmount_TextChanged;
}
holder.Value.Amount.SetText(child.Object.Value.ToString(), TextView.BufferType.Editable);
holder.Value.Amount.SetSelectAllOnFocus(true);
holder.Value.Amount.AfterTextChanged += EtAmount_TextChanged;
...
private void EtAmount_TextChanged(object sender, AfterTextChangedEventArgs e)
{
if (sender.GetType().BaseType == typeof(EditText))
{
var etAmount = (EditText)sender;
var parent = (View) etAmount.Parent.Parent;
Holder<HolderObject> holder = (Holder<HolderObject>)parent.Tag;
int value = 0;
string newValue = e.Editable.ToString();
try
{
if (!string.IsNullOrEmpty(newValue))
{
value = Convert.ToUInt16(newValue);
}
else
{
holder.Value.Amount.Text = "0";
}
}
catch
{
holder.Value.Amount.Text = "0";
}
items[holder.Value.GroupPosition].SubItems[holder.Value.ChildPosition].Value = value;
NotifyDataSetChanged();
//UpdateSelectedItem(child, value);
}
}
public void OnClick(View v)
{
HideSoftKeyboard(listView);
View parent = (View) v.Parent.Parent.Parent;
HolderObject holder = ((Holder<HolderObject>)parent.Tag).Value;
var child = (JavaObjectWrapper<MobilePOSSubItem>) GetChild(holder.GroupPosition, holder.ChildPosition);
if (holder.Amount != null)
{
int value;
if (int.TryParse(holder.Amount.Text, out value))
{
if (v.Id.Equals(Resource.Id.add))
{
if (value < 299 && (child.Object.ValueMax == null || value < child.Object.ValueMax))
{
value++;
holder.Amount.Text = value.ToString();
}
}
else if (v.Id.Equals(Resource.Id.remove))
{
if (value > 0 && (child.Object.ValueMin == null || value > child.Object.ValueMin))
{
value--;
holder.Amount.Text = value.ToString();
}
}
}
else
{
holder.Amount.Text = "0";
}
}
else
{
CultureInfo original = CultureInfo.CurrentCulture;
NumberFormatInfo noCurrencySymbol = (NumberFormatInfo)original.NumberFormat.Clone();
noCurrencySymbol.CurrencySymbol = string.Empty;
var cbArticle = (CheckBox)v;
child.Object.ArticleList.FirstOrDefault(x => x.ArticleId == cbArticle.Id).Value = (cbArticle.Checked) ? 1 : 0;
holder.Tariff.Text = string.Format("{0} {1}", child.Object.Currency, child.Object.GetTotalAmount().ToString("C", noCurrencySymbol));
}
}
So basically what I thought would work is:
If I scroll, EditText values gets to 0, button clicks get completely different view. So if I click in second item in second group (that would be groupPosition: 1 and childPosition: 1 based on indexes), I try to get Holder object and Holder object has groupPosition:0 and childPosition:5 which are completely wrong values.
I also set stable ids and I can confirm that all children has different ids:
public override long GetChildId(int groupPosition, int childPosition)
{
return (long) items[groupPosition].SubItems[childPosition].Idx;
}
public override bool HasStableIds
{
get
{
return true;
}
}
Problem was this line convertView.Tag = holder;
.
Attaching holder to a view should be last thing you do in function, so I moved that line to the end of function and everything worked, right before return convertView
. Mistake was that I first attached holder to view and then change it and added listeners to holder views. That should be last thing, first apply all things to holder views (like text, listeners, colors etc.) and at the end attach it to view.