I want to add RecyclerViews with changeset notifications so not all data in the RecyclerView has to be refreshed when there are updates, just like in Realm Android: realm-android-adapters
Not sure if this is a feature or a code example request, but if what I want to achieve is currently unsupported, this is a feature request.
RecyclerView adapters compatible with Realm Xamarin
I tried converting the adapters to Xamarin myself, but the API has some differences and I'm not sure how to fix my issue. I receive detached-row exceptions and another unspecified exception which I can't seem to track down in Visual Studio.
This is my own WIP version of the RealmRecyclerViewAdapter
`public abstract class RealmRecyclerViewAdapter
where VH : RecyclerView.ViewHolder
{
public const int HEADER_COUNT = 1;
public const int FOOTER_COUNT = 1;
private Boolean hasAutoUpdates;
private NotificationCallbackDelegate<T> listener;
//Our data source
protected IRealmCollection<T> adapterData;
private void CreateListener(IRealmCollection<T> sender, ChangeSet changeSet, System.Exception error)
{
// null Changes means the async query returns the first time.
if (changeSet == null)
{
NotifyDataSetChanged();
return;
}
// For deletions, the adapter has to be notified in reverse order.
int[] deletions = changeSet.DeletedIndices;
for (int i = deletions.Length - 1; i >= 0; i--)
{
/*OrderedCollectionChangeSet.Range range = deletions[i];
NotifyItemRangeRemoved(range.startIndex, range.length);*/
NotifyItemRemoved(i);
}
int[] insertions = changeSet.InsertedIndices;
for (int i = insertions.Length - 1; i >= 0; i--)
//for (OrderedCollectionChangeSet.Range range : insertions)
{
//NotifyItemRangeInserted(range.startIndex, range.length);
NotifyItemInserted(i);
}
int[] modifications = changeSet.ModifiedIndices;
for (int i = modifications.Length - 1; i >= 0; i--)
//for (OrderedCollectionChangeSet.Range range : modifications)
{
//NotifyItemRangeChanged(range.startIndex, range.length);
NotifyItemChanged(i);
}
}
public RealmRecyclerViewAdapter(IRealmCollection<T> data, Boolean autoUpdate)
{
this.adapterData = data;
this.hasAutoUpdates = autoUpdate;
//this.listener = hasAutoUpdates ? CreateListener : null;
this.listener = CreateListener;
}
public override void OnAttachedToRecyclerView(RecyclerView recyclerView)
{
base.OnAttachedToRecyclerView(recyclerView);
if (hasAutoUpdates && isDataValid())
{
//noinspection ConstantConditions
addListener(adapterData);
}
}
public override void OnDetachedFromRecyclerView(RecyclerView recyclerView)
{
base.OnDetachedFromRecyclerView(recyclerView);
if (hasAutoUpdates && isDataValid())
{
//noinspection ConstantConditions
removeListener(adapterData);
}
}
/**
* Returns the current ID for an item. Note that item IDs are not stable so you cannot rely on the item ID being the
* same after notifyDataSetChanged() or {@link #updateData(OrderedRealmCollection)} has been called.
*
* @param index position of item in the adapter.
* @return current item ID.
*/
public override long GetItemId(int position)
{
return position;
}
public override int ItemCount
{
get
{
//noinspection ConstantConditions
return isDataValid() ? adapterData.Count() : 0;
}
}
/**
* Returns the item associated with the specified position.
* Can return {@code null} if provided Realm instance by {@link OrderedRealmCollection} is closed.
*
* @param index index of the item.
* @return the item at the specified position, {@code null} if adapter data is not valid.
*/
public T getItem(int index)
{
//noinspection ConstantConditions
return isDataValid() ? adapterData.ElementAt(index) : null;
}
/**
* Returns data associated with this adapter.
*
* @return adapter data.
*/
public IRealmCollection<T> getData()
{
return adapterData;
}
/**
* Updates the data associated to the Adapter. Useful when the query has been changed.
* If the query does not change you might consider using the automaticUpdate feature.
*
* @param data the new {@link OrderedRealmCollection} to display.
*/
public void updateData(IRealmCollection<T> data)
{
if (hasAutoUpdates)
{
if (isDataValid())
{
//noinspection ConstantConditions
removeListener(adapterData);
}
if (data != null)
{
addListener(data);
}
}
this.adapterData = data;
NotifyDataSetChanged();
}
private void addListener(IRealmCollection<T> data)
{
if (data is IRealmCollection<T>)
{
IRealmCollection<T> results = (IRealmCollection<T>) data;
//noinspection unchecked
//results.addChangeListener(listener);
results.SubscribeForNotifications(listener);
}
/*else
if (data
instanceof IRealmCollection)
{
IRealmCollection<T> list = (IRealmCollection<T>) data;
//noinspection unchecked
list.addChangeListener(listener);
}*/
else
{
throw new IllegalArgumentException("RealmCollection not supported: " + data.GetType());
}
}
private void removeListener(IRealmCollection<T> data)
{
if (data is IRealmCollection<T>)
{
IRealmCollection<T> results = (IRealmCollection<T>) data;
//noinspection unchecked
//results.removeChangeListener(listener);
}
/*else
if (data
instanceof IRealmCollection)
{
IRealmCollection<T> list = (IRealmCollection<T>) data;
//noinspection unchecked
list.removeChangeListener(listener);
}*/
else
{
throw new IllegalArgumentException("RealmCollection not supported: " + data.GetType());
}
}
private Boolean isDataValid()
{
return adapterData != null /* && adapterData.isValid()*/;
}
}`
It would help hugely if you could send an entire sample project using this class to [email protected] so we can see exactly how you are triggering your errors.
@AndyDentFree Thanks for your response. I have sent an example project.
Hi, I am not particularly familiar with the RecyclerView API or adapters, but looking through the example you sent us, in the CreateListener method, instead of calling NotifyItemRemoved(i) you should be be using NotifyItemRemoved(deletions[i]) as the value stored in the array actually indicates the index of the removed item, the index in the array is irrelevant. Its the same for insertions and modifications. Fixing those, allowed me to run the example without exceptions. The UI didn't update after I pressed the Add button, but I guess that's an issue with the adapter API, not with Realm itself.
@nirinchev Thanks, what an oversight...
An additional mistake I made was that I was still using the regular List
I have cleaned up my example and put it on my repository, if anyone needs it: link.
I had to comment some lines that aren't available in Realm Xamarin (yet), for example IsManaged();
Realm is working as expected :) Thanks for your time! One more question: is there any update on stable IDs?
In the AddListener implementation, you could clean up the casts a little - I guess that's leftover from java.
We don't expose IsManaged because you can only obtain IRealmCollection that is managed. There's a corner case though, where you get an IList property, e.g. var collection = person.Dogs and then delete the parent object (person). In that case, the collection will become invalid and we could expose that. I logged it as a feature request: https://github.com/realm/realm-dotnet/issues/1324
In terms of stable ids - we're working on it, but as the feature is rather huge, there's no estimate we can give as of yet. Is there a particular use case you have for it?
You're right, I've updated it ;)
I see now. Thanks for explaining, I was wondering why it was missing.
Okay, great to hear. No, I don't have a use case for it yet, I'm just experimenting at this point. But I can imagine it being necessary sometimes, for example in a to-do list that supports reordering.
We have very comprehensive merge rules that produce expected results even without stable ids. Once we have implemented those, we can greatly simplify these rules, but end result will be the same (although with stable ids we will get to that result much faster) 馃 If you take a look at the Realm Tasks demo, you'll see that we do list reordering there (when you complete a task, we move it to the bottom), and this works as expected.
I'll be closing this issue then. If you have any more questions, we're here to help 馃憤
Oh right, I saw that link before but forgot about it... Thanks a lot for the quick help! 馃憤
Most helpful comment
@nirinchev Thanks, what an oversight...
An additional mistake I made was that I was still using the regular List in my ProductsListAdapter in some places instead of retrieving from the managed list in the base class... This was some left-over code from before I switched to this "advanced" RecyclerView adapter. I was too focused on the Realm adapter class and convinced that it was causing the issue.
I have cleaned up my example and put it on my repository, if anyone needs it: link.
I had to comment some lines that aren't available in Realm Xamarin (yet), for example IsManaged();
Realm is working as expected :) Thanks for your time! One more question: is there any update on stable IDs?