MvxSectionedRecyclerView 1.0.1.1

dotnet add package MvxSectionedRecyclerView --version 1.0.1.1                
NuGet\Install-Package MvxSectionedRecyclerView -Version 1.0.1.1                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="MvxSectionedRecyclerView" Version="1.0.1.1" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add MvxSectionedRecyclerView --version 1.0.1.1                
#r "nuget: MvxSectionedRecyclerView, 1.0.1.1"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install MvxSectionedRecyclerView as a Cake Addin
#addin nuget:?package=MvxSectionedRecyclerView&version=1.0.1.1

// Install MvxSectionedRecyclerView as a Cake Tool
#tool nuget:?package=MvxSectionedRecyclerView&version=1.0.1.1                

Android MvxSectionedRecyclerView

This is an unofficial package that contains an expandable AndroidX RecyclerView supported for MvvmCross. This view allows us to bind a collection of items (objects, ViewModels, etc) to the ItemsSource property. It works similarly to a RecyclerView. However, this comes with out-of-the-box functionality such as grouping items with collapsible/expandable headers. Additional functionality can be implemented such as dragging items up and down and swiping them by setting a boolean property to EnableDrag, EnableSwipeRight and/or EnableSwipeLeft attributes, via MvxSmartRecyclerView.

All original functionality of MvxRecyclerView is also available and it is highly encouraged that you read the documentation before proceeding.

All functionality of MvxSmartRecyclerView is also available and it is essential to read the documentation for a better understanding, before proceeding.

Getting Started

You will need to ensure that you have the MvxSectionedRecyclerView NuGet package installed in your .Droid project.

  1. We want to create an app where students are grouped based on their lesson. Firstly, in our .Core project, we will need to create our classes: Student.cs and Lesson.cs.
public class Student : MvxNotifyPropertyChanged
{
    private string firstName;
    private string lastName;
    private Lesson lesson;
    private int sequence;

    public Student(string firstName, string lastName, Lesson lesson)
    {
        FirstName = firstName;
        LastName = lastName;
        Lesson = lesson;
    }

    public string FirstName { get => firstName; set => SetProperty(ref firstName, value); }
    public string LastName { get => lastName; set => SetProperty(ref lastName, value); }
    public Lesson Lesson { get => lesson; set => SetProperty(ref lesson, value); }
    public int Sequence { get => sequence; set => SetProperty(ref sequence, value); }
}
public class Lesson : MvxNotifyPropertyChanged
{
    public static readonly Lesson Empty = new Lesson(Subject.None, DateTime.MinValue);

    private int sequence;

    public Lesson(Subject subject, DateTime dateTime)
    {
        Subject = subject;
        DateTime = dateTime;
    }

    public DateTime DateTime { get; }
    public int Sequence { get => sequence; set => SetProperty(ref sequence, value); }
    public Subject Subject { get; }
}

public enum Subject
{
    None = 0,
    English = 1,
    Math = 2,
}
  1. In our ViewModel we will initialise a list of Students for binding.
public MvxObservableCollection<Student> Students { get; set; }
  1. For the rest of the steps, everything will be done in our .Droid project. We will create a layout to display our Student.cs entity by creating StudentView.xml.

StudentView.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   android:layout_width="match_parent"
   android:layout_height="30dp"
   android:orientation="horizontal"
   android:gravity="center">
   <TextView
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      app:MvxBind="Text Format('{0} {1} - {2} on {3:d}', FirstName, LastName, Lesson.Subject, Lesson.DateTime);"/>
   <TextView
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_marginHorizontal="4sp"
      android:textColor="?android:attr/colorAccent"
      app:MvxBind="Text Sequence;"/>
</LinearLayout>
  1. We then create a layout for our section header. In this example: we will create an xml representing a Lesson called LessonHeaderView.xml. This view binds to properties in IMvxSection such as IMvxSection.Header, IMvxSection.Items and IMvxSection.IsCollapsed.

LessonHeaderView.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:background="@android:color/holo_blue_bright"
   android:orientation="horizontal"
   android:padding="8sp"
   android:gravity="center">
   <FrameLayout
      android:layout_width="wrap_content"
      android:layout_height="wrap_content">
      <ImageView
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:src="@android:drawable/arrow_down_float"
         app:MvxBind="Visible IsCollapsed;" />
      <ImageView
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:src="@android:drawable/arrow_up_float"
         app:MvxBind="Visible !IsCollapsed;" />
   </FrameLayout>
   <TextView
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      style="@style/Base.TextAppearance.AppCompat.Headline"
	  app:MvxBind="Text Format('{0} on {1:d}', Header.Subject, Header.DateTime);"/>
   <TextView
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_marginHorizontal="4sp"
      android:textColor="@android:color/holo_blue_light"
      app:MvxBind="Text Header.Sequence;" />
   <TextView
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_marginHorizontal="4sp"
      android:textColor="@android:color/holo_red_dark"
      app:MvxBind="Text Items.Count;" />
</LinearLayout>
  1. For MvxSectionedRecyclerView to know how to group and display our items, we will need to create an adapter class that inherits MvxSectionedRecyclerAdapter<THeader, TItem> as follows:
namespace AppointmentPlanner.DroidX.Components
{
    public class AppointmentSectionedRecyclerAdapter : MvxSectionedRecyclerAdapter<Lesson, Student>
    {
        public AppointmentSectionedRecyclerAdapter()
        {
        }
        
        public AppointmentSectionedRecyclerAdapter(IMvxAndroidBindingContext bindingContext)
            : base(bindingContext)
        {
        }
        
        protected override Lesson GetItemHeader(Student item) => item.Lesson;
        
        protected override void SetItemHeader(Lesson header, Student item) => item.Lesson = header;
    }
}

Important: To set this adapter via xml you will need to provide it in the MvxAdapter attribute on the MvxSectionedRecyclerView. It must be of the format: Fully.Qualified.ClassName, Assembly.Name. Hence, for the example above: let us say the assembly will be AppointmentPlanner.DroidX and as you see the namespace is AppointmentPlanner.DroidX.Components then the string will be: AppointmentPlanner.DroidX.Components.AppointmentSectionedRecyclerAdapter, AppointmentPlanner.DroidX.

  1. We then create a custom Item Template Selector by inheriting MvxSectionedTemplateSelector<THeader, TItem> to handle displaying layouts for the corresponding section header(s) and item(s).
namespace AppointmentPlanner.DroidX.Components
{
    public class AppointmentSectionedTemplateSelector : MvxSectionedTemplateSelector<Lesson, Student>
    {
        protected override int SelectItemViewType(Student item)
        {
            return Resource.Layout.StudentView;
        }

        protected override int SelectSectionViewType(MvxSection<Lesson, Student> section)
        {
            return Resource.Layout.LessonHeaderView;
        }
    }
}
  1. Finally, adding MvxSectionedRecyclerView to one of your View.xml is very simple.
<MvvmCross.SectionedRecyclerView.MvxSectionedRecyclerView
   android:id="@+id/appointment_sectioned_recyclerview"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   app:MvxAdapter="AppointmentPlanner.DroidX.Components.AppointmentSectionedRecyclerAdapter, AppointmentPlanner.DroidX"
   app:MvxTemplateSelector="AppointmentPlanner.DroidX.Components.AppointmentSectionedTemplateSelector, AppointmentPlanner.DroidX"
   app:MvxBind="ItemsSource Students;"/>

Important: MvxExpandableRecyclerView will require you to bind a MvxObservableCollection<TItem> to ItemsSource. It will need to have your custom MvxSectionedRecyclerAdapter<THeader, TItem> and MvxSectionedTemplateSelector<THeader, TItem> for it to display your headers and items correctly by adding the app:MvxAdapter and app:MvxTemplateSelector attributes. The view will also require THeader to inherit IComparable<THeader> or else the view will not show the items and extra setup will be needed. More info is provided in the following.

Section Headers - IComparable<T> / IComparer<T>.

InvalidOperationException: Failed to compare two elements in the array.

ArgumentException: At least one object must implement IComparable.

If your list doesn't display or you come across any of the above errors when loading MvxSectionedRecyclerView, it means that the view does not know how to order the sections based on the header class. To fix this, you can either:

  • Modify your header class to implement IComparable<T> where T is your header class. In our example, our Lessons will be compared based on their DateTime.
public class Lesson : MvxNotifyPropertyChanged, IComparable<Lesson>
{
    // Constructors, properties, etc...

    public int CompareTo(Lesson other)
    {
        if (other == null)
        {
            return 1;
        }

        return DateTime.CompareTo(other.DateTime);
    }
}
  • However, if the header class cannot be modified, you will need to create a class that inherits IComparer<T> where T is your header class. For example:
public class LessonComparer : IComparer<Lesson>
{
    public int Compare(Lesson x, Lesson y)
    {
        if (x == null && y == null)
        {
            return 0;
        }
        else if (x == null)
        {
            return -1;
        }
        else if (y == null)
        {
            return 1;
        }
        else
        {
            return x.DateTime.CompareTo(y.DateTime);
        }
    }
}

Then you override the GetSectionComparer method in your custom adapter that inherits MvxSectionedRecyclerAdapter<THeader, TItem> and return your custom IComparer<T>. For example:

public class AppointmentSectionedRecyclerAdapter : MvxSectionedRecyclerAdapter<Lesson, Student>
{
    /// Constructors, properties, etc...

    protected override IComparer<Lesson> GetSectionComparer() => new LessonComparer();
}

Section Headers - IEquatable<T> / IEqualityComparer<T>

Similar to the above, you can modify your header class to implement IEquatable<T> where T is your header class to determine if 2 headers are the same. For example:

public class Lesson : MvxNotifyPropertyChanged, IEquatable<Lesson>
{
    // Constructors, properties, etc...

    public bool Equals(Lesson other)
    {
        if (other == null)
        {
            return false;
        }

        return Subject == other.Subject && DateTime.Equals(other.DateTime);
    }

    public override bool Equals(object obj)
    {
        if (obj is Lesson lesson)
        {
            return Equals(lesson);
        }
        return false;
    }

    public override int GetHashCode()
    {
        return HashCode.Combine(Subject, DateTime);
    }
}

However, if you're not able to override the equality of your header class, you can override the GetSectionEqualityComparer method (in your custom adapter that inherits MvxSectionedRecyclerAdapter<THeader, TItem>) and return an IEqualityComparer<T>. For example:

public class LessonEqualityComparer : IEqualityComparer<Lesson>
{
    public bool Equals(Lesson x, Lesson y)
    {
        if (ReferenceEquals(x, y))
        {
            return true;
        }

        if (x == null || y == null)
        {
            return false;
        }

        return x.Subject.Equals(y.Subject) && x.DateTime.Equals(y.DateTime);
    }

    public int GetHashCode(Lesson obj) => HashCode.Combine(obj?.Subject, obj?.DateTime);
}
public class AppointmentRecyclerAdapter : MvxSectionedRecyclerAdapter<Lesson, Student>
{
    /// Constructors, properties, etc...

    protected override IEqualityComparer<Lesson> GetSectionEqualityComparer() => new LessonEqualityComparer();
}

Add Permanent Headers

If you would like to always show certain headers in your list, you can modify your custom adapter and override the AddInitialHeaders method to return a list of headers. For example:

public class AppointmentRecyclerAdapter : MvxSectionedRecyclerAdapter<Lesson, Student>
{
    /// Constructors, properties, etc...

    protected override IEnumerable<Lesson> AddInitialHeaders()
    {
        return new List<Lesson>(1)
        {
            Lesson.Empty,
        };
    }
}

Dragging Items

To enable the dragging feature, we need to modify our xml and set the EnableDrag attribute to true.

<MvvmCross.SectionedRecyclerView.MvxSectionedRecyclerView
    app:EnableItemDrag="true"/>

Prevent Dragging Items

If you want to prevent certain items in the list from being dragged, you can create a custom adapter that inherits MvxSectionedRecyclerAdapter<THeader, TItem> and override the ShouldDragItem(TItem item) method. This allows you to specify conditions on which items are allowed to be dragged.

public class AppointmentSectionedRecyclerAdapter : MvxSectionedRecyclerAdapter<Lesson, Student>
{
    // Constructors...

    public override bool ShouldDragItem(Student item)
    {
        if (item is something...)
        {
            // Allow dragging
            return true;
        }
        else
        {
            // Prevent dragging
            return false;
        }
    }
}

Swiping Items

To enable the swiping feature, we need to modify our xml and set EnableSwipeRight and/or EnableSwipeLeft attributes to true.

<MvvmCross.SectionedRecyclerView.MvxSectionedRecyclerView
    app:EnableSwipeRight="true"
    app:EnableSwipeLeft="true"
    app:MvxBind="ItemSwipeRight SwipeRightCommand;
            ItemSwipeLeft SwipeLeftCommand;"/>

Swipe actions are bindable and can have 2 different actions depending on the direction of the swipe. ItemSwipeLeft and ItemSwipeRight are bindable and are done in the same way as MvxRecyclerView's ItemClickCommand and ItemLongClickCommand.

Prevent Swiping Items

If you want to prevent certain items in the list from being swiped left or right, you can create a custom adapter that inherits MvxSectionedRecyclerAdapter<THeader, TItem> and override either the ShouldSwipeLeft(TItem item) and/or ShouldSwipeRight(TItem item) methods. This allows you to specify conditions on which items are allowed to be swiped left or right. For example:

public class AppointmentSectionedRecyclerAdapter : MvxSectionedRecyclerAdapter<Lesson, Student>
{
    // Constructors...

    public override bool ShouldSwipeLeft(Student item)
    {
        if (item is something...)
        {
            // Allow left swipe
            return true;
        }
        else
        {
            // Prevent left swipe
            return false;
        }
    }

    public override bool ShouldSwipeRight(Student item)
    {
        if (item is something...)
        {
            // Allow right swipe
            return true;
        }
        else
        {
            // Prevent right swipe
            return false;
        }
    }
}

Swipe Backgrounds

We can show different backgrounds for an item depending on the swipe direction. In this example, we create 2 new layout files:

UnplanItemBackgroundView.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   android:id="@id/smart_recycler_item_left_background_view"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:orientation="horizontal"
   android:gravity="start"
   android:background="@android:color/holo_blue_light">
    <ImageView
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:layout_centerVertical="true"
        android:layout_marginRight="10sp"
        android:background="@drawable/abc_ic_ab_back_material"
        android:contentDescription="Unplan Item" />
</LinearLayout>

RemoveItemBackgroundView.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   android:id="@id/smart_recycler_item_right_background_view"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:orientation="horizontal"
   android:gravity="end"
   android:background="@android:color/holo_red_light">
    <ImageView
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:layout_centerVertical="true"
        android:layout_marginLeft="10sp"
        android:background="@drawable/abc_ic_clear_material"
        android:contentDescription="Remove Item" />
</LinearLayout>

Important: the important thing to notice in these files is that each layout has an android:id attribute. This is important for the control because it identifies which layout to show when swiping left or right, or not swiping at all. The android:ids needed for the control are:

  • android:id="@id/smart_recycler_item_left_background_view" (show layout when swiping right)
  • android:id="@id/smart_recycler_item_right_background_view" (show layout when swiping left)
  • android:id="@id/smart_recycler_item_foreground_view" (show layout for item when user is not swiping).

We then modify our StudentView.xml to include these layouts and wrap everything in a FrameLayout, making sure the background layouts are added first. We also need to add android:id="@id/smart_recycler_item_foreground_view" to the nested LinearLayout holding the default view to show when the user isn't swiping.

Note: by default, the LinearLayout's background is transparent, resulting in the background views to show. The LinearLayout's background attribute is set to @android:color/white to ensure the background views are hidden.

StudentView.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <include
        layout="@layout/unplanitembackgroundview"/>
    <include
        layout="@layout/removeitembackgroundview"/>
    <LinearLayout
        android:id="@id/smart_recycler_item_foreground_view"
        android:layout_width="match_parent"
        android:layout_height="30dp"
        android:orientation="horizontal"
        android:gravity="center"
        android:background="@android:color/white">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:MvxBind="Text Format('{0} {1} - {2} on {3:d}', FirstName, LastName, Lesson.Subject, Lesson.DateTime);"/>
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginHorizontal="4sp"
            android:textColor="?android:attr/colorAccent"
            app:MvxBind="Text Sequence;"/>
    </LinearLayout>
</FrameLayout>

Dynamic Swipe Backgrounds

If you want to show different swipe backgrounds depending on certain conditions relating to the item, you can create a custom adapter that inherits MvxSectionedRecyclerAdapter<THeader, TItem> and override either the GetLeftBackgroundResourceId(TItem item) and/or GetRightBackgroundResourceId(TItem item) methods. This allows you to specify conditions on what backgrounds to show when swiping left or right. You will also need to override the GetBackgroundResourceIds() method to return a list of Resource Ids that will be used for the background views to help prevent visual bugs.

For example, continuing on from the Swipe Backgrounds section above, we will create 2 more layouts:

PlanItemBackgroundView.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/plan_item_background_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    android:gravity="start"
    android:background="@android:color/holo_green_light">
    <ImageView
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:layout_centerVertical="true"
        android:layout_marginRight="10sp"
        android:background="@drawable/abc_ic_ab_back_material"
        android:contentDescription="Plan Item" />
</LinearLayout>

ResetItemBackgroundView.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/reset_item_background_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    android:gravity="end"
    android:background="@android:color/holo_orange_light">
    <ImageView
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:layout_centerVertical="true"
        android:layout_marginLeft="10sp"
        android:background="@drawable/abc_ic_clear_material"
        android:contentDescription="Reset Item" />
</LinearLayout>

Important: make sure each layout has an android:id attribute. This is important for the control because it will help identify which layout to show when swiping left or right. In this example, they are:

  • android:id="@+id/plan_item_background_view"
  • android:id="@+id/reset_item_background_view"

For example, in our custom adapter: MvxSectionedRecyclerAdapter<Lesson, Student> we make sure to override the required methods: GetLeftBackgroundResourceId(Student item), GetRightBackgroundResourceId(Student item) and GetBackgroundResourceIds():

public class AppointmentSectionedRecyclerAdapter : MvxSectionedRecyclerAdapter<Lesson, Student>
{
    // Constructors...

    public override IEnumerable<int> GetBackgroundResourceIds() => new List<int>(2)
    {
        Resource.Id.reset_item_background_view,
        Resource.Id.plan_item_background_view,
    };

    public override int GetLeftBackgroundResourceId(Student item)
    {
        if (item is something...)
        {
            return Resource.Id.plan_item_background_view;
        }

        return base.GetLeftBackgroundResourceId(item);
    }

    public override int GetRightBackgroundResourceId(Student item)
    {
        if (item is something...)
        {
            return Resource.Id.reset_item_background_view;
        }

        return base.GetRightBackgroundResourceId(item);
    }
}

Note: the base implementation for:

  • GetLeftBackgroundResourceId(object item) returns Resource.Id.smart_recycler_item_left_background_view
  • GetRightBackgroundResourceId(object item) returns Resource.Id.smart_recycler_item_right_background_view

We then modify our StudentView.xml to include these new layouts and wrap everything in a FrameLayout, making sure the background layouts are added first.

StudentView.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <include
        layout="@layout/unplanitembackgroundview"/>
    <include
        layout="@layout/removeitembackgroundview"/>
		
    <include
        layout="@layout/planitembackgroundview"/>
    <include
        layout="@layout/resetitembackgroundview"/>
		
    <LinearLayout
        android:id="@id/smart_recycler_item_foreground_view"
        android:layout_width="match_parent"
        android:layout_height="30dp"
        android:orientation="horizontal"
        android:gravity="center"
        android:background="@android:color/white">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:MvxBind="Text Format('{0} {1} - {2} on {3:d}', FirstName, LastName, Lesson.Subject, Lesson.DateTime);"/>
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginHorizontal="4sp"
            android:textColor="?android:attr/colorAccent"
            app:MvxBind="Text Sequence;"/>
    </LinearLayout>
</FrameLayout>

Note: for the actual logic that gets executed when swiping left or right, the ItemSwipeLeft and ItemSwipeRight will need to have logic that accounts for this.

Performance Optimisation

If you want to fine-tune when the RecyclerView should update its views, you can modify your custom adapter that inherits MvxSectionedRecyclerAdapter<THeader, TItem> and override the CreateDiffUtilHelper(IList oldList, IList newList) method. This method allows you to return a DiffUtil.Callback object which helps the RecyclerView determine what views to update when performing an operation (add, remove, update). By default, this method returns a MvxDefaultSmartDiffUtilHelper which handles any object. However, you can override the method and return your own custom class that inherits MvxSmartDiffUtilHelper<T> where T is your item (not header) class to fine-tune when the RecyclerView should update its views.

Using the list of students as an example:

public class AppoinmentDiffUtilHelper : MvxSmartDiffUtilHelper<Student>
{
    public AppoinmentDiffUtilHelper(IList oldList, IList newList) 
        : base(oldList, newList)
    {
    }

    protected override bool AreContentsTheSame(Student oldItem, Student newItem)
    {
        return oldItem.FirstName == newItem.FirstName 
            && oldItem.LastName == newItem.LastName 
            && oldItem.Lesson.Subject == newItem.Lesson.Subject 
            && oldItem.Lesson.DateTime == newItem.Lesson.DateTime;
    }

    protected override bool AreItemsTheSame(Student oldItem, Student newItem)
    {
        return oldItem.FirstName == oldItem.FirstName
            && oldItem.LastName == newItem.LastName;
    }
}

Note: the AreItemsTheSame(Student oldItem, Student newItem) method checks if the two items represent the same item. If true: the AreContentsTheSame(Student oldItem, Student newItem) method is called and checks if the item's content has been changed. If true: the RecyclerView updates the corresponding view, otherwise it doesn't.

In our custom adapter we make sure to override the CreateDiffUtilHelper(IList oldList, IList newList) method and return a new instance of our custom DiffUtil.Callback class: AppoinmentDiffUtilHelper.

public class AppointmentSectionedRecyclerAdapter : MvxSectionedRecyclerAdapter<Lesson, Student>
{
    // Constructors...

    protected override DiffUtil.Callback CreateDiffUtilHelper(IList oldList, IList newList) => new AppoinmentDiffUtilHelper(oldList, newList);
}
Product Compatible and additional computed target framework versions.
MonoAndroid monoandroid10.0 is compatible. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.0.1.1 130 2/22/2024
1.0.1 118 2/10/2024
1.0.0 99 2/10/2024