4

Using RxUI for Xamarin.Forms, how would you create a command that is meant to be executed only once automatically (when a page initially appears) but that te user can request its execution later on (like from a pull to refresh kind of event)?

I've hooked my command to the Appearing event using the FromEventPattern but when I navigate back to the page it gets executed again which is an undesired behavior.

This is my scenario: I need a list to be populated automatically when the user opens the page that contains it. Then the user can select an element and view its details in a separate page (using a NavigationPage), but when the user returns to the list page it gets repopulated which should not happen. The user should be able to request new data vía a button or pull to refresh, though.

Thank you.

io_exception
  • 154
  • 1
  • 13

3 Answers3

4

Using Dorus hint: in your page consturctor you need to create an observable from the event, then Take just the first one:

Observable.FromEventPattern(ev => Appearing += ev, ev => Appearing -= ev)
            .Select(e => Unit.Default)
            .Take(1)
            .InvokeCommand(ViewModel.InitialCollectionLoad);
io_exception
  • 154
  • 1
  • 13
1

Here's how I've handled this scenario. You'll need a behavior similar to this one, which will invoke your command only when a specific, known value is set.

// Only invoke the command when the property matches a known value that can't happen through normal execution
this.WhenAnyValue(vm => vm.SomeProperty)
                .Where(sp => sp == null)
                .Throttle(TimeSpan.FromSeconds(.25), TaskPoolScheduler.Default)
                .Do(_ => Debug.WriteLine($"Refresh the List"))
                .InvokeCommand(GetList)
                .DisposeWith(SubscriptionDisposables);

At the end of your constructor, set SomeProperty to match the known value

this.SomeProperty = null; // or some value that makes sense

In this case, you don't need to fire the command manually in OnAppearing - it happens when your ViewModel is constructed for the first time and will not be executed again until the ViewModel is disposed and recreated. It seems a little hack-ey to me, so I'm hoping wiser, more seasoned RxUI wizards will chime in, but it gets the job done.

If you prefer to leave the OnAppearing call in place, you could also potentially handle this by setting the canExecute property of your ReactiveCommand and using an entirely different ReactiveCommand for your PullToRefresh action (even though both commands would behave the same otherwise). In this scenario, you'd want to make the canExecute always false after the list is populated initially, so even when the user return to the page the initial population isn't triggered.

var isInitialized = this.WhenAnyValue(vm => vm.IsInit).Select( _ => _ == false).DistinctUntilChanged();

InitList = ReactiveCommand.CreateFromTask( _ => 
{ 
    // get list 
}, isInitialized);

RefreshList = ReactiveCommand.CreateFromTask( _ =>
{
    // essentially the same as InitList, but with different/no canExecute parameters
});

InitList.ObserveOn(RxApp.MainThreadScheduler).Subscribe(result =>
{
    this.IsInit = false
}).DisposeWith(SubscriptionDisposables);

The disadvantage here is, obviously, you have some logic duplicated

Joe
  • 370
  • 3
  • 14
  • Thanks, @Joe, your solution works too, and I can use it to achieve the task from the ViewModel. – io_exception Aug 18 '17 at 19:19
  • @fferegrino - care to provide the FromEventPattern solution proposed by Dorus as an answer also? It may be useful for others and I am curious myself – Joe Aug 18 '17 at 19:57
1

Maybe I'm misunderstanding, but I do this often, so I don't think I am.

I just use the VM creation as the trigger for the initial execution of the command. Then I associate that same command with the pull-to-refresh feature in XF.

So my VM looks like this:

public class MyVM
{
    public MYVM()
    {
        this.refreshCommand = ...;

        this
            .refreshCommand
            .Execute()
            .Subscribe()
                _ => {},
                _ => {});
    }

    public ReactiveCommand<...> RefreshCommand => this.refreshCommand;
}

This way the command is executed as soon as the VM is created, so data is retrieved ASAP. But it won't re-execute again unless the VM is recreated or the user pulls-to-refresh.

Kent Boogaart
  • 175,602
  • 35
  • 392
  • 393