Trigger an action to start after X milliseconds
Asked Answered
F

5

20

I'm developing a Xamarin Forms mobile app, which has a page containing a SearchBar, a ListView, and Map control. The list view contains a list of addresses, which are reflected as pins on the map.

As the user types in the SearchBar, the ListView is automatically updated (through ViewModel binding). The ViewModel method that filters the datasource for the list looks something like this...

void FilterList()
{
    listDataSource = new ObservableCollection<location>(
        locationData.Where(l => l.Address.Contains(searchBar.Text))
    );

    // ***** Now, update the map pins using the data in listDataSource
}

I want to update the Map as the ListView is filtered, but not on EVERY keypress as that could happen multiple times a second. Essentially, I want a "rolling pause" in each FilterList event before the Map is updated. In pseudo-code...

    // ***** Now, update the map pins using the data in listDataSource
    if (a previously-requested map update is pending)
    {
        // Cancel the pending map update request
    }

    // Request a new map update in 1000ms using [listDataSource]

It's possible to do this using the Stopwatch class, which is available in Portable Class Libraries, but I suspect there's a cleaner/better way to accomplish this using Tasks.

Can anyone suggest a "cleverer" PCL-compatible way to do this?

Fechter answered 7/8, 2014 at 7:25 Comment(2)
What PCL profile are you using?Wendolyn
Profile7 - portable-net45+netcore45+MonoAndroid1+MonoTouch1Fechter
C
2

The problem with simple delays is that you can get a bunch of events queued up for when the delay expires. You can add logic to throw away the events that were raised while the delay was running. If you want to declare the logic at a higher level, you can use Reactive Extensions.

Cooler answered 12/8, 2019 at 17:12 Comment(1)
Thanks @edward-brey - I bound the Text property of a Field to a ViewModel property, and used that to fire a TextChanged event, and then used Rx Observable.FromEventPattern with the event, using .Throttle(TimeSpan) to define the interval at which the events would actually bubble up.Fechter
F
24

you can try :

await Task.Delay(2000);
Fleisig answered 7/8, 2014 at 15:7 Comment(4)
Task.Delay is available in PCL (and in Profile7), and there are overloads that take a CancellationToken.Hypoglycemia
I m using in Xamarin.Forms to delay my animations, so i can assure it works.Fleisig
My bad guys - I was basing my comments on the MSDN documentation for Task.Delay and the fact that PCL isn't included in the Version Information section (in the way that PCL is included in the Version Information section for the Task class as a whole).Fechter
This answer is only part of what is needed to solve what is in the question. How "cancel the pending update"? So that several quick key presses don't trigger several updates. HarshitGindra's answer is more complete.Crescentic
R
13

Just like you said this can be accomplished in a very clean way using Tasks and async programming.

You will want to read about it: http://msdn.microsoft.com/en-us/library/hh191443.aspx

Here's an example:

public async Task DelayActionAsync(int delay, Action action) 
{
    await Task.Delay(delay);

    action();
}
Roue answered 7/8, 2014 at 17:2 Comment(4)
As I mentioned to Rui, the Delay method on the Task class is not available in PCL, so that's not an option for me.Fechter
@Fechter It looks to me like it is available in Profile7 (in VS 2013).Wendolyn
My bad @Wendolyn - I was basing my comments on the MSDN documentation for Task.Delay and the fact that PCL isn't included in the Version Information section (in the way that PCL is included in the Version Information section for the Task class as a whole).Fechter
@Fechter Well, a PCL profile should be just an intersection of some set of framework versions. So, if something is available in all the versions that are relevant to you, it should be available in PCL too. I think what PCL mentioned in the docs is supposed to mean is that it's available in all PCL profiles.Wendolyn
O
13

Here is what I've done and it works in my Xamarin Form apps.

    public string Search
    {
        get { return _search; }
        set
        {
            if (_search == value)
                return;

            _search = value;
            triggerSearch = false;
            Task.Run(async () =>
            {
                string searchText = _search;
                await Task.Delay(2000);
                if (_search == searchText)
                {
                    await ActionToFilter();
                }
            });
        }
    }

I've this 'Search' Property binded to my Entry field. Whenever the user filters something, code waits for 1 second and then compares the new Text field with the field it was before 1 second before. Assuming the string is equal implies that user has stopped entering the text and code can now be triggered to filter.

Oversight answered 11/6, 2017 at 2:10 Comment(2)
What is triggerSearch?Meuser
triggerSearch is the property I use to inform the UI whether filtering has started. Its the way I use to make the user aware if any backend processing is going on. Although it is not required for this question. I forgot to take it out.Oversight
C
2

The problem with simple delays is that you can get a bunch of events queued up for when the delay expires. You can add logic to throw away the events that were raised while the delay was running. If you want to declare the logic at a higher level, you can use Reactive Extensions.

Cooler answered 12/8, 2019 at 17:12 Comment(1)
Thanks @edward-brey - I bound the Text property of a Field to a ViewModel property, and used that to fire a TextChanged event, and then used Rx Observable.FromEventPattern with the event, using .Throttle(TimeSpan) to define the interval at which the events would actually bubble up.Fechter
P
0

Working in latest xamarin version.

Another Great method way on button click is ,

    public async void OnButtonClickHandler(Object sender, EventArgs e)
{
    try
    {
        //ShowProgresBar("Loading...");

        await Task.Run(() =>
        {
            Task.Delay(2000); //wait for two seconds

            //TODO Your Business logic goes here
            //HideProgressBar();

        });

        await DisplayAlert("Title", "Delayed for two seconds", "Okay");

    }
    catch (Exception ex)
    {

    }
}

async and await keys are important, suppose if you working on multithread environment.

Pseudonymous answered 14/3, 2018 at 7:5 Comment(3)
Task.Delay(2000) is 2 seconds, not 2 milliseconds.Cooler
This seems overly complicated. You are already in an async method. Why put Task.Delay inside Task.Run? Why not do what already existing answers do, which is simply await Task.Delay? I don't see what this answer contributes to this Q&A. (Its also worth reviewing blog.stephencleary.com/2013/11/…)Crescentic
sorry, ignore the link I just gave. That isn't the problem here. There is nothing inherently wrong with the way you coded; it just felt a bit cleaner to move Task.Delay out of the Task.Run - though I see that does requiring prefixing that line also with await, so on further thought, what you did is justifiable.Crescentic

© 2022 - 2024 — McMap. All rights reserved.