jamesmontemagno / Xamarin.Forms-PullToRefreshLayout

Pull To Refresh a ScrollView or ListView in Xamarin.Forms
218 stars 49 forks source link

PullToRefreshLayout - Refresher does not hide after set IsRefreshing to false #49

Closed viniciusmaia closed 4 years ago

viniciusmaia commented 6 years ago

I'm using PullToRefreshLayout with Prism MVVM, I have a bool property in ViewModel to bind for IsRefreshing called IsBusy and when I set IsBusy to false, the refresher still appear on the page.

JunkyXL86 commented 6 years ago

I faced the same issue. I think it's because the IsRefreshing property does not recognize an immediate reset of the current value. It seems that the refresher spins forever because it never got notified about the false value. So I inserted a await Task.Delay(50) so that the UI has enough time to reflect the property change. ->

IsRefreshing = true;
await Task.Delay(50);
IsRefreshing = false;
jamesmontemagno commented 6 years ago

Would need a sample app to see what your issue is.

jochembroekhoff commented 6 years ago

In my case, it was resolved by using Device.BeginInvokeOnMainThread: Xamarin.Forms.Device.BeginInvokeOnMainThread(() => IsRefreshing = false);

meauris commented 6 years ago

Same issue here, I am using PullToRefreshLayout in the same situation as @viniciusmaia describes. Both the 'solutions' of @JunkyXL86 and @jochembroekhoff provided me a workaround.

KristofferBerge commented 6 years ago

There is definitely something up with the IsRefreshing property. In my case I am only able to update it if I directly reference the control. On Android

Xaml

            <refresh:PullToRefreshLayout x:Name="PullToRefreshLayout" IsPullToRefreshEnabled="True"
                                         RefreshCommand="{Binding RefreshCommand}"
                                         IsRefreshing="{Binding IsRefreshing}">
                <wwr:FormsWebView x:Name="WebViewElement" OnContentLoaded="OnContentLoaded"/>
            </refresh:PullToRefreshLayout>

Codebehind

        private ICommand refreshCommand;
        public ICommand RefreshCommand
        {
            get { return refreshCommand; }
            set
            {
                if (value == refreshCommand)
                    return;

                refreshCommand = value;
                OnPropertyChanged(nameof(RefreshCommand));
            }
        }

        private bool isRefreshing;
        public bool IsRefreshing
        {
            get { return isRefreshing; }
            set
            {
                //note that it's raising the property change event even when the value set is the same
                isRefreshing = value;
                OnPropertyChanged(nameof(IsRefreshing));
            }
        }
        public MyComponent()
        {
            InitializeComponent();
            RefreshCommand = new Command(RefreshPage, CanRefresh);
        }

        async void RefreshPage()
        {
            WebViewElement.Refresh();

            // Only way to remove the loading
            //PullToRefreshLayout.IsRefreshing = false;

            // Still nothing
            await Task.Delay(50);
            Device.BeginInvokeOnMainThread(() =>
            {
                IsRefreshing = false;
            });
        }

        // Even when webview has finished loading, updating the boolean has no effect
        private void OnContentLoaded(object sender, EventArgs e)
        {
            IsRefreshing = false;
        }
SunnyMukherjee commented 5 years ago

Even though I am not a fan of using the MessagingCenter to message from the viewmodel class to the code-behind, I get around the IsRefreshing bug in this way.

I used the MainThread class in the Xamarin.Essentials package like below.

HomeViewModel.cs MainThread.BeginInvokeOnMainThread ( () => { MessagingCenter.Send<HomeViewModel>(this, "RefreshFinished"); } );

HomePage.xaml.cs MessagingCenter.Subscribe<HomeViewModel>(this, "RefreshFinished", (viewModel) => { MainThread.BeginInvokeOnMainThread(() => { this.PullToRefreshLayoutControl.IsRefreshing = false; }); });

RonnyBansemer commented 5 years ago

I'm facing the same problem:

XAML:

<ptr:PullToRefreshLayout IsPullToRefreshEnabled="{Binding Path=CloudService.IsReachable}"
                         RefreshCommand="{Binding Path=RefreshMasterDataCommand}"
                         IsRefreshing="{Binding Path=MasterDataIsRefreshing}">
   <ScrollView> 
      <!-- some stuff -->
   </ScrollView>
</ptr:PullToRefreshLayout>

ViewModel:

class MyVM : INotifyPropertyChanged
{
      DelegateCommand _refreshMasterDataCommand;
      public DelegateCommand RefreshMasterDataCommand => _refreshMasterDataCommand ?? (_refreshMasterDataCommand = new DelegateCommand(OnRefreshMasterDataExecuted));

      void OnRefreshMasterDataExecuted()
      {
         MasterDataIsRefreshing = true;
         NavigateAsync($"{nameof(Views.MasterDataUpdateModalPage)}", useModalNavigation: true);
         MasterDataIsRefreshing = false;
      }

      public bool MasterDataIsRefreshing { get; set; }
}

OnPropertyChanged is implemented using PropertyChanged.Fody and works fine for all other Properties.

ViewModel (even not working):

class MyVM : INotifyPropertyChanged
{
      DelegateCommand _refreshMasterDataCommand;
      public DelegateCommand RefreshMasterDataCommand => _refreshMasterDataCommand ?? (_refreshMasterDataCommand = new DelegateCommand(OnRefreshMasterDataExecuted));

      void OnRefreshMasterDataExecuted()
      {
         MasterDataIsRefreshing = true;
         NavigateAsync($"{nameof(Views.MasterDataUpdateModalPage)}", useModalNavigation: true);
         MainThread.BeginInvokeOnMainThread(() => MasterDataIsRefreshing = false);
      }

      public bool MasterDataIsRefreshing { get; set; }
}
nielscup commented 5 years ago

same problem here

draco961 commented 5 years ago

Same problem :-) This in the ViewModel solved the problem, Thanks @jochembroekhoff for the tip Xamarin.Forms.Device.BeginInvokeOnMainThread(() => IsRefreshing = false);

wint100 commented 5 years ago

Same issue and same fix as above

phlashdev commented 5 years ago

I've used the layout in multiple views in our project. The same problem occurs only on one view, on the others everything works as expected. But I wasn't able yet to determine what is causing the issue.

Note: the "BeginInvokeOnMainThread"-Fix worked for me too!

gaocan1992 commented 5 years ago

First I thought it is because the binding mode issue. So we cannot change value and let it propagate from view controller to layout itself. I modified the code like this:

/// <summary>
        /// The is refreshing property.
        /// </summary>
        public static readonly BindableProperty IsRefreshingProperty =
            BindableProperty.Create(nameof(IsRefreshing), typeof(bool), typeof(PullToRefreshLayout), false, BindingMode.TwoWay, propertyChanged: OnIsRefreshingChanged);

        protected static void OnIsRefreshingChanged(BindableObject bindable, object oldValue, object newValue) {
            var pullToRefreshLayout = bindable as PullToRefreshLayout;
            if (pullToRefreshLayout == null) return;
            pullToRefreshLayout.IsRefreshing = (bool)newValue;
        }

        /// <summary>
        /// Gets or sets a value indicating whether this instance is refreshing.
        /// </summary>
        /// <value><c>true</c> if this instance is refreshing; otherwise, <c>false</c>.</value>
        public bool IsRefreshing {
            get { return (bool)GetValue(IsRefreshingProperty); }
            set { SetValue(IsRefreshingProperty, value); }
        }

However it doesn't work (it should but I could figure out why)

<controls:PullToRefreshLayout x:Name="PullToRefreshLayout"
                                  IsPullToRefreshEnabled="True"
                                  RefreshCommand="{Binding RefreshCommand}">
PullToRefreshLayout.IsRefreshing = true;
await LoadAsync();
PullToRefreshLayout.IsRefreshing = false;

This is what I have done to make it works.

scriptBoris commented 5 years ago

Hello author, can you tell when this problem will be solved into NugetPackage?

JunkyXL86 commented 5 years ago

Can someone create an ultra simple fully working sample app that reproduces this behavior?

jamesmontemagno commented 4 years ago

Please use the RefreshView as this is officially deprecated now https://docs.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/refreshview

predalpha commented 1 year ago

I've recently encountered the same issue with the refreshView. I dont use prism but i use MVVM with the community control toolkit. Solved by explicitely setting the binding mode to OneWay :

<RefreshView x:DataType="local:myViewModel" Padding="20,10" Command="{Binding PageAppearingCommand}" IsRefreshing="{Binding IsBusy, Mode=OneWay}">