3

I'm struggling to prevent double click on a button using ReactiveUI with Xamarin Forms. Let's say we have a command like this:

NextCommand = ReactiveCommand.CreateFromTask(async () => await HostScreen.Router.NavigateAndReset.Execute(new NewViewModel()));

It's bound to a button in a normal Xamarin Forms way. Unfortunately, very fast click causes two command calls.

Is it possible to fix it using ReactiveUI?

I updated the question to summarise what I've tried:

var obs = Observable.FromAsync(async () => await HostScreen.Router.NavigateAndReset.Execute(new CitizensIndexViewModel()))
                    .Throttle(TimeSpan.FromSeconds(2));
LoginCommand = ReactiveCommand.CreateFromObservable(() => obs);

Unfortunately, it didn't help. When you double-click very fast on a button, two executions of a command will take place.

I've also tried to add lock statement or semaphore to 'execute' method but it also failed to work because executions aren't done in parallel. That's also a reason why using CanExecute method won't work.

What can potentially work is an ugly bool flag. The only problem with this solution is that I'd have to reset it on back navigation which is obviously doable but I don't consider it as the best solution.

  • 1
    That doesn't sound right. See implementation over at https://github.com/reactiveui/ReactiveUI/blob/f23538577ea7e567ab372c550adebebe9fdbb7f7/src/ReactiveUI/ReactiveCommand.cs#L566 There's not enough information in this question to be able to answer. What is the usual way? What does your implementation look like. Please provide a repro. – Geoffrey Huntley Oct 06 '17 at 14:38
  • @GeoffreyHuntley I updated my question. I hope it's more clear what I mean . I'm fairly new to ReactiveUI so please forgive me my ignorance :). I only want to learn – mierzynskim Oct 09 '17 at 15:02

2 Answers2

3

You can pass a CanExecute parameter which will decide if the command can be executed. As you cans see in Controlling Executability :

var canExecute = this
    .WhenAnyValue(
        x => x.UserName,
        x => x.Password,
        (u, p) => !string.IsNullOrEmpty(u) && !string.IsNullOrEmpty(p));
var command = ReactiveCommand.CreateFromObservable(this.LogOnAsync, canExecute);

In very similar manner you can use a waiting indicator with your command:

bool busy = false;
NextCommand = ReactiveCommand.CreateFromTask(
async () => 
{ 
    busy = true;
    // do your studd here.
    busy = false;
},
!busy);

Hope this helps.

Umair M
  • 10,298
  • 6
  • 42
  • 74
  • See my updated question. Maybe using CanExecute is a right path but even you block it with bool flag, the next execution isn't done in parallel and it will succeed – mierzynskim Oct 13 '17 at 11:17
  • Have your tried to debug your code with CanExecute? Did you actually see that CanExecute returns true for second click? What do you mean by executions aren't done in parallel? if you are using a isBusy for CanExecute, your button should be disabled after first click immediately. Is it not happening? – Umair M Oct 13 '17 at 11:29
3
var canLogin = this.WhenAnyValue(vm => vm.IsLogingExec, p =>!p);
LoginCommand = ReactiveCommand.CreateFromTask(async () => { this.IsLogingExec = true; await HostScreen.Router.NavigateAndReset.Execute(new CitizensIndexViewModel()); } , canLogin);

. 
. 
. 

private bool isLogingExec;
public bool IsLogingExec{
    get => isLoggingExec;
    set => this.RaiseAndSetIfChanged(ref isLogingExec, value);
} 

canLogin is an observable that return true or false depending on IsLogingExec value.

ReactiveCommand.CreateFromTask has a second parameter, true if the command can be executed, false if not. If a command cannot be executed, the button with the command binded will be disabled.

The first thing the command is doing when executed is to change the IsLogingExec to false, that's way canLogin will change to false and the commandbutton will be disabled, and then, it will execute the navigate method.

IsLogingExec is a reactive property, that's way canLogin will react to changes.

Luis
  • 436
  • 4
  • 11