DerFlickschter
DerFlickschter

Reputation: 1059

Xamarin Forms ListView Programatic Refresh Not Stopping on Android When Page Loaded

I have Portable project in Visual Studio 2015 with a ListView that gets populated with some data via an API call through the following function refreshData:

async Task refreshData()
{
    myListView.BeginRefresh();
    var apiCallResult = await App.Api.myApiGetCall();            
    myListView.ItemsSource = apiCallResult;
    myListView.EndRefresh();    
}

refreshData() is called in

protected override void OnAppearing()
{
    base.OnAppearing();
    refreshData();
}

Everything is working fine except on Android where the refresh indicator is not stopping or disappearing on EndRefresh() when the page is initially loaded. The page is in a TabbedPage so I can go to a different tab and then return to this page and the refresh indicator properly starts and stops with completion of my API call.

Why is refresh is not stopping when the page initially loads on Android? Any help would be appreciated.

Note: This works perfectly fine when I run on iOS.

So far I've tried:

  1. replacing myListView.BeginRefresh() with myListView.IsRefreshing = true and myListView.EndRefresh() with myListView.IsRefreshing = false

  2. Using Device.BeginInvokeOnMainThread(() => {//update list and endRefresh}).

  3. Using async void refreshData() instead of async Task refreshData().

Upvotes: 9

Views: 5288

Answers (3)

Ingenator
Ingenator

Reputation: 222

You need to follow MVVM Pattern

On your ViewModel you need to:

  • Implement INotifyPropertyChanged

  • Define properties like:

     private bool _IsRefreshing;
     public bool IsRefreshing
     {
         get { return _IsRefreshing; }
         set { SetProperty(ref _IsRefreshing, value; } 
         /*So every time the property changes the view is notified*/
     }
    
  • Define the method that fetch your data, in your case refreshData()

  • Toggle the IsRefreshing true/false when needed

On your Page you need to:

  • Bind the listview itemSource to a VM property with the SetPropertyValue

  • Bind ListView.IsRefreshing to ViewModel's IsRefreshing:

    MyListView.SetBinding<MyViewModel>(ListView.IsRefreshing, vm => vm.IsRefreshing);

Here is a great article talking about INotifyPropertyChanged

Upvotes: 2

Mikalai Daronin
Mikalai Daronin

Reputation: 8716

Personally I can get this problem when I start ListView refreshing in the Page Contructor and stop it after the data is loaded. Sometimes (quite often) Xamarin.Forms ListView doesn't cancel refreshing animation.

I believe you faced with a quite common issue with Android SwipeRefreshLayout: it may not stop refreshing animation after setRefreshing(false) called. Native Android developers use the following approach:

swipeRefreshLayout.post(new Runnable() {
    @Override
        public void run() {
            mSwipeRefreshLayout.setRefreshing(refreshing);
    }
});

Interestingly, Xamarin.Forms uses this approach when it sets initial refreshing status (code); however, it is not enough. You need a custom renderer:

public class ExtendedListViewRenderer : ListViewRenderer
{
    /// <summary>
    /// The refresh layout that wraps the native ListView.
    /// </summary>
    private SwipeRefreshLayout _refreshLayout;

    public ExtendedListViewRenderer(Android.Content.Context context) : base(context)
    {
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            _refreshLayout = null;
        }
        base.Dispose(disposing);
    }

    protected override void OnElementChanged(ElementChangedEventArgs<ListView> e)
    {
        base.OnElementChanged(e);
        _refreshLayout = (SwipeRefreshLayout)Control.Parent;
    }

    protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == ListView.IsRefreshingProperty.PropertyName)
        {
            // Do not call base method: we are handling it manually
            UpdateIsRefreshing();
            return;
        }
        base.OnElementPropertyChanged(sender, e);
    }

    /// <summary>
    /// Updates SwipeRefreshLayout animation status depending on the IsRefreshing Element 
    /// property.
    /// </summary>
    protected void UpdateIsRefreshing()
    {
        // I'm afraid this method can be called after the ListViewRenderer is disposed
        // So let's create a new reference to the SwipeRefreshLayout instance
        SwipeRefreshLayout refreshLayoutInstance = _refreshLayout;

        if (refreshLayoutInstance == null)
        {
            return;
        }

        bool isRefreshing = Element.IsRefreshing;
        refreshLayoutInstance.Post(() =>
        {
            refreshLayoutInstance.Refreshing = isRefreshing;
        });
    }
}

Upvotes: 4

Diego Rafael Souza
Diego Rafael Souza

Reputation: 5314

Try this:

async void refreshData()
{
    Device.BeginInvokeOnMainThread(() => {
        myListView.BeginRefresh();
        var apiCallResult = await App.Api.myApiGetCall();
        myListView.ItemsSource = apiCallResult;
        myListView.EndRefresh();
    });
}

Apparently, there is no need for "Task" anymore.

If the error occurs only in Android, it's possible that it's just the way Android handles threads. It does not allow threads to change visual elements directly. Sometimes when we try to do that it throws a silent exception and the code action have no practical effect.

Upvotes: 2

Related Questions