Pull to refresh in viewmodel

15 posts, 0 answers
  1. Gagan
    Gagan avatar
    6 posts
    Member since:
    Nov 2015

    Posted 08 Jun 2016 Link to this post

    Is there a way i can make PullToRefresh a command in my viewmodel?

    Or is there another way to make it more MVVM?

  2. Pavel R. Pavlov
    Admin
    Pavel R. Pavlov avatar
    1251 posts

    Posted 09 Jun 2016 Link to this post

    Hi,

    I already answered this question in your support ticket. However I will copy/paste the same answer here so that others from the community can benefit from it.

    Pull to refresh is a feature allowing end customers to manually update a list of items. This is an UI operation and it should not be triggered from code. If developers need to update that same list they should directly add items to the list's ItemsSource. 

    As for the other way, you can create a custom behavior which can be attached to the RadListView. That behavior can listen for the RefreshRequested event and can trigger your custom command as well as the EndRefresh() method. Using behavior does not break any MVVM approach and is frequently used.

    If you ask if there is out of the box approach for implementing pull to refresh command - the answer is no. Currently we do not support such command. However, there are many third party behaviors (e.g. event to command behavior) that can fit your scenario. You can try looking for them as well.

    Regards,
    Pavel R. Pavlov
    Telerik
    Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Feedback Portal and vote to affect the priority of the items
  3. Pierre
    Pierre avatar
    261 posts
    Member since:
    Apr 2007

    Posted 15 Jan 2018 Link to this post

    Sorry for hijacking this old post. I currently use behaviours to hook some ListView commend to my Viewmodel. But can you point me the way to call the viewmodel Event with ICommand AND do a EndRefresh() on the local side? DO you pass the ListviewObject to the viewmodel for that?
    Thanks

  4. Pierre
    Pierre avatar
    261 posts
    Member since:
    Apr 2007

    Posted 15 Jan 2018 in reply to Pierre Link to this post

    I am using this EventToCommand

    public class EventToCommandBehavior : BehaviorBase<View>
        {
            Delegate eventHandler;
     
            public static readonly BindableProperty EventNameProperty = BindableProperty.Create ("EventName", typeof(string), typeof(EventToCommandBehavior), null, propertyChanged: OnEventNameChanged);
            public static readonly BindableProperty CommandProperty = BindableProperty.Create ("Command", typeof(ICommand), typeof(EventToCommandBehavior), null);
            public static readonly BindableProperty CommandParameterProperty = BindableProperty.Create ("CommandParameter", typeof(object), typeof(EventToCommandBehavior), null);
            public static readonly BindableProperty InputConverterProperty = BindableProperty.Create ("Converter", typeof(IValueConverter), typeof(EventToCommandBehavior), null);
     
            public string EventName {
                get { return (string)GetValue (EventNameProperty); }
                set { SetValue (EventNameProperty, value); }
            }
     
            public ICommand Command {
                get { return (ICommand)GetValue (CommandProperty); }
                set { SetValue (CommandProperty, value); }
            }
     
            public object CommandParameter {
                get { return GetValue (CommandParameterProperty); }
                set { SetValue (CommandParameterProperty, value); }
            }
     
            public IValueConverter Converter {
                get { return (IValueConverter)GetValue (InputConverterProperty); }
                set { SetValue (InputConverterProperty, value); }
            }
     
            protected override void OnAttachedTo (View bindable)
            {
                base.OnAttachedTo (bindable);
                RegisterEvent (EventName);
            }
     
            protected override void OnDetachingFrom (View bindable)
            {
                base.OnDetachingFrom (bindable);
                DeregisterEvent (EventName);
            }
     
            void RegisterEvent (string name)
            {
                if (string.IsNullOrWhiteSpace (name)) {
                    return;
                }
     
                EventInfo eventInfo = AssociatedObject.GetType ().GetRuntimeEvent (name);
                if (eventInfo == null) {
                    throw new ArgumentException (string.Format ("EventToCommandBehavior: Can't register the '{0}' event.", EventName));
                }
                MethodInfo methodInfo = typeof(EventToCommandBehavior).GetTypeInfo ().GetDeclaredMethod ("OnEvent");
                eventHandler = methodInfo.CreateDelegate (eventInfo.EventHandlerType, this);
                eventInfo.AddEventHandler (AssociatedObject, eventHandler);
            }
     
            void DeregisterEvent (string name)
            {
                if (string.IsNullOrWhiteSpace (name)) {
                    return;
                }
     
                if (eventHandler == null) {
                    return;
                }
                EventInfo eventInfo = AssociatedObject.GetType ().GetRuntimeEvent (name);
                if (eventInfo == null) {
                    throw new ArgumentException (string.Format ("EventToCommandBehavior: Can't de-register the '{0}' event.", EventName));
                }
                eventInfo.RemoveEventHandler (AssociatedObject, eventHandler);
                eventHandler = null;
            }
     
            void OnEvent (object sender, object eventArgs)
            {
                if (Command == null) {
                    return;
                }
     
                object resolvedParameter;
                if (CommandParameter != null) {
                    resolvedParameter = CommandParameter;
                } else if (Converter != null) {
                    resolvedParameter = Converter.Convert (eventArgs, typeof(object), null, null);
                } else {
                    resolvedParameter = eventArgs;
                }      
     
                if (Command.CanExecute (resolvedParameter)) {
                    Command.Execute (resolvedParameter);
                }
            }
     
            static void OnEventNameChanged (BindableObject bindable, object oldValue, object newValue)
            {
                var behavior = (EventToCommandBehavior)bindable;
                if (behavior.AssociatedObject == null) {
                    return;
                }
     
                string oldEventName = (string)oldValue;
                string newEventName = (string)newValue;
     
                behavior.DeregisterEvent (oldEventName);
                behavior.RegisterEvent (newEventName);
            }
        }
  5. Lance | Manager Technical Support
    Admin
    Lance | Manager Technical Support avatar
    1193 posts

    Posted 16 Jan 2018 Link to this post

    Hello Pierre,

    You need a reference to the RadListView in order to call EndRefresh.

    How you pass that to the ViewModel is up to you, but since you already have a behavior set up, you can just pass the RadListView as the CommandParameter instead of the event args (event args is the default).

    For example:

    <telerikDataControls:RadListView x:Name="radListView"
                        ItemsSource="{Binding MyItems}"
                        IsPullToRefreshEnabled="True">
        <telerikDataControls:RadListView.Behaviors>
            <portable:EventToCommandBehavior EventName="RefreshRequested"
                            Command="{Binding PullToRefreshCommand}"
                            CommandParameter="{x:Reference radListView}" />
        </telerikDataControls:RadListView.Behaviors>
    </telerikDataControls:RadListView>

    public class ViewModel
    {
        public ViewModel()
        {
            PullToRefreshCommand = new Command(async (rlv) => await RefreshData(rlv));
        }
     
        private async Task RefreshData(object obj)
        {
            // this just simulates a network delay
            await Task.Delay(1000);
                 
            // 1 - refresh your data
            MyItems.Add($"Item {MyItems.Count + 1}");
                 
            // 2 - end refresh
            (obj as RadListView)?.EndRefresh();
        }
     
        public Command PullToRefreshCommand { get; set; }
     
        public ObservableCollection<string> MyItems { get; set; } = new ObservableCollection<string> {"Item 1", "Item 2", "Item 3"};
    }

    I've attached a demo using the above code.

    If you have any further trouble, open a support ticket here, attach the code we can use to reproduce the problem and we'll investigate further.

    Regards,
    Lance | Tech Support Engineer, Sr.
    Progress Telerik
    Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Feedback Portal and vote to affect the priority of the items
  6. Pierre
    Pierre avatar
    261 posts
    Member since:
    Apr 2007

    Posted 25 Jan 2018 in reply to Lance | Manager Technical Support Link to this post

    Thanks, you code work great. But The PullToRefresh work only on IOS. In UWP I can't slide to make the refresh zone appears.

    Any idea?

    De we have a way to haev a template of the PullToRefresh zone? For now I only get an big black zone with a loading indicator. Can we write some text (like the last time we update the list)

  7. Lance | Manager Technical Support
    Admin
    Lance | Manager Technical Support avatar
    1193 posts

    Posted 26 Jan 2018 Link to this post

    Hi Pierre,

    If the UWP app is deployed to mobile device family, it should be working as expected. However on desktop, you'd need touch to pull it as the mouse won't trigger the gesture. In this case, you can dynamically show a refresh button if the runtime platform is UWP.

    Regarding customizing the pull to refresh area, you'll need to write a custom renderer as we don't expose the API in Xamarin.Forms, see this forum thread for a discussion and example.

    For your convenience, I've updated the cmeo to use the latest version and remove the deprecated code. Here's a short video of the attached demo at runtime and here's the entirety of the renderer in the AppDelegate class:

    using Foundation;
    using Telerik.XamarinForms.DataControls;
    using UIKit;
    using ListViewPullToRefreshStyle.iOS;
    using Xamarin.Forms;
    using Xamarin.Forms.Platform.iOS;
     
    [assembly: ExportRenderer(typeof(RadListView), typeof(CustomListViewRenderer))]
    namespace ListViewPullToRefreshStyle.iOS
    {
        [Register("AppDelegate")]
        public partial class AppDelegate : FormsApplicationDelegate
        {
            public override bool FinishedLaunching(UIApplication app, NSDictionary options)
            {
                Forms.Init();
     
                LoadApplication(new Portable.App());
     
                return base.FinishedLaunching(app, options);
            }
        }
     
        public class CustomListViewRenderer : Telerik.XamarinForms.DataControlsRenderer.iOS.ListViewRenderer
        {
            protected override void OnElementChanged(ElementChangedEventArgs<RadListView> e)
            {
                base.OnElementChanged(e);
     
                this.Control.PullToRefreshView.ActivityIndicator.ActivityIndicatorViewStyle = UIActivityIndicatorViewStyle.WhiteLarge;
     
                // and or
     
                this.Control.PullToRefreshView.ActivityIndicator.Color = UIColor.White;
                this.Control.PullToRefreshView.BackgroundColor = UIColor.Green;
            }
        }
    }


    As far as using text instead of the busy indicator animation, this is not currently a feature. If you're familiar with Xamarin.iOS, you can create a subview and use UITextView to manually create it, but this is not supported and a completely custom approach which falls outside the scope of Telerik Support. 

    Lastly, you'll need to create custom renderers for each platform and keep in mind not all platforms have the same customization features for the PullToRefresh indicator and you'll have to account for these nuances. Here is the documentation for the other platforms: here for UI for UWP and here UI for Xamarin.Android.

    If you'd like us to add an API that lets you customize it without a custom renderer, submit a feature request here.

    Regards,
    Lance | Tech Support Engineer, Sr.
    Progress Telerik
    Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Feedback Portal and vote to affect the priority of the items
  8. Pierre
    Pierre avatar
    261 posts
    Member since:
    Apr 2007

    Posted 29 Jan 2018 in reply to Lance | Manager Technical Support Link to this post

    Lance | Tech Support Engineer, Sr. said:Hi Pierre,

    If the UWP app is deployed to mobile device family, it should be working as expected. However on desktop, you'd need touch to pull it as the mouse won't trigger the gesture. In this case, you can dynamically show a refresh button if the runtime platform is UWP.

    It is a limitation of UWP itself? Or a limitation on Telerik RadListview?My app will be deployed on IOS and UWP (mobile and desktop). I can add a Refresh Button UWP view, but I got the sale problem with cellSwipe in the listview... Any workaround?

  9. Lance | Manager Technical Support
    Admin
    Lance | Manager Technical Support avatar
    1193 posts

    Posted 29 Jan 2018 Link to this post

    Hi Pierre,

    Correct, this isn't related to Xamarin or UI for Xamarin. It's a platform-specific approach, not necessarily a limitation, because many desktop PCs now have touchscreens. As a UWP developer you'd build your apps expecting certain inputs to be available and surface contextual interactions accordingly.

    We have been adding some features to the native RadListView to overcome some of these like the Handle reorder mode for the RadListView. On desktop, the handle allows you to reorder the item with mouse.

    There have been requests to bring more pointer base gestures to the native control, you can find the native control's home here on GitHubAdd an Issue to the repo here and requesting that swipe and pull to refresh to be supported. As you can see this Issue was the one that requested the Reorder mode support and was promptly added to the product.


    Advanced Workaround

    There is one workaround you can try, but it's a more advanced scenario, you could try to reroute pointer input into touch input (here's an example for WPF). I couldn't find any examples for UWP, but this is the Pointer input information you'd need to build it.

    Regards,
    Lance | Tech Support Engineer, Sr.
    Progress Telerik
    Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Feedback Portal and vote to affect the priority of the items
  10. Pierre
    Pierre avatar
    261 posts
    Member since:
    Apr 2007

    Posted 07 Feb 2018 in reply to Lance | Manager Technical Support Link to this post

    I use your solution for the listview SellSwipe too. I pass the LV instance to be able to do the EndItemSwipe().

    But I can't detect the swipeOffset and know if the swipe is right or left. Normally in the code behind we got an ItemSwipeCompletedEventArgs that contains an e.Offset for that. But I can't see anything like that directly in the LV object.

    For now I do:

    private async void OnItemSwipeCompleted(RadListView lv)
    {
        if (lv.IsSwipingInProgress == true)
        {
            await Task.Delay(1000);
            var item = lv.SelectedItem;
            var tt = lv.SwipeOffset;
            lv.EndItemSwipe();
        }
    }

     

    But lv.SwipeOffet contained the general offset information.

    Do we have another way? Can we pass the ItemSwipeCompletedEventArgs when commanding?

    Thanks

  11. Lance | Manager Technical Support
    Admin
    Lance | Manager Technical Support avatar
    1193 posts

    Posted 07 Feb 2018 Link to this post

    Hello Pierre,

    Yes, if you use an EventToCommandBehavior, you'll get the ItemSwipeCompletedEventArgs passed to the command's Action (take a look at the EventToCommandBehavior in my GitHub demo here). 

    Example

    Here's a small demo to convey the approach:

    In the View:

    <dataControls:RadListView ...>
        <dataControls:RadListView.Behaviors>
            <behaviors:EventToCommandBehavior EventName="ItemSwipeCompleted"
                                   Command="{Binding MySwipeCommand}"/>
        </dataControls:RadListView.Behaviors>
    </dataControls:RadListView>

    In the ViewModel

    public class ViewModel
    {
        public ViewModel()
        {
            MySwipeCommand = new Command(ExecuteOnSwipeCompete);
        }
     
        public Command MySwipeCommand { get; }
     
        private void ExecuteOnSwipeComplete(object obj)
        {
            var args = obj as ItemSwipeCompletedEventArgs;
            // this is the same args you get in a code-behind event handler
        }
    }


    Further Assistance

    If you have any trouble implementing it, please open a support ticket here and attach your code directly to the ticket (you have priority support). I will see the ticket come in and will investigate further.

    Regards,
    Lance | Tech Support Engineer, Sr.
    Progress Telerik
    Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Feedback Portal and vote to affect the priority of the items
  12. Pierre
    Pierre avatar
    261 posts
    Member since:
    Apr 2007

    Posted 07 Feb 2018 in reply to Lance | Manager Technical Support Link to this post

    Hello Lance,

    Ok I understand that, but in this case, I lost the LV object so I can't do the EndItemSwipe. In you exemple can I get the "sender" object to?

  13. Lance | Manager Technical Support
    Admin
    Lance | Manager Technical Support avatar
    1193 posts

    Posted 08 Feb 2018 Link to this post

    Hello Pierre,

    EventToCommand isn't designed to also pass the sender because this breaks MVVM.  If you don't mind tightly coupling the view to the viewmodel, you can pass the RadListView reference to the view model in the page's code behind.

    For example:

    ViewModel

    public class ViewModel
    {
        public ViewModel()
        {
            MySwipeCommand = new Command(ExecuteOnSwipeCompete);
        }
      
        public RadListView MyListViewReference { get; set; }
       
        public Command MySwipeCommand { get; }
       
        private void ExecuteOnSwipeComplete(object obj)
        {
            var args = obj as ItemSwipeCompletedEventArgs;
            // this is the same args you get in a code-behind event handler
      
            MyListViewReference.xxx
        }
    }


    View Markup

    <ContentPage..>
        <telerik:RadListView x:Name="MyListView" />
    </ContentPage>

    View Code behind:

    public partial class MyPage : ContentPage
    {
        public MyPage()
        {
            InitializeComponent();
     
            (BindingContext as ViewModel).MyListViewReference = this.MyListView;
        }
    }



    Regards,

    Lance | Tech Support Engineer, Sr.
    Progress Telerik
    Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Feedback Portal and vote to affect the priority of the items
  14. darrell
    darrell avatar
    1 posts
    Member since:
    Apr 2014

    Posted 13 Feb 2018 in reply to Lance | Manager Technical Support Link to this post

    The answers on this post are nasty hacks and this is disappointing from Telerik.

    It's doubly disappointing because the out of the box Xamarin.Forms ListView has Commanding for PullToRefresh where as the RadListView does not - it's worse than the out of the box component in this area.

    So here is once way to work around this:

     

    ```csharp

        public interface IGridAdaptor
        {
            void Cancel();
            void EndRefresh();      
        }

        public class RadGridAdaptor : IGridAdaptor
        {
            private readonly PullToRefreshRequestedEventArgs _args;
            private readonly RadListView _listView;

            public RadGridAdaptor(PullToRefreshRequestedEventArgs args, RadListView listView)
            {
                _args = args;
                _listView = listView;
            }
            public void Cancel()
            {
                _args.Cancel = true;
            }

            public void EndRefresh()
            {
                _listView.EndRefresh();
            }
        }

        public class RadGridPullToRefreshArgsConverter : IValueConverter
        {
            public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
            {
                var args = value as PullToRefreshRequestedEventArgs;
                var listView = parameter as RadListView;
                return new RadGridAdaptor(args, listView);
            }

            public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
            {
                throw new NotImplementedException();
            }
        }

    ```

     

    Then in your xaml:

     

    ```xml

     <telerikDataControls:RadListView.Behaviors>
                                    <b:EventToCommandBehavior EventName="RefreshRequested" 
                                              Command="{Binding RefreshCommand}"  
                                              EventArgsConverterParameter="{x:Reference listView}"
                                              EventArgsConverter="{converters:RadGridPullToRefreshArgsConverter}"/>
                                </telerikDataControls:RadListView.Behaviors>

    ```

    The idea is basically the `Behavior` handles the event, and then passes the Grid and the Event Args to a Converter.

    The Converter creates an `IGridAdaptor` which wraps those, and can be passed into your `ICommand`. This way you can potentially switch the ListView implementation in future without having to refactor your commands, by implementing a new Converter.

    The command accepts the `IGridAdaptor` as an argument does its business, then calls EndRefresh (or Cancel).

    ```

                RefreshCommand = new DelegateCommand<IGridAdaptor>(async (arg) =>
                {
                    await DoStuff();

                     _deviceService.BeginInvokeOnMainThread(() =>
                    {
                        arg.EndRefresh();
                    });               

                });

     

    ```

    Be careful, you must call the EndRefresh() from a UI thread.

     

     

     

  15. Lance | Manager Technical Support
    Admin
    Lance | Manager Technical Support avatar
    1193 posts

    Posted 14 Feb 2018 Link to this post

    Hi Darrel,

    Initially the RadListView didn't have support for commands, which was why we had to suggest such workarounds. We understood the need for this and thus we've recently added MVVM Command support for all the relevant events.

    You just need to add to the RadListView.Commands collection using an ID to identify which event the command is for (as opposed to directly wiring up the command to a property).

    Here's a list of the available IDs:





    You can use the default command as seen above (a ListViewUserCommand), or use a custom command by extending ListViewCommand and overriding the command methods.


    Custom Command Example

    In the custom command you have access to the Owner (RadListView), so you can call a method on the BindingContext as well as EndRefresh().

    <telerikDataControls:RadListView BindingContext="{StaticResource viewModel}"
                       ItemsSource="{Binding Items}"
                       IsPullToRefreshEnabled="True">
        <telerikDataControls:RadListView.Commands>
            <portable:CustomRefreshRequestedCommand />
        </telerikDataControls:RadListView.Commands>
    </telerikDataControls:RadListView>


    Command

    public class CustomRefreshRequestedCommand : ListViewCommand
    {
        public CustomRefreshRequestedCommand()
        {
            this.Id = CommandId.PullToRefreshRequested;
        }
             
        public override bool CanExecute(object parameter)
        {
            return true;
        }
     
        public override void Execute(object parameter)
        {
            Device.BeginInvokeOnMainThread(() =>
            {
                (Owner?.BindingContext as ViewModel)?.RefreshItems();
                Owner?.EndRefresh();
            });
        }
    }


    Documentation

    The documentation and SDKExample examples still needs to be updated with these options. I have informed the development team so that they can make sure these are in the next updates.

    In the meantime, the RadDataGrid does have a Commands article that is similar to explain what some of the properties are, but it's not an exact match (e.g. the IDs are different).

    Further support

    If you have any difficulty implementing a command, open a support ticket here and I'll make sure the approriate engineering team follows up with you.

    Regards,
    Lance | Tech Support Engineer, Sr.
    Progress Telerik
    Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Feedback Portal and vote to affect the priority of the items
Back to Top