Quantcast
Channel: Category Name
Viewing all articles
Browse latest Browse all 10804

Using cross-app communication to make apps work together (10 by 10)

$
0
0

With Windows 10, the Universal Windows Platform (UWP) provides a number of improvements to better support sharing data and files across Windows apps.

Cross-app communication in Windows 10

Windows 10 introduces some new and improved ways to ease communication between apps on the same device, whether it’s an app launching another app with some data, or apps simply exchanging data without launching anything.

There are no restrictions or limits on which apps can talk to each other, or what kind of data they can exchange. This means that apps can define their own contracts with each other and extend each other’s functionality. It also makes it possible for app developers to break their apps into smaller experience chunks that are easier to maintain, update, and consume.

Reintroducing drag and drop

If you’ve developed for desktop before, you’re probably already familiar with the basics of drag and drop. In Windows 10, drag and drop has been reintroduced as UWP app APIs – with new features that enhance the UX. By fully exploiting these features, you can give the user a more tailored experience.

In this article, we’ll walk through how to deep-link apps, set up logic sharing between apps, and implement drag-and-drop concepts.

Deep linking: Getting an app ready

To demonstrate how to deep-link, we’ll use an example of a product inventory app that displays product details, along with a sales app that shows broad sales trends. To display individual product details, the sales app will deep-link into the inventory app.

The first step is making the inventory app available to be launched by other apps. To do this, we add a protocol declaration to the inventory app’s package manifest (Package.appxmanifest). Here’s how this declaration looks in the graphical editor, and the XML snippet it generates.

1_appManifest

Next, we need to add activation code so the app can respond appropriately when launched. The code goes into the inventory app’s Application class (App.xaml.cs), where all activations are routed. To respond to protocol activations, we need to override the OnActivated method of the activation class. That code looks like this:

protected override void OnActivated(IActivatedEventArgs args)
{
    Frame rootFrame = CreateRootFrame();

    if (args.Kind == ActivationKind.Protocol)
    {
        var protocolArgs = args as ProtocolActivatedEventArgs;
        rootFrame.Navigate(typeof(ProtocolActivationPage), protocolArgs.Uri);
    }
    else
    {
        rootFrame.Navigate(typeof(MainPage));
    }

    // Ensure the current window is active
    Window.Current.Activate();
}

In this code, we check the incoming IActivatedEventArgs to determine if it is a protocol activation. If so, we typecast the incoming arguments as ProtocolActivatedEventArgs and send the incoming URI to the ProductDetails page. The ProductDetails page is set up to parse a URI such as com.contoso.showproduct:Details?ProductId=3748937 and show the corresponding product’s details. The inventory app can now handle incoming deep links.

Deep linking into the app

Finally, we can enable the sales app to deep-link into the inventory app. The app simply uses the Launcher.LaunchUriAsync method to deep-link into the inventory app. The code might look like this:

Uri uri = new Uri("com.contoso.showproduct:?ProductId=3748937");
await Launcher.LaunchUriAsync(uri);

Launching a specific app

With Windows 10, you now also have the ability to launch a specific app for when you want to control which app the user is launched into. Look at the code below:

var options = new LauncherOptions();
options.TargetApplicationPackageFamilyName = "24919.Contoso.InventoryApp";

Uri uri = new Uri("com.contoso.showproduct:?ProductId=3748937");
await Launcher.LaunchUriAsync(uri, options);

We can invoke a specific app through the LauncherOptions of the Launcher. Using this API, you can specify the TargetApplicationPackageFamilyName, the unique name you get when you register an app. By specifying this name alongside the launch URI, you can easily launch a specific app that implements the protocol declaration.

Sharing a file between apps

You can also make files accessible to the app you’re launching, using another new API, SharedStorageAccessManager.AddFile, which returns a token that you can send to the launched app. A data structure called ValueSet is used for communication between the apps. ValueSets are key/value dictionaries that can carry simple types: integers, floating-point numbers, strings, and byte arrays. In the following example, we use this API to make an IStorageFile instance available to the inventory app.

var options = new LauncherOptions();
options.TargetApplicationPackageFamilyName = "24919.Contoso.InventoryApp";

var token = SharedStorageAccessManager.AddFile(gpxFile);

ValueSet inputData = new ValueSet();
inputData.Add("Token", token);

Uri uri = new Uri("com.contoso.showproduct:?ProductId=3748937");
await Launcher.LaunchUriAsync(uri, options, inputData);

Launching an app to get a result back

In previous versions of Windows, if you wanted to launch an app and make it do something, there was no programmatic way of notifying you that the action was completed. With the new “launch for results” feature, that has changed. Let’s take a look at the code:

var options = new LauncherOptions();
options.TargetApplicationPackageFamilyName = "24919.Contoso.InventoryApp";
var launchUri = new Uri("com.contoso.showproduct:?ProductId=3748937");
await Launcher.LaunchUriForResultsAsync(launchUri, options, data);

var resultData = new ValueSet();
resultData.Add("Result", value);
operation.ProtocolForResultsOperation.ReportCompleted(resultData);

The new LaunchUriForResultsAsync method relies on the TargetApplicationPackageFamilyName API to make two-way communication between apps possible. That’s all we need in the sales app and we can now implement the following in the inventory app:

var resultData = new ValueSet();
resultData.Add("Result", value);
operation.ProtocolForResultsOperation.ReportCompleted(resultData);

This uses another ValueSet, but this time the data is passed back from the inventory app to the sales app when the ReportCompleted method is called.

Data sharing between apps

In other scenarios, apps may need to share data without sending the user into another app. For example, the sales app can display sales by region or store, and when that data is categorized by product, you might want to show how many products are available in a store or region. Although the inventory app would be the best source for that data, launching the inventory app in this case would disrupt the user experience. That’s the exact scenario app services are designed to handle.

In the examples below, we’ll show how the inventory app can provide a service, invoked by the sales app, to query the inventory app for its data.

Creating the inventory app service

To add an app service, we add a Windows Runtime Component to the solution that contains the inventory app.

2_newProject

Inside the project, we then add a new class called InventoryServiceTask that implements the IBackgroundTask interface. We must then get a BackgroundTaskDeferral to tell the operating system that the task is used as an app service and should be kept around for as long as the client needs it. We must also attach an event handler to the app service-specific RequestReceived event. The handler is invoked whenever the client sends a request for this service to handle. The code to initialize the inventory app service:

namespace Contoso.Inventory.Service
{
    public sealed class InventoryServiceTask : IBackgroundTask
    {
        BackgroundTaskDeferral serviceDeferral;
        AppServiceConnection connection;

        public void Run(IBackgroundTaskInstance taskInstance)
        {
            //Take a service deferral so the service isn't terminated
            serviceDeferral = taskInstance.GetDeferral();

            taskInstance.Canceled += OnTaskCanceled;

            var details = taskInstance.TriggerDetails as AppServiceTriggerDetails;
            connection = details.AppServiceConnection;

            //Listen for incoming app service requests
            connection.RequestReceived += OnRequestReceived;
        }
    }
}

Now, let’s look at the implementation of the RequestReceived handler. Once again, we take a deferral as soon as a request comes in, but this time it’s an AppServiceDeferral, which we’ll release when we’re done handling the incoming request. Again, we use a ValueSet to pass data to the app service. We also handle the Canceled event to gracefully handle cancellation or the app service’s task.

async void OnRequestReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args)
{
    //Get a deferral so we can use an awaitable API to respond to the message
    var messageDeferral = args.GetDeferral();

    try
    {
        var input = args.Request.Message;
        string command = input["Command"] as string;

        switch(command)
        {
            case "GetProductUnitCountForRegion":
                {
                    var productId = (int)input["ProductId"];
                    var regionId = (int)input["RegionId"];
                    var inventoryData = GetInventoryData(productId, regionId);
                    var result = new ValueSet();
                    result.Add("UnitCount", inventoryData.UnitCount);
                    result.Add("LastUpdated", inventoryData.LastUpdated.ToString());

                    await args.Request.SendResponseAsync(result);
                }
                break;

            //Other commands

            default:
                return;
        }
    }
    finally
    {
        //Complete the message deferral so the operating system knows we're done responding
        messageDeferral.Complete();
    }
}
private void OnTaskCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
{
    if (serviceDeferral != null)
    {
        //Complete the service deferral
        serviceDeferral.Complete();
        serviceDeferral = null;
    }
}

Next, the inventory app service needs to be published and given an endpoint. That process starts by updating the inventory app project so it has a reference to the Windows Runtime Component we just created. The Entry point is set to the fully qualified name of the InventoryServiceTask class; Name is the name we’ll use to identify the endpoint. This is the same name the app service’s clients will use to reach it.

To communicate with the inventory app service, clients also need the package family name of the inventory app. The easiest way to get this value is to use the Windows.ApplicationModel.Package.Current.Id.FamilyName API within the inventory app. You can do this by outputting this value to the debug window and picking it up from there.

3_appManifest

Calling the inventory app service

Finally, the inventory app service can be called from the sales app. In order to do that, a client can use the AppServiceConnection class. An instance of this class requires the name of the app service endpoint and the package family name of the package where the service resides.

Below, you see the code the sales app uses to connect to the app service. We need to plug the Package Family Name of the inventory app into the AppServiceConnection.PackageFamilyName property. Once we’re ready, we call the AppServiceConnection.OpenAsync method to open a connection. This returns a status upon completion, which indicates if the connection was successful or not.

When connected, the client sends the app service a set of values in a ValueSet using the AppServiceConnection.SendMessageAsync method. The Command property in the ValueSet is set to GetProductUnitCountForRegion, which we know the app service understands. SendMessageAsync returns a response containing the ValueSet sent back by the app service. Then, we parse out and display the UnitCount and LastUpdated values.

When the AppServiceConnection is disposed the corresponding app service background task is torn down. This is good practice to prevent the app service taking up memory and CPU that the app could use.

using (var connection = new AppServiceConnection())
{
    //Set up a new app service connection
    connection.AppServiceName = "com.contoso.inventoryservice";
    connection.PackageFamilyName = "Contoso.Inventory_876gvmnfevegr";

    AppServiceConnectionStatus status = await connection.OpenAsync();

    //The new connection opened successfully
    if (status != AppServiceConnectionStatus.Success)
    {
        return;
    }

    //Set up the inputs and send a message to the service
    var inputs = new ValueSet();
    inputs.Add("Command", "GetProductUnitCountForRegion");
    inputs.Add("ProductId",productId);
    inputs.Add("RegionId", regionId);

    AppServiceResponse response = await connection.SendMessageAsync(inputs);

    //If the service responded with success display the result and walk away
    if (response.Status == AppServiceResponseStatus.Success)
    {
        var unitCount = response.Message["UnitCount"] as string;
        var lastUpdated = response.Message["LastUpdated"] as string;

        //Display values from service
    }
}

Using drag and drop in Windows 10

Now let’s turn our attention to drag-and-drop functionality in Windows 10. Using drag and drop, you can transfer data between applications or within an application using a standard gesture, such as press-hold-and-pan with the finger or press-and-pan with a mouse or a stylus.

The drag source – the area where the drag gesture is triggered – provides the data to be transferred by filling a data package object that can contain standard data formats, including text, RTF, HTML, bitmaps, storage items, or custom data formats. The source also indicates the kind of operations it supports: copy, move, or link. When the pointer is released, the drop occurs. The drop target, which is the application or area underneath the pointer, processes the data package and returns the specific type of operation it performed.

The drag UI provides a visual indication of the type of drag-and-drop operation that’s taking place. This visual indicator is initially provided by the drag source, but can be changed by the drag target as the pointer moves there. Drag and drop allows data transfer between or within any kind of application, including Classic Windows applications, although this article focuses on the XAML API for drag and drop in UWP apps.

Making the UIElement draggable

You can any UIElement draggable by setting its CanDrag property to true in the XAML markup or the code-behind. The XAML framework handles gesture recognition and fires the DragStarting event, indicating the start of a drag operation. The application must configure the DataPackage, provided in the Data property of the DragStartingEventArgs, by filling its content and specifying the supported operations.


    ...
private void SourceGrid_DragStarting(UIElement sender, DragStartingEventArgs args)
{
    if (_fileSource == null)
    {
        args.Cancel = true;
    }
    else
    {
        args.Data.RequestedOperation = DataPackageOperation.Copy | DataPackageOperation.Move;
        args.Data.SetStorageItems(new IStorageItem[] { _fileSource });
    }
}

If you’d like to cancel the drag operation in the event handler, simply set the Cancel property of the DragStartingEventArgs parameter.

Once the user has released the pointer, the drag-and-drop operation is complete, and the source is notified through the DropCompleted event, which contains the DataPackageOperation returned by the target on which the user released the pointer (or DataPackageOperation.None if the pointer was released on a target that doesn’t accept the data, or if Cancel was pressed).

private void DropGrid_DropCompleted(UIElement sender, DropCompletedEventArgs args)
{
    if (args.DropResult == DataPackageOperation.Move)
    {
        // Move means that we should clear our own image
        Picture = null;
        _bitmapSource = null;
        _fileSource = null;
    }
}

Implementing a drop target

Any UIElement can be a drop target, as long as its AllowDrop property is set to true. During drag and drop, DragEnter, DragOver, DragLeave, and Drop events can be raised. Although these events already exist in Windows 8.1, the DragEventArgs class has been extended in Windows 10 to give applications access to all the features of modern drag and drop.

When handling a drag-and-drop event, the target application should first inspect the content of the DataPackage through the DataView property of the event argument. In most cases, checking for the presence of a data type is enough and can be done synchronously. In some cases, such as with files, the application might have to check the type of the available files before accepting or ignoring the DataPackage.

Once the target has determined if it can process the data, it must set the AcceptedOperation property of the DragEventArgs instance to allow the system to provide the right feedback to the user.

If the application returns DataTransferOperation.None—or an operation not accepted by the source—from an event handler, the drop will not take place even if the user releases the pointer over the target. Instead, the DragLeave event will be raised.

The application can handle either DragEnter or DragOver; the AcceptedOperation returned by DragEnter is kept if DragOver isn’t handled. As DragEnter is called only once, that’s the event you should use over DragOver for performance reasons. For nested targets, though, you’ll need to return the correct value from DragOver in case a parent target might override it.

A scenario to illustrate drag and drop could be a Photo Booth application with UI components that are both drag sources and drop targets. In the application, each photo placeholder checks for images in the DataPackage and routes the event only to the parent grid if there isn’t an image available. This allows the grid to accept text even if it is physically dropped on a placeholder.

Here is a basic implementation of a target accepting only Storage Items:


    ...
private void TargetGrid_DragEnter(object sender, DragEventArgs e)
{
    e.AcceptedOperation = e.DataView.Contains(StandardDataFormats.StorageItems)
        ? DataPackageOperation.Move : DataPackageOperation.None;
}

private async void TargetGrid_Drop(object sender, DragEventArgs e)
{
    if (e.DataView.Contains(StandardDataFormats.StorageItems))
    {
        var items = await e.DataView.GetStorageItemsAsync();
        e.AcceptedOperation = DataPackageOperation.Move;
    }
}

With the above scenario, we just scratched the surface of how you can use drag and drop in your app. For more advanced scenarios with drag and drop, head over to GitHub for the Drag and drop sample.

Wrapping up

That completes week 7 of our Windows 10 by 10 development series. Our DVLUP challenge this week is: Make Your Apps Cooperate with Cross-App Communication, be sure to check it out to claim points and XP for implementing the new cross-app communication features.

For the remaining three weeks in the series, we will cover more a few more general app development topics, such as design, performance, and security, so be sure to come back for that.

We’re looking forward to seeing how you can make your apps work better together, so do let us know what you think on Twitter via @WindowsDev and #Win10x10!

Additional Resources:

Special thanks to Arun Singh, Anna Pai and Alain Zanchetta for writing the content this article was based on and Stefan Wick, Hector Barbera, Howard Kapustein, Abdul Hadi Sheikh, Jill Bender and Jon Wiswall from the Windows engineering team for providing technical guidance and review.


Viewing all articles
Browse latest Browse all 10804

Trending Articles