Reputation: 1059
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:
replacing myListView.BeginRefresh()
with myListView.IsRefreshing = true
and myListView.EndRefresh()
with myListView.IsRefreshing = false
Using Device.BeginInvokeOnMainThread(() => {//update list and endRefresh})
.
Using async void refreshData()
instead of async Task refreshData()
.
Upvotes: 9
Views: 5288
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
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
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