Axemasta
Axemasta

Reputation: 843

Binding Generic Object (List<T>) to Custom Xamarin Control

I am developing a mobile app that has an API that I can call to get data, the majority of the endpoints are paged and return the following object:

public class PagedResult<T>
{
    public List<T> Data { get; private set; }

    public PagingInfo Paging { get; private set; }

    public PagedResult(IEnumerable<T> items, int pageNo, int pageSize, long totalRecordCount)
    {
        Data = new List<T>(items);
        Paging = new PagingInfo
        {
            PageNo = pageNo,
            PageSize = pageSize,
            TotalRecordCount = totalRecordCount,
            PageCount = totalRecordCount > 0
                ? (int)Math.Ceiling(totalRecordCount / (double)pageSize)
                : 0
        };
    }
}

I have built a control that can select the paged results, the control presents a list of the current page items & allows the user to move through the other pages (pictured below).

The pagination control I built

I have built a proof of concept for this control however I have run into a small but really annoying stumbling block.

My PagedResult<T> is generic, but the PagedItemSource for my list view is not. I built the control to have the bindable properties: DataTemplate, PagedItemSource & LoadPageCommand. The control itself determines which page gets loaded & will execute that command itself (I may expose this value later but it's not needed for now).

When building the control I had to initialise the PagedResult<T>, however instead of worrying about generics I just made it into PagedResult<object> and did the same in my view model. Now i'm at the point where I want this list to be generic, part of the reason I built this control was to be generic and abstract as much responsibility for the paging to the control. However due to the fact that my API model uses a generic, I've gotten stuck!

I cannot initialise my control with a Generic T:

public partial class PaginationControl<T>: ContentView
{
    ...
}

Will mean that the C# compiler starts shouting at me, the whole class goes beserk in fact. My binded properties display the warning: Static field in generic type. This is why I just used <object> as the type of my list, unfortunately the binding won't work in the following scenario:

View Model

public PagedResult<MyObject> PagedItemSource { get; set; }

Control

public static readonly BindableProperty PagedItemSourceProperty = BindableProperty.Create(
    nameof(PagedItemSource),
    typeof(PagedResult<object>),
    typeof(PaginationControl),
    defaultValue: null,
    propertyChanged: (bindable, oldVal, newVal) => ((PaginationControl)bindable).OnPagedItemSourceChanged((PagedResult<object>)newVal)
);

It will work if my view model's data is also of type <object> however thats no use because now I need to track the type of my data in my view model...

I've come up with a hacky work around so that I can crack on with the app, but it's stupid and feels dirty and I hate it...

My hack is to have a second property in my view model which just takes the current PagedResult<T> and converts it to PagedResult<object>, and binding this property to the control. (Sorry you had to read this)

I went and had a trawl for the Xamarin.Forms source code to see how they did this for ListView since it's essentially the same issue & their ListView accepts any generic object. Unfortunately I found the src incredibly confusing and learned nothing that would help solve my issue (I saw in ItemView<Cell> that ItemSource is an IEnumerable but I couldnt figure out how they were setting the T of this...

Any help would be much appreciated, I'm probably looking at this in the wrong way!

Upvotes: 0

Views: 2317

Answers (1)

user10608418
user10608418

Reputation:

As Ed Plunkett already mentioned you're thinking about the wrong way.

ItemsSource is of type IEnumerable there's no generic argument there since the ListView does not care about the specific type of items. This should be the case for your control as well, otherwise you wouldn't make it generic.

So why not just follow the same design they implement for most collections, List<T> for example also implements IList<T> and IList as interfaces. the last interface can be used when you're not interested in the generic argument but just want access to the list. Following this setup we can make something like this:

public interface IPagedResult
{
    IEnumerable Data { get; }

    PagingInfo Paging { get; }

    //any other properties you might need
}

Then we can declare PagedResult like this:

public class PagedResult<T> : IPagedResult
{
    public List<T> List { get; private set; }
    public IEnumerable Data => List;

    public PagingInfo Paging { get; private set; }

    public PagedResult(IEnumerable<T> items, int pageNo, int pageSize, long totalRecordCount)
    {
        List = new List<T>(items);
        Paging = new PagingInfo
        {
            PageNo = pageNo,
            PageSize = pageSize,
            TotalRecordCount = totalRecordCount,
            PageCount = totalRecordCount > 0
                ? (int)Math.Ceiling(totalRecordCount / (double)pageSize)
                : 0
        };
    }
}

And now you should just be able to declare your BindableProperty like this:

public static readonly BindableProperty PagedItemSourceProperty = BindableProperty.Create(
    nameof(PagedItemSource),
    typeof(IPagedResult),
    typeof(PaginationControl),
    defaultValue: null,
    propertyChanged: (bindable, oldVal, newVal) => ((PaginationControl)bindable).OnPagedItemSourceChanged((IPagedResult)newVal)
);

And from this bindable property you can access the data list but also the paging information.

Hope this helps.

Upvotes: 3

Related Questions