MvxSectionedRecyclerView 1.0.0
See the version list below for details.
dotnet add package MvxSectionedRecyclerView --version 1.0.0
NuGet\Install-Package MvxSectionedRecyclerView -Version 1.0.0
<PackageReference Include="MvxSectionedRecyclerView" Version="1.0.0" />
paket add MvxSectionedRecyclerView --version 1.0.0
#r "nuget: MvxSectionedRecyclerView, 1.0.0"
// Install MvxSectionedRecyclerView as a Cake Addin #addin nuget:?package=MvxSectionedRecyclerView&version=1.0.0 // Install MvxSectionedRecyclerView as a Cake Tool #tool nuget:?package=MvxSectionedRecyclerView&version=1.0.0
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.
- 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
andLesson.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,
}
- In our ViewModel we will initialise a list of
Student
s for binding.
public MvxObservableCollection<Student> Students { get; set; }
- For the rest of the steps, everything will be done in our
.Droid
project. We will create a layout to display ourStudent.cs
entity by creatingStudentView.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>
- We then create a layout for our section header. In this example: we will create an
xml
representing a Lesson calledLessonHeaderView.xml
. This view binds to properties inIMvxSection
such asIMvxSection.Header
,IMvxSection.Items
andIMvxSection.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>
- For
MvxSectionedRecyclerView
to know how to group and display our items, we will need to create an adapter class that inheritsMvxSectionedRecyclerAdapter<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
.
- 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;
}
}
}
- Finally, adding
MvxSectionedRecyclerView
to one of yourView.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>
whereT
is your header class. In our example, ourLesson
s will be compared based on theirDateTime
.
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>
whereT
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:id
s 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)
returnsResource.Id.smart_recycler_item_left_background_view
GetRightBackgroundResourceId(object item)
returnsResource.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 | Versions Compatible and additional computed target framework versions. |
---|---|
MonoAndroid | monoandroid10.0 is compatible. |
-
MonoAndroid 10.0
- MvvmCross.DroidX.RecyclerView (>= 7.0.1)
- MvxSmartRecyclerView (>= 1.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.