After giving a short overview of some common use-cases of ReactiveUI interactions, this time I’d like to discuss a few approaches for tweaking and fine-tuning interactions for most easy usage!
Fire-and-forget broadcast
The first application of interactions I presented in my previous blog post was broadcasting data: Sending out a message with or without content for everyone to read if interested, without excpecting any result or feedback. Whenever one of my colleagues sees such a code snippet, they are annoyed by how long it is: The Interaction.Handle()
must always be wrapped within a try ... catch
block to avoid UnhandledInteractionExceptions
bubbling up. As mentioned before, these exceptions are useful in certain scenarios (e.g., for defining default values when not receiving an expected result from the interaction’s handler), however due to the nature of broadcast messages
- it is totally acceptable (and does not represent an ecxeptional state) if nobody reads the message, and
- it is not possible to expect feedback, since the message can be received by several recipients, in which case it would be unclear which of those is allowed to provide the feedback (due to the architecture of ReactiveUI interactions, only one recipient is technically able to do so, because afterwards the interaction is closed instantly).
Therefore, I’d like to suggest a customization of the original ReactiveUI interaction that respects these two limitations by fulfilling the following requirements:
- Does not throw an exception on its
Handle
method - Does not return any value
The easiest approach towards these two goals is to create an alternative to the Handle
method, e.g. called Broadcast
. To do so, we need a static extension method within a static class:
public static class ReactiveInteractionExtensions { public static async Task Broadcast<TInput>(this Interaction<TInput, System.Reactive.Unit> interaction, TInput input) { try { await interaction.Handle(input); } catch (UnhandledInteractionException<TInput, System.Reactive.Unit>) { // Don't care } } }
You’ll notice that this is a simple wrapper for the default Handle
method, that fulfils the two requirements established before:
- It wraps the call to
interaction.Handle()
in atry ... catch
block and does not react to anUnhandledInteractionException
, because we can be sure that such an exception will occur each time theBroadcast
method is used. - It defines
System.Reactive.Unit
as return type, and returnsTask
(thus, basically not returning a value while still running asynchronuously) compared to theIObservable<TOutput>
return type of the originalHandle
method, and indeed the result of the call tointeraction.Handle(input)
(see line 7) is never used nor assigned to any field.
Since this is an extension method, it can be called just like the Handle
method (assuming the ReactiveInteractionExtensions
class’ namespace is referenced):
await MyAppInteractions.AvatarChangedInteraction.Broadcast(@"C:/new_file_path/avatar_image.png");
Of course, for interactions that do not expect an input value, we still can pass System.Reactive.Unit.Default
. However, in case you need to do this regularly, a more convenient (and more readable) solution is to implement a second method within our ReactiveInteractionExtensions
class that is an overload to the first extension method and that does not even expect an input parameter:
public static async Task Broadcast(this Interaction<System.Reactive.Unit, System.Reactive.Unit> interaction) { try { await interaction.Handle(System.Reactive.Unit.Default); } catch (UnhandledInteractionException<System.Reactive.Unit, System.Reactive.Unit>) { // Don't care } }
This one is even more reduced to what’s absolutely necessary: It does declare neither input nor output type, therefore it can be called in the simplest possible way:
await MyAppInteractions.AvatarChangedInteraction.Broadcast();
Verbose interaction declarations
While these improvements reduce the necessary code for invoking an interaction, declaring interactions still needs a lot of boiler-plate code:
public static class MyAppInteractions { public static Interaction<Unit, Unit> AvatarChangedInteraction = new Interaction<Unit, Unit>(); }
Notice the redundant use of the System.Reactive.Unit
type in this code snippet – although it is useful to mark input and / or output types as not available, in the scenarios presented above (broadcast interactions that will never produce output data, and hardly expect any input values) this could be omitted for shorter declarations and better readability.
One approach towards this is to define custom interaction types, e.g. called BroadcastInteraction
for interactions that accept neither input not output data, InputInteraction<TInput>
for input-only interactions (see the above scenarios for samples of these two), or OutputInteraction<TOutput>
for interactions that request feedback but do not contain input data.
I will not go into detail and provide full code samples – check out my blog post about verbose command declarations, I’m sure this will guide you towards an implementation, since the design principles are really similar!
Pingback: Convenient in-app communication with Reactive interactions #1 – www.mobilemotion.eu