Why my diffutils dont update my position in background - xamarin.forms

I'm using DiffUtils to update my Recyclerview.
Visually works but clicking an item returns it to me a position that does not match the item I clicked
I used NotifyDataChange that was able to solve the position problem. But visually, the last item is misconfigured.
I try to use NotifyDataChange that was able to solve the position problem. But visually, the last item is misconfigured.
My adapter:
class AdapterUser : RecyclerView.Adapter
{
//Global Var
public event EventHandler<int> ItemClick;
public List<Class.Result> ClassUser;
public Android.Content.Context context;
public class UserViewHolder : RecyclerView.ViewHolder
{
public static TextView txtName { get; set; }
public static TextView txtEspecialidade { get; set; }
public static CircleImageView Imagem { get; set; }
public static Bitmap bitmap { get; set; }
public static CircleImageView status { get; set; }
public static Button button { get; set; }
public UserViewHolder(View itemview, Action<int> listener) : base(itemview)
{
UserViewHolder.txtName = itemview.FindViewById<TextView>(Resource.Id.nameUser);
UserViewHolder.txtEspecialidade = itemview.FindViewById<TextView>(Resource.Id.speciality);
UserViewHolder.Imagem = itemview.FindViewById<CircleImageView>(Resource.Id.avatarUser);
UserViewHolder.status = itemview.FindViewById<CircleImageView>(Resource.Id.status);
UserViewHolder.button = itemview.FindViewById<Button>(Resource.Id.BtCriar);
}
}
public AdapterUser(List<Class.Result> user)
{
ClassUser = user;
}
public override int ItemCount
{
get { return ClassUser.Count(); }
}
public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int position)
{
//Method to transform the uri to bitmap
async Task MyMethodAsync()
{
// Do asynchronous work.
UserViewHolder.Imagem.SetImageBitmap(await Droid.Class.Functions.GetImageBitmapFromUrlAsync(ClassUser[position].PhotoUri));
};
UserViewHolder userHolder = holder as UserViewHolder;
if (ClassUser[position].PhotoUri != null)
{
MyMethodAsync();
}
else
{
UserViewHolder.Imagem.SetImageResource(Resource.Drawable.avatar);
}
if (ClassUser[position].IsOnline != true)
{
UserViewHolder.status.Visibility = ViewStates.Invisible;
}
else
{
UserViewHolder.status.Visibility = ViewStates.Visible;
}
UserViewHolder.txtName.Text = ClassUser[position].Name;
UserViewHolder.txtEspecialidade.Text = ClassUser[position].Specialty;
UserViewHolder.button.Click += (sender, args) =>
{
Toast.MakeText(context, ClassUser[position].Name, ToastLength.Short).Show();
Console.WriteLine(ClassUser[position].Name);
};
}
public override RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int viewType)
{
View itemView = LayoutInflater.From(parent.Context).Inflate(Resource.Layout.ResultUser, parent, false);
UserViewHolder vh = new UserViewHolder(itemView, OnClick);
context = parent.Context;
return vh;
}
private void OnClick(int obj)
{
}
public void Update(List<Class.Result> oldList, List<Class.Result> newList)
{
// Set detectMoves to true for smoother animations
MyDiffCallback callback = new MyDiffCallback(oldList, newList);
DiffUtil.DiffResult result = DiffUtil.CalculateDiff(callback);
this.ClassUser.Clear();
this.ClassUser.AddRange(newList);
// Despatch the updates to your RecyclerAdapter
result.DispatchUpdatesTo(this);
}
My DiffUtills:
class MyDiffCallback : DiffUtil.Callback
{
List<Class.Result> newList;
List<Class.Result> oldList;
public MyDiffCallback(List<Class.Result> oldList, List<Class.Result> newList)
{
this.oldList = oldList;
this.newList = newList;
}
public override int NewListSize => newList.Count;
public override int OldListSize => oldList.Count;
public override bool AreContentsTheSame(int oldItemPosition, int newItemPosition)
{
return oldList[oldItemPosition].IsOnline == newList[newItemPosition].IsOnline;
}
public override bool AreItemsTheSame(int oldItemPosition, int newItemPosition)
{
return oldList[oldItemPosition].RowKey == newList[newItemPosition].RowKey;
}
public override Java.Lang.Object GetChangePayload(int oldItemPosition, int newItemPosition)
{
return base.GetChangePayload(oldItemPosition, newItemPosition);
}
}
My activity:
ClassUtilizador = Data.Result.ToString();
Class.ClassUtilizador classUser = JsonConvert.DeserializeObject<Class.ClassUtilizador>(Data.Result.ToString());
//Call the function to update item
adapter.Update(classUserOld.Result, classUser.Result);
//Clean class and put the new data
classUserOld = new Class.ClassUtilizador();
classUserOld = classUser;
Update items, remove, add ou move items. Depending of my request.

You should call:
holder.bindingAdapterPosition

clicking an item returns it to me a position that does not match the
item I clicked
UserViewHolder.button.Click += (sender, args) =>
{
Toast.MakeText(context, ClassUser[position].Name, ToastLength.Short).Show();
Console.WriteLine(ClassUser[position].Name);
};
change to :
UserViewHolder.button.Tag = position;
UserViewHolder.button.SetOnClickListener(this);
then let your adapter extends View.IOnClickListener
class AdapterUser : RecyclerView.Adapter,View.IOnClickListener
public void OnClick(View v)
{
//here you could get the correct position
Toast.MakeText(context, v.Tag.ToString(), ToastLength.Short).Show();
}
if it doesn't update your Recyclerview, you could add the method in your adapter:
public void setData(List<Class.Result> newList)
{
this.ClassUser= new List<Class.Result>(newList);
}
in your update method :
public void Update(List<Class.Result> oldList, List<Class.Result> newList)
{
// Set detectMoves to true for smoother animations
MyDiffCallback callback = new MyDiffCallback(oldList, newList);
DiffUtil.DiffResult result = DiffUtil.CalculateDiff(callback);
this.ClassUser.Clear();
this.ClassUser.AddRange(newList);
setData(this.ClassUser);
// Despatch the updates to your RecyclerAdapter
result.DispatchUpdatesTo(this);
}

Related

Xamarin Forms: Selected items get cleared when perform search in listview

I have done the fetching of contacts from the phone using this blog.
Now I am trying to add the selection of contacts. Using a switch I have done the selection. But the selected contacts are clearing when performing a search operation.
xaml
<Switch
Toggled="OnToggledEvent"
HorizontalOptions="EndAndExpand"
VerticalOptions="CenterAndExpand"/>
xaml.cs
public List<Contact> contactList;
public MainPage(IContactsService contactService)
{
InitializeComponent();
contactList = new List<Contact>();
BindingContext = new ContactsViewModel(contactService);
}
void OnToggledEvent(object sender, EventArgs args)
{
ViewCell cell = (sender as Xamarin.Forms.Switch).Parent.Parent as ViewCell;
if (cell.BindingContext is Contact)
{
Contact contact = cell.BindingContext as Contact;
if (contact != null)
{
if (contact != null && !contactList.Contains(contact))
{
contactList.Add(contact);
}
else if (contact != null && contactList.Contains(contact))
{
contactList.Remove(contact);
}
}
}
Debug.WriteLine("contactList:>>" + contactList.Count);
}
ContactsViewModel
public class ContactsViewModel : INotifyPropertyChanged
{
IContactsService _contactService;
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public string Title => "Contacts";
string search;
public string SearchText
{
get { return search; }
set
{
if (search != value)
{
search = value;
OnPropertyChanged("SearchText");
if (string.IsNullOrEmpty(SearchText))
{
FilteredContacts = new ObservableCollection<Contact>(Contacts);
}
else
{
FilteredContacts = new ObservableCollection<Contact>(Contacts?.ToList()?.Where(s => !string.IsNullOrEmpty(s.Name) && s.Name.ToLower().Contains(SearchText.ToLower())));
}
}
}
}
public ObservableCollection<Contact> Contacts { get; set; }
ObservableCollection<Contact> filteredContacts;
public ObservableCollection<Contact> FilteredContacts
{
get { return filteredContacts; }
set
{
if (filteredContacts != value)
{
filteredContacts = value;
OnPropertyChanged("FilteredContacts");
}
}
}
public ContactsViewModel(IContactsService contactService)
{
_contactService = contactService;
Contacts = new ObservableCollection<Contact>();
Xamarin.Forms.BindingBase.EnableCollectionSynchronization(Contacts, null, ObservableCollectionCallback);
_contactService.OnContactLoaded += OnContactLoaded;
LoadContacts();
FilteredContacts = Contacts;
}
void ObservableCollectionCallback(IEnumerable collection, object context, Action accessMethod, bool writeAccess)
{
// `lock` ensures that only one thread access the collection at a time
lock (collection)
{
accessMethod?.Invoke();
}
}
private void OnContactLoaded(object sender, ContactEventArgs e)
{
Contacts.Add(e.Contact);
}
async Task LoadContacts()
{
try
{
await _contactService.RetrieveContactsAsync();
}
catch (TaskCanceledException)
{
Console.WriteLine("Task was cancelled");
}
}
}
I am adding the selected contact to a list when toggling the switch. If again click the switch I will remove the contact from the list. But the problem is when searching for a contact, already selected contacts get clear. I try to fix this using IsToggled property of switch, but no luck.
I have added a sample project here for the reference.
The itemsource updates every time you search , you should add a property inside model to log the status of the switch and implement INotifyPropertyChanged .
Model
public class Contact : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public string Name { get; set; }
public string Image { get; set; }
public string[] Emails { get; set; }
public string[] PhoneNumbers { get; set; }
private bool isToggled;
public bool IsToggled {
get {
return isToggled;
} set {
isToggled = value;
OnPropertyChanged();
}
}
}
in Xaml
<Switch IsToggled="{Binding IsToggled} //... >"
Modify the method OnToggledEvent as below
void OnToggledEvent(object sender, EventArgs args)
{
var s = sender as Xamarin.Forms.Switch;
var model = s.BindingContext as Contact;
if(model != null)
{
if (model.IsToggled && !contactList.Contains(model))
{
contactList.Add(model);
}
else if (!model.IsToggled && contactList.Contains(model))
{
contactList.Remove(model);
}
Debug.WriteLine("contactList:>>" + contactList.Count);
}
}

Realm Array of Strings in Android

I have been trying to store an array of strings in Realm database programmatically as given below:
Model Class:
public class Station extends RealmObject {
private String name;
// ... Generated getters and setters ...
}
Saving Data:
realm.executeTransactionAsync(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
Station station1 = realm.createObject(Station.class)
station1.setName(name1);
Station station2 = realm.createObject(Station.class)
station2.setName(name2);
//goes on till station8000
}
}, new Realm.Transaction.OnSuccess() {
#Override
public void onSuccess() {
// ...
});
Is there an alternate best way for this?
Why yes of course there is
public class Station extends RealmObject {
private String name;
// ... Generated getters and setters ...
}
and
// field variable
RealmResults<Station> stations;
// field variable
RealmChangeListener<RealmResults<Station>> changeListener = new RealmChangeListener<RealmResults<Station>>() {
#Override
public void onChange(RealmResults<Station> results) {
// handle onSuccess()
}
}
and
stations = realm.where(Station.class).findAll();
stations.addChangeListener(changeListener);
realm.executeTransactionAsync(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
Station station = new Station();
for(String stationName : listOfStationNames) {
station.setName(stationName);
realm.insert(station);
}
}
});
EDIT: Check out this sexy spinner.
public class DropdownSpinnerAdapter
extends BaseAdapter
implements SpinnerAdapter {
private static final String TAG = "DropdownSpinnerAdapter";
private boolean isItemSelected;
RealmResults<Station> content;
public ResultDropdownSpinnerAdapter(RealmResults<Station> objects) {
this.content = objects;
}
#Override
public int getCount() {
if(content == null || !content.isValid()) {
return 1;
}
return content.size() + 1;
}
#Override
public String getItem(int position) {
if(position <= 0) {
return "";
}
return content.get(position - 1);
}
#Override
public long getItemId(int position) {
return position;
}
public int findPosition(Station selectedItem) {
for(int i = 0, s = content.size(); i < s; i++) {
Station item = content.get(i);
if(item.equals(selectedItem)) {
return i + 1;
}
}
return 0;
}
static class ViewHolder {
TextView textView;
ImageView imageView;
public ViewHolder(View convertView) {
textView = ButterKnife.findById(convertView, R.id.dropdown_textview);
imageView = ButterKnife.findById(convertView, R.id.dropdown_arrow);
}
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView != null) {
if(!(convertView instanceof DropdownHeaderView)) {
convertView = null;
}
}
if(convertView == null) {
convertView = LayoutInflater.from(parent.getContext())
.inflate((isItemSelected) ? R.layout.dropdown_selected : R.layout.dropdown,
parent,
false);
ViewHolder viewHolder = new ViewHolder(convertView);
convertView.setTag(viewHolder);
}
ViewHolder viewHolder = (ViewHolder) convertView.getTag();
viewHolder.textView.setText(getItem(position).getName());
return convertView;
}
public void setItemSelected(boolean selected) {
this.isItemSelected = selected;
}
#Override
public View getDropDownView(int position, View convertView, ViewGroup parent) {
if(convertView != null) {
if(!(convertView instanceof DropdownView)) {
convertView = null;
}
}
if(convertView == null) {
convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.dropdown_noarrow, parent, false);
ViewHolder viewHolder = new ViewHolder(convertView);
convertView.setTag(viewHolder);
}
ViewHolder viewHolder = (ViewHolder) convertView.getTag();
viewHolder.textView.setText(getItem(position).getName());
return convertView;
}
public void updateContent(RealmResults<Station> content) {
this.content = content;
notifyDataSetChanged();
}
}

Android changing order of items run-time in Realm Recyclerview

I need to order the list of items based on a field say starredAt
I am loading the data in the recyclerview from Realm DB using RealmRecyclerView by thorbenprimke
The field changes it value on user's action i.e when user presses star button the item should be moved to top.
For this I am just updating the starredAt field of the object.
The items are already sorted by starredAt so realm loads the updated list but it randomly adds one more item to the recyclerview.
CheatSheet.java
public class CheatSheet extends RealmObject {
#PrimaryKey
private String id;
private RealmList<Item> items;
private String title;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public RealmList<Item> getItems() {
return items;
}
public void setItems(RealmList<Item> items) {
this.items = items;
}
}
Item.java
public class Item extends RealmObject {
#PrimaryKey
private String id;
private String description;
private Date starredAt;
public Item() {
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Date getStarredAt() {
return starredAt;
}
public void setStarredAt(Date starredAt) {
this.starredAt = starredAt;
}
}
CheatSheetActivity.java
public class MainActivity extends AppCompatActivity {
RealmRecyclerView revItems;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setData();
}
private void setData() {
rvItems = (RealmRecyclerView) findViewById(R.id.rev_items);
RealmResults<Item> items = Realm.getDefaultInstance().where(CheatSheet.class)
.equalTo("id", "some-id").findFirst().getItems()
.where()
.findAllSorted("starredAt", Sort.DESCENDING);
ItemRealmListAdapter itemRealmListAdapter =
new ItemRealmListAdapter(this, items,
true, true);
rvItems.setAdapter(itemRealmListAdapter);
}
ItemRealmListAdapter.java
public class ItemRealmListAdapter extends RealmBasedRecyclerViewAdapter<Item,
ItemRealmListAdapter.ItemViewHolder> {
RealmResults<Item> mItems;
public ItemRealmListAdapter(Context context, RealmResults<Item> realmResults,
boolean automaticUpdate, boolean animateResults) {
super(context, realmResults, automaticUpdate, animateResults);
this.mItems = realmResults;
}
#Override
public ItemViewHolder onCreateRealmViewHolder(ViewGroup viewGroup, int i) {
return new ItemViewHolder(LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.item_layout_cs_text, viewGroup, false));
}
public Item getItem(int position) {
return mItems.get(position);
}
#Override
public void onBindRealmViewHolder(ItemViewHolder itemViewHolder, int position) {
itemViewHolder.txtBody.setText(getItem(position).getDescription());
if (getItem(position).getStarredAt() != null) {
itemViewHolder.imvStar.setImageResource(R.drawable.ic_star_yellow);
}
itemViewHolder.imvStar.setOnClickListener(v -> handleStarClick(v,position));
}
private void handleStarClick(View v, int position) {
if (getItem(position).getStarredAt() != null) {
((ImageView) v).setImageResource(R.drawable.ic_star);
CheatSheetStorage.unStarItem("some-id", getItem(position));
} else {
((ImageView) v).setImageResource(R.drawable.ic_star_yellow);
CheatSheetStorage.starItem("some-id", getItem(position));
}
}
public static class ItemViewHolder extends RealmViewHolder {
#Bind(R.id.txt_cheat_sheet)
TextView txtBody;
#Bind(R.id.img_star)
ImageView imvStar;
public ItemViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}
}
}
CheatSheetStorage.java
public class CheatSheetStorage {
public static void unStarItem(String cheatSheetId, Item item) {
Realm realm = Realm.getDefaultInstance();
realm.beginTransaction();
CheatSheet cheatSheet = getCheatSheetById(cheatSheetId);
Item itemDB = cheatSheet.getItems().where().equalTo("id", item.getId()).findFirst();
itemDB.setStarredAt(null);
realm.commitTransaction();
}
public static void starItem(String cheatSheetId, Item item) {
Realm realm = Realm.getDefaultInstance();
realm.beginTransaction();
CheatSheet cheatSheet = getCheatSheetById(cheatSheetId);
Item itemDB = cheatSheet.getItems().where().equalTo("id", item.getId()).findFirst();
itemDB.setStarredAt(new Date());
realm.commitTransaction();
}
}
Please refer following screenshots for clearer idea :
Screenshot before starring
Screenshot after starring the sixth item
#Rohan-Peshkar - You will have to provide a animateExtraColumnName value to the adapter. For the animations, the adapter keeps track of the items and since that item's id doesn't change, the list isn't updated. With an additional column (in your case that should be the starredAt column - as long as it is stored as an Integer), the diffing algorithm will detect a change and the order is updated.
For reference: https://github.com/thorbenprimke/realm-recyclerview/blob/2835a543dce20993d8f98a4f773fa0e67132ce52/library/src/main/java/io/realm/RealmBasedRecyclerViewAdapter.java#L177
You can also check out the MainActivity in the example folder. The example changes a row's text from "ABC" to "Updated ABC" and the list recognizes the change because both the primary key and the quote field are used to basically create a composite key for diffing purposes.

How to render a xamarin.forms view in a custom renderer

I've got the following code:
Shared project:
using System;
using Xamarin.Forms;
namespace XamarinForms.Framework.Controls
{
public enum DrawerPosition
{
Left,
Right
}
public interface ISideDrawerNativeEventProxy
{
void RaiseSlideChanged(float percentage);
}
public class SideDrawer : Grid, ISideDrawerNativeEventProxy
{
#region DrawerSize
public static BindableProperty DrawerSizeProperty = BindableProperty.Create<SideDrawer, MaxedPercentSize>(d => d.DrawerSize, new MaxedPercentSize(MaxedPercentSizeType.Width, 80, 400));
public MaxedPercentSize DrawerSize
{
get { return (MaxedPercentSize) GetValue(DrawerSizeProperty); }
set { SetValue(DrawerSizeProperty, value); }
}
#endregion DrawerSize
#region IsOpen
public static BindableProperty IsOpenProperty = BindableProperty.Create<SideDrawer, bool>(d => d.IsOpen, default(bool));
public bool IsOpen
{
get { return (bool) GetValue(IsOpenProperty); }
set { SetValue(IsOpenProperty, value); }
}
#endregion IsOpen
#region DrawerPosition
public static BindableProperty DrawerPositionProperty = BindableProperty.Create<SideDrawer, DrawerPosition>(d => d.DrawerPosition, default(DrawerPosition));
public DrawerPosition DrawerPosition
{
get { return (DrawerPosition) GetValue(DrawerPositionProperty); }
set { SetValue(DrawerPositionProperty, value); }
}
#endregion DrawerPosition
public static BindableProperty MainContentProperty = BindableProperty.Create<SideDrawer, View>(d => d.MainContent, default(View));
public View MainContent
{
get { return (View) GetValue(MainContentProperty); }
set { SetValue(MainContentProperty, value); }
}
public static BindableProperty DrawerContentProperty = BindableProperty.Create<SideDrawer, View>(d => d.DrawerContent, default(View));
public View DrawerContent
{
get { return (View) GetValue(DrawerContentProperty); }
set { SetValue(DrawerContentProperty, value); }
}
#region DrawerLength
public static BindableProperty DrawerLengthProperty = BindableProperty.Create<SideDrawer, int>(d => d.DrawerLength, default(int), defaultValueCreator: DrawerLengthDefault);
private static int DrawerLengthDefault(SideDrawer bindable)
{
return 300;
}
public int DrawerLength
{
get { return (int) GetValue(DrawerLengthProperty); }
set { SetValue(DrawerLengthProperty, value); }
}
#endregion DrawerLength
#region IsContentTranslated
public static BindableProperty IsContentTranslatedProperty = BindableProperty.Create<SideDrawer, bool>(d => d.IsContentTranslated, true);
public bool IsContentTranslated
{
get { return (bool) GetValue(IsContentTranslatedProperty); }
set { SetValue(IsContentTranslatedProperty, value); }
}
#endregion IsContentTranslated
void ISideDrawerNativeEventProxy.RaiseSlideChanged(float percentage)
{
}
}
}
Android:
using System;
using System.ComponentModel;
using Android.Support.V4.Widget;
using Android.Views;
using Android.Widget;
using Mobile.XamarinForms.Droid.Controls.SideDrawer;
using Mobile.XamarinForms.Framework.Controls;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using Android.Graphics;
using Application = Android.App.Application;
using Color = Android.Graphics.Color;
using RelativeLayout = Android.Widget.RelativeLayout;
using View = Android.Views.View;
[assembly: ExportRenderer(typeof(SideDrawer), typeof(SideDrawerRenderer))]
namespace Mobile.XamarinForms.Droid.Controls.SideDrawer
{
// public class SideDrawerRenderer : Xamarin.Forms.Platform.Android.AppCompat.ViewRenderer<Framework.Controls.SideDrawer, DrawerLayout>, DrawerLayout.IDrawerListener
public class SideDrawerRenderer : ViewRenderer<Framework.Controls.SideDrawer, DrawerLayout>, DrawerLayout.IDrawerListener
{
private DrawerLayout _nativeDrawerLayout;
private MarginLayoutParams _contentLayoutParameters;
private RelativeLayout _contentView;
private TextView _drawerView;
protected override void OnElementChanged(ElementChangedEventArgs<Framework.Controls.SideDrawer> e)
{
base.OnElementChanged(e);
if (this.Control == null)
{
InitializeNativeControl();
}
if (e.OldElement != null)
{
_nativeDrawerLayout.SetDrawerListener(null);
}
if (e.NewElement != null)
{
_nativeDrawerLayout.SetDrawerListener(this);
}
}
private void InitializeNativeControl()
{
_nativeDrawerLayout = new DrawerLayout(Context.ApplicationContext);
_nativeDrawerLayout.SetBackgroundColor(Element.BackgroundColor.ToAndroid());
_contentLayoutParameters = new MarginLayoutParams(LayoutParams.MatchParent, LayoutParams.MatchParent);
var layoutParamsDrawer = new DrawerLayout.LayoutParams(Element.DrawerLength, LinearLayout.LayoutParams.MatchParent);
layoutParamsDrawer.Gravity = GetDrawerGravity();
_drawerView = GetDrawerView(layoutParamsDrawer);
_contentView = GetContentView(_contentLayoutParameters);
// this one works, but i need the content from my forms property
var contentChild = new RelativeLayout(Context.ApplicationContext);
var contentChildLParams = new RelativeLayout.LayoutParams(300, 300);
contentChild.SetBackgroundColor(Color.Yellow);
_contentView.AddView(contentChild, contentChildLParams);
// i need to figure out how to make this work
// var contentRenderer = RendererFactory.GetRenderer(Element.MainContent);
// _contentView.AddView(contentRenderer.ViewGroup, new LayoutParams(LayoutParams.MatchParent, LayoutParams.MatchParent));
_nativeDrawerLayout.AddView(_drawerView);
_nativeDrawerLayout.AddView(_contentView);
SetNativeControl(_nativeDrawerLayout);
}
private int GetDrawerGravity()
{
switch (Element.DrawerPosition)
{
case DrawerPosition.Left:
return (int)GravityFlags.Start;
case DrawerPosition.Right:
return (int)GravityFlags.End;
default:
throw new ArgumentOutOfRangeException();
}
}
private RelativeLayout GetContentView(LayoutParams layoutParameters)
{
var view = new RelativeLayout(Context.ApplicationContext);
view.LayoutParameters = layoutParameters;
view.SetBackgroundColor(Color.Red);
return view;
}
private TextView GetDrawerView(LayoutParams layoutParameters)
{
var view = new TextView(Context.ApplicationContext);
view.LayoutParameters = layoutParameters;
view.SetBackgroundColor(Color.Purple);
view.SetTextColor(Color.Blue);
view.SetText("just some text", TextView.BufferType.Editable);
return view;
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
switch (e.PropertyName)
{
case nameof(Framework.Controls.SideDrawer.Height):
break;
case nameof(Framework.Controls.SideDrawer.Width):
break;
}
}
public void OnDrawerClosed(View drawerView)
{
Element.IsOpen = false;
System.Diagnostics.Debug.WriteLine("OnDrawerClosed");
}
public void OnDrawerOpened(View drawerView)
{
Element.IsOpen = true;
System.Diagnostics.Debug.WriteLine("OnDrawerOpened");
}
public void OnDrawerSlide(View drawerView, float slideOffset)
{
switch (Element.DrawerPosition)
{
case DrawerPosition.Left:
_contentView.TranslationX = (int) Math.Abs(Element.DrawerLength*slideOffset);
break;
case DrawerPosition.Right:
_contentView.TranslationX = (int) Math.Abs(Element.DrawerLength*slideOffset)*-1;
break;
default:
throw new ArgumentOutOfRangeException();
}
_nativeDrawerLayout.BringChildToFront(_drawerView);
_nativeDrawerLayout.RequestLayout();
((ISideDrawerNativeEventProxy) Element)?.RaiseSlideChanged(slideOffset);
}
public void OnDrawerStateChanged(int newState)
{
// not really needed
// System.Diagnostics.Debug.WriteLine($"OnDrawerStateChanged {newState}");
}
}
}
How do i output the contents of e.g. MainContent (Shared project property)?
I was unable to find anything in xamarin docs about this and support is rather quiet about this topic so far (guess they are too busy).
Does anyone have experience with this issue?
Update: Reproduction solution
Android container:
internal sealed class FormsElementWrapper : FormsViewGroup
{
public override bool OnInterceptTouchEvent(MotionEvent ev)
{
return false;
}
private readonly IVisualElementRenderer _view;
public FormsElementWrapper(Xamarin.Forms.View content) : base(Application.Context)
{
_view = content != null ? Platform.CreateRenderer(content) : null;
if (_view == null)
return;
AddView(_view.ViewGroup);
}
protected override void OnLayout(bool changed, int left, int top, int right, int bottom)
{
if (_view == null)
return;
_view.Element.Layout(new Rectangle(0.0, 0.0, ContextExtensions.FromPixels(Context, right - left), ContextExtensions.FromPixels(Context, bottom - top)));
_view.UpdateLayout();
}
protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
MeasureSpecMode widthMode = MeasureSpec.GetMode(widthMeasureSpec);
MeasureSpecMode heightMode = MeasureSpec.GetMode(heightMeasureSpec);
int widthSize = MeasureSpec.GetSize(widthMeasureSpec);
int heightSize = MeasureSpec.GetSize(heightMeasureSpec);
int pxHeight = (int) ContextExtensions.ToPixels(Context, _view.Element.HeightRequest);
int pxWidth = (int) ContextExtensions.ToPixels(Context, _view.Element.WidthRequest);
var measuredWidth = widthMode != MeasureSpecMode.Exactly ? (widthMode != MeasureSpecMode.AtMost ? pxHeight : Math.Min(pxHeight, widthSize)) : widthSize;
var measuredHeight = heightMode != MeasureSpecMode.Exactly ? (heightMode != MeasureSpecMode.AtMost ? pxWidth : Math.Min(pxWidth, heightSize)) : heightSize;
SetMeasuredDimension(measuredWidth, measuredHeight);
}
}
android drawer:
using System;
using System.ComponentModel;
using Android.Support.V4.Widget;
using Android.Views;
using Android.Widget;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using Color = Android.Graphics.Color;
using RelativeLayout = Android.Widget.RelativeLayout;
using View = Android.Views.View;
[assembly: ExportRenderer(typeof(SideDrawer), typeof(SideDrawerRenderer))]
namespace MyNamespace.SideDrawer
{
public class SideDrawerRenderer : ViewRenderer<SideDrawer, DrawerLayout>, DrawerLayout.IDrawerListener
{
private DrawerLayout _nativeDrawerLayout;
private FormsElementWrapper _contentView;
private FormsElementWrapper _drawerView;
protected override void OnElementChanged(ElementChangedEventArgs<Framework.Controls.SideDrawer> e)
{
base.OnElementChanged(e);
if (this.Control == null)
{
InitializeNativeControl();
}
if (e.OldElement != null)
{
_nativeDrawerLayout.SetDrawerListener(null);
}
if (e.NewElement != null)
{
_nativeDrawerLayout.SetDrawerListener(this);
}
}
private void InitializeNativeControl()
{
_nativeDrawerLayout = new DrawerLayout(Context.ApplicationContext);
_nativeDrawerLayout.SetBackgroundColor(Element.BackgroundColor.ToAndroid());
AddDrawerLayer(_nativeDrawerLayout);
AddContentLayer(_nativeDrawerLayout);
SetNativeControl(_nativeDrawerLayout);
}
private void AddContentLayer(DrawerLayout nativeDrawerLayout)
{
_contentView = new FormsElementWrapper(Element.MainContent);
nativeDrawerLayout.AddView(_contentView);
}
private void AddDrawerLayer(DrawerLayout nativeDrawerLayout)
{
_drawerView = new FormsElementWrapper(Element.DrawerContent);
UpdateDrawerLength();
nativeDrawerLayout.AddView(_drawerView);
}
private int GetDrawerGravity()
{
switch (Element.DrawerPosition)
{
case DrawerPosition.Left:
return (int)GravityFlags.Start;
case DrawerPosition.Right:
return (int)GravityFlags.End;
default:
throw new ArgumentOutOfRangeException();
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
switch (e.PropertyName)
{
case nameof(Framework.Controls.SideDrawer.Height):
break;
case nameof(Framework.Controls.SideDrawer.Width):
break;
case nameof(Framework.Controls.SideDrawer.MainContent):
_contentView = new FormsElementWrapper(Element.MainContent);
break;
case nameof(Framework.Controls.SideDrawer.DrawerContent):
_drawerView = new FormsElementWrapper(Element.DrawerContent);
break;
case nameof(Framework.Controls.SideDrawer.IsOpen):
UpdateDrawerStateProgramatically();
break;
case nameof(Framework.Controls.SideDrawer.DrawerLength):
case nameof(Framework.Controls.SideDrawer.DrawerPosition):
UpdateDrawerLength();
break;
}
}
private void UpdateDrawerLength()
{
var layoutParamsDrawer = new DrawerLayout.LayoutParams(Element.DrawerLength, ViewGroup.LayoutParams.MatchParent);
layoutParamsDrawer.Gravity = GetDrawerGravity();
_drawerView.LayoutParameters = layoutParamsDrawer;
}
private void UpdateDrawerStateProgramatically()
{
if (Element.IsOpen)
{
Control.OpenDrawer(GetDrawerGravity());
}
else
{
Control.CloseDrawer(GetDrawerGravity());
}
}
public void OnDrawerClosed(View drawerView)
{
Element.IsOpen = false;
System.Diagnostics.Debug.WriteLine("OnDrawerClosed");
}
public void OnDrawerOpened(View drawerView)
{
Element.IsOpen = true;
System.Diagnostics.Debug.WriteLine("OnDrawerOpened");
}
public void OnDrawerSlide(View drawerView, float slideOffset)
{
switch (Element.DrawerPosition)
{
case DrawerPosition.Left:
_contentView.TranslationX = (int)Math.Abs(Element.DrawerLength * slideOffset);
break;
case DrawerPosition.Right:
_contentView.TranslationX = (int)Math.Abs(Element.DrawerLength * slideOffset) * -1;
break;
default:
throw new ArgumentOutOfRangeException();
}
_nativeDrawerLayout.BringChildToFront(_drawerView);
_nativeDrawerLayout.RequestLayout();
((ISideDrawerNativeEventProxy)Element)?.RaiseSlideChanged(slideOffset);
}
public void OnDrawerStateChanged(int newState)
{
}
}
}
Xamarin.Forms Control:
public enum DrawerPosition
{
Left,
Right
}
public interface ISideDrawerNativeEventProxy
{
void RaiseSlideChanged(float percentage);
}
public class SideDrawer : Grid, ISideDrawerNativeEventProxy
{
#region IsOpen
public static BindableProperty IsOpenProperty = BindableProperty.Create<SideDrawer, bool>(d => d.IsOpen, default(bool), defaultBindingMode: BindingMode.TwoWay);
public bool IsOpen
{
get { return (bool) GetValue(IsOpenProperty); }
set { SetValue(IsOpenProperty, value); }
}
#endregion IsOpen
#region DrawerPosition
public static BindableProperty DrawerPositionProperty = BindableProperty.Create<SideDrawer, DrawerPosition>(d => d.DrawerPosition, default(DrawerPosition));
public DrawerPosition DrawerPosition
{
get { return (DrawerPosition) GetValue(DrawerPositionProperty); }
set { SetValue(DrawerPositionProperty, value); }
}
#endregion DrawerPosition
public static BindableProperty MainContentProperty = BindableProperty.Create<SideDrawer, View>(d => d.MainContent, default(View), propertyChanging: MainContentPropertyChanging, propertyChanged: MainContentPropertyChanged);
private static void MainContentPropertyChanged(BindableObject bindable, View oldValue, View newValue)
{
if (newValue == null)
return;
newValue.Parent = (View)bindable;
}
private static void MainContentPropertyChanging(BindableObject bindable, View oldValue, View newValue)
{
if (oldValue == null)
return;
oldValue.Parent = null;
}
public View MainContent
{
get { return (View) GetValue(MainContentProperty); }
set { SetValue(MainContentProperty, value); }
}
public static BindableProperty DrawerContentProperty = BindableProperty.Create<SideDrawer, View>(d => d.DrawerContent, default(View), propertyChanging: DrawerContentPropertyChanging, propertyChanged: DrawerContentPropertyChanged);
private static void DrawerContentPropertyChanged(BindableObject bindable, View oldValue, View newValue)
{
if (newValue == null)
return;
newValue.Parent = (View)bindable;
}
private static void DrawerContentPropertyChanging(BindableObject bindable, View oldValue, View newValue)
{
if (oldValue == null)
return;
oldValue.Parent = null;
}
public View DrawerContent
{
get { return (View) GetValue(DrawerContentProperty); }
set { SetValue(DrawerContentProperty, value); }
}
#region DrawerLength
public static BindableProperty DrawerLengthProperty = BindableProperty.Create<SideDrawer, int>(d => d.DrawerLength, default(int), defaultValueCreator: DrawerLengthDefault);
private static int DrawerLengthDefault(SideDrawer bindable)
{
return 300;
}
public int DrawerLength
{
get { return (int) GetValue(DrawerLengthProperty); }
set { SetValue(DrawerLengthProperty, value); }
}
#endregion DrawerLength
#region IsContentTranslated
public static BindableProperty IsContentTranslatedProperty = BindableProperty.Create<SideDrawer, bool>(d => d.IsContentTranslated, true);
public bool IsContentTranslated
{
get { return (bool) GetValue(IsContentTranslatedProperty); }
set { SetValue(IsContentTranslatedProperty, value); }
}
#endregion IsContentTranslated
void ISideDrawerNativeEventProxy.RaiseSlideChanged(float percentage)
{
}
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
if (MainContent != null)
SetInheritedBindingContext(MainContent, BindingContext);
if (DrawerContent != null)
SetInheritedBindingContext(DrawerContent, BindingContext);
}
}
The major flaws in my original implementation were:
lack of measurement data being forwarded
not forwarding parent hierarchy
not inheriting bindingcontext

ListView/adapter throwing IndexOutOfBound

I have a listview with a getCount() of 7. I want all 7 items to be shown regardless if any data from my database is available to populate them. If no data is available then an item should just be blank with predetermined text.
When I have not hardcoded 7 database entries beforehand to go into the 7 views then I get an indexoutofbound exception when running the app due to the 7 items not being able to be populated accordingly. This happens in ListMealsAdapter.java when method Meal currentItem = getItem(position); is called and triggers public Meal getItem(int position).
I am looking for a condition statement that I can use for my listview/adapter that can handle an empty database so that the index does not go out of bounds. Also, is the BaseAdapter suited for what I want to do?
MainActivity.java
public class MainActivity extends BaseActivity {
public static final String TAG = "MainActivity";
private ListView mListviewMeals;
private MealDAO mMealDao;
private List<Meal> mListMeals;
private ListMealsAdapter mAdapter;
private SQLiteDatabase mDatabase;
DatabaseHelper mDbHelper;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
activateToolbar(1);
// initialize views
initViews();
// fill the dailyListView
mMealDao = new MealDAO(this);
mListMeals = mMealDao.getAllMeals();
mAdapter = new ListMealsAdapter(this, mListMeals, MainActivity.this);
mListviewMeals.setAdapter(mAdapter);
}
private void initViews() {
this.mListviewMeals = (ListView) findViewById(R.id.view_daily_list);
}
ListMealsAdapter.java
public class ListMealsAdapter extends BaseAdapter {
public static final String TAG = "ListMealsAdapter";
Activity mActivity;
private List<Meal> mItems;
private LayoutInflater mInflater;
public ListMealsAdapter(Context context, List<Meal> listMeals, Activity activity) {
super();
mActivity = activity;
this.setItems(listMeals);
this.mInflater = LayoutInflater.from(context);
}
#Override
public int getCount() {
return 7;
}
#Override
public Meal getItem(int position) {
return (getItems() != null && !getItems().isEmpty()) ? getItems().get(position) : null;
}
#Override
public long getItemId(int position) {
return (getItems() != null && !getItems().isEmpty()) ? getItems().get(position).getId() : position;
}
#Override
public View getView(int position, final View convertView, final ViewGroup parent) {
View v = convertView;
final ViewHolder holder;
if (v == null) {
v = mInflater.inflate(R.layout.list_item_daily, parent, false);
holder = new ViewHolder();
holder.txtDescription = (TextView) v.findViewById(R.id.txtBreakfast);
v.setTag(holder);
} else {
holder = (ViewHolder) v.getTag();
}
// fill row data
Meal currentItem = getItem(position);
if (currentItem != null) {
holder.txtDescription.setText(currentItem.getDescription());
}
return v;
}
public List<Meal> getItems() {
return mItems;
}
public void setItems(List<Meal> mItems) {
this.mItems = mItems;
}
class ViewHolder {
TextView txtDescription;
}
}
Meal.java
public class Meal implements Serializable {
public static final String TAG = "Meal";
private static final long serialVersionUID = -7406082437623008161L;
private long mId;
private int mType;
private String mDescription;
public Meal() {
}
public Meal(int type, String description) {
this.mType = type;
this.mDescription = description;
}
public long getId() {
return mId;
}
public void setId(long mId) {
this.mId = mId;
}
public int getType() {
return mType;
}
public void setType(int mType) {
this.mType = mType;
}
public String getDescription() {
return mDescription;
}
public void setDescription(String mDescription) {
this.mDescription = mDescription;
}
}
MealDAO.java
public class MealDAO {
public static final String TAG = "MealDAO";
private SQLiteDatabase mDatabase;
private DatabaseHelper mDbHelper;
private Context mContext;
private String[] mAllColumns = { DatabaseHelper.COLUMN_MEAL_ID,
DatabaseHelper.COLUMN_MEAL_TYPE, DatabaseHelper.COLUMN_MEAL_DESCRIPTION};
public MealDAO(Context context) {
this.mContext = context;
mDbHelper = new DatabaseHelper(context);
// open the database
try {
open();
} catch (SQLException e) {
Log.e(TAG, "SQLException on opening database " + e.getMessage());
e.printStackTrace();
}
}
public void open() throws SQLException {
mDatabase = mDbHelper.getWritableDatabase();
}
public void close() {
mDbHelper.close();
}
public List<Meal> getAllMeals() {
List<Meal> listMeals = new ArrayList<Meal>();
Cursor query = mDatabase.rawQuery("SELECT * from meal", null);
if(query.moveToFirst()) {
do {
// Cycle through all records
Meal meal = cursorToMeal(query);
listMeals.add(meal);
} while(query.moveToNext());
}
return listMeals;
}
public Meal getMealById(long id) {
Cursor cursor = mDatabase.query(DatabaseHelper.TABLE_MEALS, mAllColumns,
DatabaseHelper.COLUMN_MEAL_ID + " = ?",
new String[] { String.valueOf(id) }, null, null, null);
if (cursor != null) {
cursor.moveToFirst();
}
Meal meal = cursorToMeal(cursor);
return meal;
}
protected Meal cursorToMeal(Cursor cursor) {
Meal meal = new Meal();
meal.setId(cursor.getLong(0));
meal.setType(cursor.getInt(1));
meal.setDescription(cursor.getString(2));
return meal;
}
}
After a LOT of trial and error I finally found an acceptable solution to my problem. What I did was to add a default row to my database for the view items that I wanted to have a predetermined database entry when no data had been entered beforehand.
I then made sure to start at index 2, making sure that index 1 would be reserved for my default value. If the index comes out of bounds then the exception is caught and the default database entry will be added to the array.
public Meal getItem(int position) {
Meal result;
try {
result = (getItems() != null && !getItems().isEmpty()) ? getItems().get(position) : null;
} catch (Exception e) {
Meal default = getItem(0);
return default;
}
return result;
}
Meal currentItem = getItem(position + 1);
if (currentItem != null) {
holder.txtDescription.setText(currentItem.getDescription());
}
With that change things have been running smooth ever since. I hope this can help someone else as well.

Resources