Quantcast
Channel: Xamarin.Forms — Xamarin Community Forums
Viewing all articles
Browse latest Browse all 79144

Forms ListView Recycling Cells with async background loading causing weird binding issues / glitches

$
0
0

I’ll preface this by saying I’m sure I’m NOT doing this right, but I haven’t been able to find a better way or example yet.

TLDR; ListView cells have view models w/ only part of the data. the rest of the data is loaded async and some times the binding doesn’t apply and the fields retrieved aren’t displayed.

Hey all, so I’ve been mulling over the best way to do this and I’m unable to figure out a way that works 100%.

I’ve got a Xamarin.Forms ListView and I’m using this behavior for Infinite Scrolling (https://github.com/mattleibow/InfiniteScrolling) however I’ve experienced the same results without so I don’t believe that it relates to the problems.

  • ListView ItemsSource is bound to a list of Post VeiwModels.
  • ListView is using CachingStrategy RetainElement (but tried recycle element as well)
  • ListView is using a custom data template. That includes an Image (FFImageLoading) and a pair of standard Labels.
  • Ideally I’d like the Cells to Size to content, but that really caused nothing but issues so I’m using a Fixed RowHeight

The Data Template called, PostCellView overrides the OnAppearing and OnDisappearing.

When the Cell comes into View OnAppearing will start a task… the task will delay for 300ms and then call a method on the ViewModel to let it know to fetch the data (I know bad, MVVM here)
If the cell leaves the View OnDisappearing will cancel the pending task if the fetch hasn’t started yet.

The reason for this is if someone is “fast” scrolling through the list I don’t want to make wasted http calls.
** I’ve since removed this delay logic because I was trying to over optimize a solution that still wasn’t working correctly ** So when a Cell comes into View, it will call a method on the ViewModel.

The BindingContext of the ViewCell is the ViewModel… and the ViewModel has a series of properties that call SetProperty (on BindableBase, using Prism) to let the bound properties on the view know to refresh.

The fetch method on the view model, makes an async / await call to an api layer, gets the model and binds into the properties on the VM.

What I see is the image loads correctly, some of the text appears, but doesn’t size property and some fields don’t even appear at all. I’ve I scroll the cell out of view and then back in, all of the fields appear and are sized properly.

So the currently flow is:

1.) Cell Comes into view
2.) Cell tells ViewModel it’s in view
3.) ViewModel checks it’s state and see if it needs to refresh… calls api async.
4.) ViewModel updates it’s properties and fires changed events.
5.) Some fields don’t display or size properly despite 100% sure the data is there, but some fields do.

Some of the other things I’ve tried is calling the ViewCell.ForceSizeUpdate on the UI thread… but the problem is this causes the ViewCell to fire it’s OnAppearing again and so I’m forced to manually guard this to prevent reentrancy… Still doing this cause graphically anomalies like cells dancing up and down as they’re sizing and other strange artifacts.

Anybody have any other ideas? I can’t be the only one loading cells into the visual tree and asynchronously binding in their data.

Thanks,
Rich

EDIT: Below is what I'm using that works the "best" but still causes issues with binding and some fields not appearing unless the cell goes in and out of view again.

public partial class PostViewCell : ViewCell
    {
        private bool _isLoaded;

        public PostViewCell()
        {
            InitializeComponent();
        }

        protected override void OnBindingContextChanged()
        {
            base.OnBindingContextChanged();
            _isLoaded = false;
        }

        protected async override void OnAppearing()
        {
            base.OnAppearing();
            if (!(this.BindingContext is PostViewModel vm) || _isLoaded)
            {
                return;
            }

            await vm.FetchData();
            Device.BeginInvokeOnMainThread(this.ForceUpdateSize);
            _isLoaded = true;
        }

        protected override void OnDisappearing()
        {
            base.OnDisappearing();
            if (!(this.BindingContext is PostViewModel vm))
            {
                return;
            }

            //Any cleanup?
        }

    }


Viewing all articles
Browse latest Browse all 79144

Latest Images

Trending Articles



Latest Images

<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>