Adding shortcuts to your app's icon

A brief intro

In Windows 95, users were introduced to the paradigm of a shortcut context menus. These menus were limited, but provided the user with additional actions that could be achieved by other means, but took more effort. As Windows advanced, so did the functionality found in the context menus with extensibility options allowing third-parties to integrate custom actions too.

Fast forward a few years to the release of the iPhone 6s and the introduction of a feature called 3D Touch which brought shortcut context menus to the smartphone masses. Last year in 2017, Android also thought this may be a nice to have, and added the feature to their Nougat 7.1 release.

As developers, we can utilize this feature to provide the user with quick actions into our apps that enhance the UX. For example, many browsers like Chrome, Edge & Safari have shortcuts to open a new incognito tab and the Shazam app includes the ability to initiate a search straight from the home screen, saving crucial seconds when you're trying to identify that snippet of a song.

App shortcuts can be static, where they're declared as part of the app's manifest, or can be added dynamically through code. This post is here to demonstrate how the latter can be done within a Xamarin Forms app, using the AppShortcuts Plugin.

Adding the plugin to your app

The AppShortcuts plugin can be installed via Nuget by searching for Plugin.AppShortcuts in the Nuget Explorer or via the Package Manager console with the command: PM> Install-Package Plugin.AppShortcuts

Install the plugin into the project where you'll be implementing the code and also each platform specific project.

Once installed, there is an extra step required for Android. In the MainActivity.OnCreate() before the LoadApplication() call, make a call to:

CrossAppShortcuts.Current.Init();

Adding a shortcut

To add a shortcut to the app's icon, first you need to create a new instance of the Shortcut class and populate it with the information you wish to pin.

using Plugin.AppShortcuts;
using Plugin.AppShortcuts.Icons;

...

var shortcut = new Shortcut()
{
    Label = "Shortcut 1",
    Description = "My shortcut",
    Icon = new FavoriteIcon(),
    Uri = "asc://AppShortcutsApp/Detail/shortcut_1_id"
};

The plugin has a number of supplied icons built in that you can use, or you can provide your own by using the CustomIcon class

Once you have your shorcut object, to add it to the App Icons shortcuts you call:

await CrossAppShortcuts.Current.AddShortcut(shortcut);

Note; you can add as any shortcuts as you wish, but only four will ever be displayed on the homepage. This is not tracked by the plugin.

Handling deeplinks when a shortcut's clicked

To provide a consistent UX when the user clicks a shortcut to launch the app, the code to handle what happens, should be done in the App.cs class' OnAppLinkReceived() method. This passes in a Uri from the platform as a parameter. In order for each platform to pass the uri to this method, some additional work is required.

protected override void OnAppLinkRequestReceived(Uri uri)
{
    var itemId = uri.ToString().Replace("asc://AppShortcutsApp/Detail/", "");
	var item = Repository.Instance.Items.FirstOrDefault(m => m.Id.Equals(itemId));

	if (item != null)
		MainPage.Navigation.PushAsync(new DetailsPage(item));
	else
		base.OnAppLinkRequestReceived(uri);
}

Android

In order for Android to launch your app from a shortcut, it needs to be told that the shortcut's uri can be handled by the app. This is done by adding an IntentFilter attribute to the MainActivity.

[Activity(Label = "AppShortcuts Sample",
          Theme = "@style/MainTheme")]
[IntentFilter(new[] { Intent.ActionView },
              Categories = new[] { Intent.CategoryDefault },
              DataScheme = "asc",
              DataHost = "AppShortcutsApp",
              AutoVerify = true)]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
    ...
    

This is all that needs to be done for Android, the FormsAppCompatActivity will look to see if the app launched with a Uri and pass this through to the App class.

iOS

iOS will automatically launch the app from a shortcut, however, the FormsApplicationDelegate is not setup to pass the data through to the App class. To do this, the AppDelegate's PerformActionForShortcutItem() needs to be overridden.

public override void PerformActionForShortcutItem(UIApplication application, UIApplicationShortcutItem shortcutItem, UIOperationHandler completionHandler)
{
    var uri = Plugin.AppShortcuts.iOS.ArgumentsHelper.GetUriFromApplicationShortcutItem(shortcutItem);
    if (uri != null)
    {
        Xamarin.Forms.Application.Current.SendOnAppLinkRequestReceived(uri);
    }
}

UWP

Similar to iOS, a UWP app will launch the app when the shortcut is clicked, but needs some additional setup to pass the Uri back to the Forms App class.

A restriction of UWP is that its implementation of shortcuts doesn't provide the ability to set multiple properties. In order for The Plugin to overcome this, the shortcut's ID and Uri are serialized into a JSON object.

Firstly, in the UWP MainPage call the helper method to split the NavigationArgs and pass this to the Forms App. Then override the OnNavigatedTo() to call it. Your UWP MainPage should look similar to this:

public sealed partial class MainPage : WindowsPage
{
    public MainPage()
    {
        this.InitializeComponent();
        LoadApplication(new App());
    }

    protected override void OnNavigatedTo(NavigationEventArgs e)
    {
        base.OnNavigatedTo(e);
        OnLaunchedEvent(e.Parameter.ToString());
    }

        public void OnLaunchedEvent(string arguments)
        {
            if (!string.IsNullOrEmpty(arguments))
            {
                var argsUri = JumplistArgumentsHelper.GetUriFromJumplistArguments(arguments);
                Xamarin.Forms.Application.Current.SendOnAppLinkRequestReceived(argsUri);
            }
        }
    }

Next, in the UWP App class, modify the OnLaunched() method so that if rootFrame.Content is not null, then it calls the MainPage's OnLaunchedEvent() directly:

protected override void OnLaunched(LaunchActivatedEventArgs e)
{
    ...

    if (rootFrame.Content == null)
    {
        rootFrame.Navigate(typeof(MainPage), e.Arguments);
    }
    else
    {
        var page = rootFrame.Content as MainPage;
        page?.OnLaunchedEvent(e.Arguments);
    }
    Window.Current.Activate();
}

To see how this plugin works within the context of a full application, a sample can be found on the Plugin's GitHub repo, along with full documentation of the library's features.