Convenient in-app communication with Reactive interactions #1

A very convenient feature included in ReactiveUI version 7 is called Interactions. In this blog post, I’d like to give a short overview of how to use interactions and (which might be even more interesting for hardcore developers) how to tweak and extend them!

What’s it about?

Interactions are a new type of communication principle between different parts of an application. The typical command flow would look similar to this: When the user hits a button in the UI, the view would invoke one of the ViewModel’s commands. The ViewModel would probably look for a service instance that is able to provide the necessary business logic (such a service instance would have been passed to the ViewModel via dependency injection, and call one of the service’s methods.

In this scenario, all of the components involved are directly connected to each other: The View has a reference to its ViewModel, and an instance of the service has been made available to the ViewModel through injection. However, there might be different scenarios which don’t allow direct access of one component to another. This is were interactions come in – they allow us to send information to any component which is interested in it. Likewise, we can request information from app components troughout the whole app. The main difference to traditional command flow is: When using interactions, we do not know which (if any) component will receive the data send, or deliver the information requested!

Let’s talk this through using some example:

Broadcast data

One of the simplest scenarios is the broadcast use-case: Imagine a large app that is only available to users after logging in, so it has basic information about the current user such as an avatar image. In some settings section, the user is able to replace that image with a new one. Now, the avatar image might be displayed in several parts of the app – we might see it in the main menu bar, on a page that displays the current user’s account details, in the settings section where it is possible to exchange it, etc. Obviously, as soon as the user changes the avatar picture, the new picture should be displayed in all of those locations to avoid confusion (“The menu bar still shows the old image, did the exchange process not work correctly?”).

To do so, we’d need to detect all components that display the avatar image, make them accessible from within the settings section (or the service that does the image exchange), and ask them to refresh their view. Sounds complicated? Think about another scenario: Somebody adds a new section to the app that displays the avatar image as well – this developer would need to know about the dependency between the settings and the newly created component, and also implement the update mechanism.

Instead, I suggest a broadcast style approach: Whenever the avatar image is changes, send out an information broadcast to all components within the app, informing them that the image has changed. It is now up to each individual component to react to that broadcast call and refresh itself, if necessary.

To implement this approach, we first need to define an interaction. A ReactiveUI interaction is a static property, defined within a static class. I usually define several static classes to group different types of interactions, but in simple projects you might as well collect all interactions in one class, or even add them to an existing static class:

public static class MyAppInteractions
{
	public static Interaction<Unit, Unit> AvatarChangedInteraction = new Interaction<Unit, Unit>();
}

In this code snippet, note that Unit refers to System.Reactive.Unit (Reactive’s standard type representing empty values), and the Interaction<TInput, TOutput> class used is in fact the ReactiveUI.Interaction<TInput, TOutput> class provided by the ReactiveUI framework! This means that for declaring an interaction no custom classes are necessary, all we might need to do is specify the input and output types of our interaction. In my example, the purpose of the interaction is to only inform components that the avatar has been changed without passing additional information, therefore we can use System.Reactive.Unit as input type. Alternatively, we might want to pass the new avatar image’s file path in addition – in this case we’d simply declare that the interaction will contain a string content value:

public static class MyAppInteractions
{
	public static Interaction<string, Unit> AvatarChangedInteraction = new Interaction<string, Unit>();
}

(Ignore the second type parameter at the moment, we’ll come to that in a later section.)

Obviously, with this the interaction is declared but not yet used. To actually make use of it consists of two parts:

  • Within the app’s settings section, we need to start the interaction to indicate that the avatar image has been changed:
    try
    {
    	await MyAppInteractions.AvatarChangedInteraction.Handle(@"C:/new_file_path/avatar_image.png");
    }
    catch (UnhandledInteractionException<string, Unit>)
    {
    	// TODO
    }
    

    The core logic here is invoking the interaction’s Handle method. If specified with string input parameter, we (of course) need to pass the file path as string – if we had declared Unit as generic input type, we’d simply pass Unit.Default to indicate that no input data will be sent together with the interaction.

    However, you’ll notice that I wrapped this line in a try ... catch block. The reason is: If no other component reacts to out interaction (we’ll be looking at how to react to an interaction in a moment), an UnhandledInteractionException will be thrown! This might be interesting in some scenarios, but in our use-case we can just ignore the exception (if no other component is currently displaying the image, fine!). I’d nevertheless recommend to put a comment in the catch section, if only to remind colleagues (or your future self) why the catch block doesn’t do anything after catching the exception… Anyways, it’s definitely important to remember to always wrap any interaction’s Handle call within a try ... catch block! For those scenarios where you really are not interested in anyone reacting to an interaction, I’ll suggest an easier solution in the next article.

  • Within any component that must to react to the interaction, we need to register a handler. Even this can be done in one simple method call:
    MyAppInteractions.AvatarChangedInteraction.RegisterHandler(async context =>
    	{
    		var imageFilePath = context.Input;
    		// TODO: actually refresh UI
    	});
    

    The RegisterHandler method call expects a lambda (of course, you could as well use a named method instead) that is invoked whenever the interaction’s Handle method has been called (see above). A so-called interaction context is passed to the handler method, which contains additional information about the interaction: In our case, the context’s Input property is of type string and contains the new image file path. After retrieving this, we would take care of the actual business logic (e.g., updating the file path in some visual picture component and reloading it). Obviously, if we had specified the interaction’s input parameter as Unit, there would be no additional information to be retrieved (but the handler method would still be invoked – in this case, it would need to retrieve the information about the changed image path elsewhere).

    What the handler method actually does depends on the scenario, of course. Most tutorials as well as the official documentation will recommend to call the interaction context’s SetOutput method after this custom logic has finished – this method is generally used to pass information back to the component that originally invoked the interaction. However, in our case – since it is of type Unit and we don’t expect any feedback – passing back data is not necessary. In addition, the SetOutput closes the interaction, which means that only one component will receive the interaction. Therefore we should skip the call to context.SetOutput in this case (we’ll have a look at scenarios that in fact need the SetOutput method in a moment.)

Request data

The second use-case for iteractions I’d like to present is, in contrast to broadcasting data to any component that is listening, communication between two distinct components that know of each other but cannot directly access each other. For example, imagine a service within your app that knows about a popup / menu bar / etc. whose ViewModel is keeping track of some data, but it cannot directly request it. One approach to solve this is to send out an interaction, asking for that specific data.

Compared to our first scenario, there are three major distinctions:

  • As mentioned before, we’re communicating with one particular component (instead of providing information for everyone who is interested),
  • no data is passed as part of the initial interaction (since it only acts as request, but
  • data is expected to be passed back as a result of invoking the interaction.

This means, this time we create an interaction that specifies some data model class as output parameter, and the input parameter is empty, meaning of type Unit (although, of course, we could as well define an input type to force interaction users to pass information about which data shall be reported back):

public static class MyAppInteractions
{
	public static Interaction<Unit, SomeDataHolder> RequestForDataInteraction = new Interaction<Unit, SomeDataHolder>();
}

The rest of this example is actually rather straightforward: To request the above-mentioned data from our remote component, we call the interaction’s Handle method – this time without passing any data (however, since some input is required, we use the well-known Unit.Default):

SomeDataHolder someVeryImportantData;
try
{
	someVeryImportantData = await MyAppInteractions.RequestForDataInteraction.Handle(Unit.Default);
}
catch (UnhandledInteractionException<string, Unit>)
{
	// TODO
}

In this scenario, the catch block suddenly makes sense – since the aim is to request data, we need to be informed in case this data cannot be retrieved, and decide what to do as fallback scenario.

How would our target component react to such a request for data? Well, probably just pass back the requested information like this:

MyAppInteractions.RequestForDataInteraction.RegisterHandler(async context =>
	{
		context.SetOutput(someVeryImportantData);
	});

With that, the purpose of the SetOutput method should become clear! The data itself will the be returned by the Handle (see the above code snippet).

If you can think about some potential applications of RegisterUI interactions after having read this blog post, stay tuned for the next in line where I’ll discuss a few ideas of how to make interactions more user friendly!