Reputation: 2731
Most (if not all) of the examples online register a handler, but then return a discrete Observable value (i.e. Observable.Return(RecoveryOptionResult.CancelOperation)
). For a proper implementation, it would be best to present the RecoveryOptions
list to the user as a list of buttons (or something similar) and pass on flow control to the user.
What I am struggling with is how to await a button click by the user (or more specifically, how to wait for one of the RecoveryOption
commands to have its RecoveryResult
set).
I managed to hack something together that does this, but I cannot imagine this way is correct. My lack of experience using reactiveui is preventing me from conceptualizing the proper way of monitoring a ReactiveList<IRecoveryCommand>
.
Below is my hacked up code.
// UserError.RegisterHandler(x => HandleErrorAsync(x));
private async Task<RecoveryOptionResult> HandleErrorAsync(UserError error)
{
// present error UI...
// use ReactiveCommand's IsExecuting observable to monitor changes (since RecoverResult is not an observable)
// is there a better way to do this??? this seems sub-optimal
await error.RecoveryOptions
.Select(x => x.IsExecuting)
.Merge()
.Where(_ => error.RecoveryOptions.Any(x => x.RecoveryResult.HasValue))
.FirstAsync();
// recovery option was clicked in the UI
// get the recovery option that was chosen
return error.RecoveryOptions
.Where(x => x.RecoveryResult.HasValue)
.Select(x => x.RecoveryResult.Value)
.First();
}
The main issue is that the RecoveryResult
is not observable. So I have to monitor IsExecuting
that is observable and then check the RecoveryResult
value. It seems however, that there must be a better way to do this.
Upvotes: 3
Views: 170
Reputation: 2731
Looking at this again today, I noticed that the reason I cannot observe RecoveryResult
is because RecoveryOptions
is a ReactiveList<IRecoveryCommand>
and IRecoveryCommand
is not observable. The easy fix for this problem is to just assume all recovery options are actually RecoveryCommand
objects (which are observable), but a more appropriate answer would be to generate an observable stream based on the IRecoveryCommand
contract.
We can adapt the code described in the RxUI docs on Recovery Options to support RxUI 6.5 by doing the following:
public static IObservable<RecoveryOptionResult> GetResultAsync(this UserError This, RecoveryOptionResult defaultResult = RecoveryOptionResult.CancelOperation)
{
return This.RecoveryOptions.Any() ?
This.RecoveryOptions
.Select(x => x.IsExecuting
.Skip(1) // we can skip the first event because it's just the initial state
.Where(_ => x.RecoveryResult.HasValue) // only stream results that have a value
.Select(_ => x.RecoveryResult.Value)) // project out the result value
.Merge() // merge the list of command events into a single event stream
.FirstAsync() : //only consume the first event
Observable.Return(defaultResult);
}
This extension method is required if you want to support any type of IRecoveryCommand
because it bases its observable stream on one of the only two observables it knows about. However, if you can be certain that you are only ever dealing with RecoveryCommand objects, you can do the following instead:
public static IObservable<RecoveryOptionResult> GetResultAsync(this UserError This, RecoveryOptionResult defaultResult = RecoveryOptionResult.CancelOperation)
{
return This.RecoveryOptions.Any() ?
This.RecoveryOptions
.Cast<RecoveryCommand>()
.Select(x => x // our command is now observable
// we don't Skip(1) here because we're not observing a property any more
.Where(_ => x.RecoveryResult.HasValue)
.Select(_ => x.RecoveryResult.Value))
.Merge()
.FirstAsync() :
Observable.Return(defaultResult);
}
I'll leave this answer up for the next while in hopes that @paul-betts can confirm or deny this is the appropriate strategy.
Upvotes: 1