Add Delay And Cancelation To A Command

JoshuaNovakJoshuaNovak USMember ✭✭

I have a search bar in my Xamarin.Forms app. I am trying to set it up so that when the user types the search bar waits for a period of time before sending the search so it does not hit the server on every character. It looks like I need to setup a cancelation token on the actual search function, but it's a command, so i'm not sure how that is supposed to work and even when I tried it as a regular function it didn't work. Does anyone know the proper way to handle this?

Here is my code:

View Model Code Behind:

async void OnTextChanged(object sender, TextChangedEventArgs e)
{
    SearchBar searchBar = sender as SearchBar;
    await ViewModel.SearchThings ("q[name_cont]=" + searchBar.Text);
}

View Model:

private Command _SearchThingsCommand;
public Command SearchThingsCommand { get { return _SearchThingsCommand ?? (_SearchThingsCommand = new Command (async () => await SearchThings())); } }

public async Task SearchThings(string query = "")
{
    if (IsBusy) return;

    IsBusy = true;
    SearchThingsCommand.ChangeCanExecute();

    SearchQuery = query;
    var sv = new ThingService();
    Response response = await sv.GetThingsAsync (1, PerPage, Query);
    TotalPages = response.total_pages;
    Page = response.page;

    Things.Clear ();

    foreach(Thing thing in response.things) {
        Things.Add (thing);
    }

    IsBusy = false;
    SearchThingsCommand.ChangeCanExecute();
}

Posts

  • ylemsoulylemsoul RUMember ✭✭✭
    edited June 2015

    In Page's constructor:
    public Page1() { // ... Observable.FromEventPattern<TextChangedEventArgs>(this.Entry, "TextChanged") // nameof(Entry.TextChanged) .Select(t => t.EventArgs.NewTextValue) .Throttle(TimeSpan.FromMilliseconds(500)) .ObserveOn(SynchronizationContext.Current) .Where(str => this.ViewModel.SearchThingsCommand.CanExecute(...)) .Subscribe(this.ViewModel.SearchThingsCommand.Execute); }

    Check this thread for details

  • JoshuaNovakJoshuaNovak USMember ✭✭

    @ylemsoul Is there a way to do this without using Reactive Extensions?

  • DavidDancyDavidDancy AUMember ✭✭✭✭

    @ylemsoul This is simple and very elegant. Thanks very much!

  • JoshuaNovakJoshuaNovak USMember ✭✭

    @ylemsoul Works perfectly!! Thanks a lot!!

  • RaphaelChiorlinRanieriRaphaelChiorlinRanieri BRMember ✭✭✭

    @ylemsoul I'm trying to use your code in a Slider...
    I was expecting it to call the function only once at the end...
    However the func is being called more than once at the end...

    Could you give a help?

    Here is my code:

    async void  StartOnChange()
    { 
    Console.WriteLine(“test"); 
    }
    
            private CancellationTokenSource throttleCts = new CancellationTokenSource();
    
    var  slider = new Slider {
                    Minimum = 0,
                    Maximum = 20,
                    Value = 1,
                    VerticalOptions = LayoutOptions.FillAndExpand,
                    HorizontalOptions =LayoutOptions.FillAndExpand,
                };
    
    slider.ValueChanged += async (sender, e) => {
    
    
                    Interlocked.Exchange(ref this.throttleCts, new CancellationTokenSource()).Cancel();
                    Task.Delay(TimeSpan.FromMilliseconds(500), this.throttleCts.Token) // throttle time
                        .ContinueWith(
                            delegate { this.StartOnChange(); },
                            CancellationToken.None,
                            TaskContinuationOptions.OnlyOnRanToCompletion,
                            TaskScheduler.FromCurrentSynchronizationContext());
    
    
                };
    
  • NicolasKrierNicolasKrier FRMember ✭✭✭

    Awesome and really well written.
    I never had to wrote the following : delegate { this.HandleString(this.Entry.Text); }
    Cool syntax too and nice to know !

    Thanks for sharing

  • kbiascikbiasci NZMember

    This is indeed a brilliant solution!
    Wrap the whole thing with a try-catch and Bob's your uncle :-)

Sign In or Register to comment.