Deep linking for NET8 MAUI

maui deep linking

I am building an app using NET8 MAUI and I want the users have the ability to open a link in email or on a website that would open this mobile app on iOS and Android or a desktop application for Windows and macCatalyst. This is also known as Deep Linking.

There are 2 different ways to accomplish this: one is at a high level there is a method also known as Universal Links. The other way, that I am more used to, is where you use a custom HTTP schema.

The high-level difference is that Universal Links use the http:// or https:// format of the URL, whereas the custom schema method uses your schema as in myschema://.

The downside of using the Universal Link method is that you need to have a website running that can accept incoming requests for a configuration file. I didn’t want that requirement for this particular app, so I decided to use the custom schema method.

When I started searching for how to implement this method, I found so many articles that it was overwhelming, in the end, I was able to get it to work, but only after much blog reading, video watching, and trial and error.

Android

First, I have to add the following method to your MainActivity.cs file:

protected override void OnCreate(Bundle savedInstanceState)
{
    base.OnCreate(savedInstanceState);

    var uri = Intent?.Data;
    if (uri != null)
    {
        // here your code
    }
}

This will detect that the app was started or resumed using a custom URL schema. The URI value is the URL used to launch the app. The parameters variable will contain any query parameters on the URL that you can use to do specific things.

Add the following attribute just above your MainActivity.cs file declaration of your MainActivity class:

[IntentFilter(new[] { Intent.ActionView },
Categories = new[]
{
    Intent.ActionView,
    Intent.CategoryDefault,
    Intent.CategoryBrowsable,
},
DataScheme = "mycustomschema", DataHost = "", DataPathPrefix = "/")]

The DataSchema must match what will be the beginning part of the URL used to launch the app. In the above code, the URL would be mycustomschema://. The DataPathPrefix can require something after the // in order to match and launch your app.

Change AndroidManifest

Now, the final step is to change the AndroidManifest.xml. In the Google documentation, I found these settings

<activity
    android:name=".MyMapActivity"
    android:exported="true"
    ...>
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="mycustomschema" />
    </intent-filter>
</activity>

What is impossible to find is where to place this part of XML. Here is the solution: place the code in the application tag like in the following example (I remove all the useless tags and properties)

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.languageinuse.app" android:versionCode="24" android:versionName="dev">
	<application>
		<!-- Deep linking -->
		<activity android:name=".MainActivity" android:exported="true">
			<intent-filter>
				<action android:name="android.intent.action.VIEW" />
				<category android:name="android.intent.category.DEFAULT" />
				<category android:name="android.intent.category.BROWSABLE" />
				<data android:scheme="mycustomschema" />
			</intent-filter>
		</activity>
	</application>
	<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <!-- ... --->
</manifest>

Create Your Assets.json for Android

Then, I have to add the new schema in the Google Play Console. In the Deep Links section, you can see a link to add a new schema.

- Deep linking for NET8 MAUI

You need to create a file to be placed on your website to help Google prove that you own the website and you are allowing the app to be launched by visiting that URL. Google refers to your assets.json as your digital asset link, which is a public verifiable statement about which page on a website is associated with an app.

You place this file in /.well-known/assetlinks.json on your root domain. Important: This must be served over HTTPS.

The assets.json file looks similar to the one below.

[{
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target": {
    "namespace": "android_app",
    "package_name": "com.mycompany.myapp",
    "sha256_cert_fingerprints":
    ["16:7E:F1:D1:56:33:06:50:D8:AA:B9:95:F2:43:22:55:16:D0:83:42:1E:1D:B2:A8:8A:04:57:32:3F:CF:44:E5"]
  }
}]

iOS

Now, it is the turn of iOS. A lot of posts are recommended to add the following method override to your AppDelegate.cs file:

[Export("application:continueUserActivity:restorationHandler:")]
public override bool ContinueUserActivity(UIKit.UIApplication application, NSUserActivity userActivity, UIKit.UIApplicationRestorationHandler completionHandler)
{
    if (userActivity != null)
    {
        string url = userActivity.WebPageUrl?.ToString();
        // use the url to extract any query parameters with values if needed
    }

    return true;
}

This code doesn’t do anything in my implementation but it could be useful in a Universal Link implementation. What is really needed is the following function:

[Export("application:openURL:options:")]
public override Boolean OpenUrl(UIApplication app, NSUrl url, NSDictionary options)
{
    if (!String.IsNullOrEmpty(url.AbsoluteString) && url.AbsoluteString.Contains("mycustomschema"))
    {
        if (url.AbsoluteString.Contains("/addword"))
        {
            Shell.Current.GoToAsync($"{nameof(WordEdit)}?{url.Query}", true);
        }
    }

    return true;
}

With this code, the application receives the call from the custom schema (for example from a website link) and reads the url. Then, I can check what page the URL contains and then open it.

Add the Entitlements.plist

Create an Entitlements.plist file in the Platforms/iOS folder with the following content:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
    <dict>
        <key>com.apple.developer.associated-domains</key>
        <array>
            <string>applinks:mycustomschema://example.test.com</string>
        </array>
    </dict>
</plist>

Lastly, add the following lines to your info.plist file:

<key>com.apple.developer.associated-domains</key>
<string>CustomDomainHere.com</string>
<string>com.exampleapp.test</string>
<key>CFBundleURLSchemes</key>
<array>
    <string>mycustomschema</string>
</array>

NOTE: The com.exampleapp.test bundle Id used in this example must be changed to match your app’s actual bundle Id, and the domain used must be changed to match your custom domain.

That’s all you have to do. I still can’t believe how much time I spent searching and trying things to end up with such a simple result. Every site or tutorial I found had different parts of the puzzle, forcing me to stitch it together piece by piece. Here it all is in one place- and I hope it will help you to avoid the same headaches I had to go through to get here.

Associate a site to the app

For Apple, we must create a json file named apple-app-site-association.json in a root folder in your web app. Add the following code to the file:

{
  "applinks": {
    "apps": [],
    "details": [
      {
        "appID": "",
        "paths": [
          "*"
        ]
      }
    ]
  }
}

The appID is {teamId}.{your app bundle id}, to get a teamId you will see it in Apple Developer (login and you will see it in the top right). To get bundleId, open your MAUI then double click on your project name see ApplicationId element it’s your app bundleId. For example:

  • teamId = XX92LZLAJL
  • app bundleId = com.companyname.mobileapp
  • appId = XX92LZLAJL.com.companyname.mobileapp.

Add associate domain

On https://developer.apple.com/account/resources/identifiers click on your app. Then enable Associated Domains, then click Save and Download a file. Please note that if you used it before you have to replace that file with the newest.

Apple Developer portal: Certificates, Identifiers & Profiles - Deep linking for NET8 MAUI
Apple Developer portal: Certificates, Identifiers & Profiles

Add XML file named Entitlements.plist in Platforms/iOS folder, and then add the following code in this file like below:

<key>com.apple.developer.associated-domains</key>
<array>
    <string>applinks:yourdomain.com</string>
</array>

Remember that whenever you change any capability in the Apple Developer portal, the provisioning profiles must be recreated.

Apple Developer portal: modify app capabilities - Deep linking for NET8 MAUI
Apple Developer portal: modify app capabilities

Windows

The configuration for Windows seems quite easy. In the Package.appxmanifest open Declarations add a new Protocol selected from the Available Declarations.

Package.appxmanifest declarations - Deep linking for NET8 MAUI
Package.appxmanifest declarations

Here, in the Name, add the schema you want to use to open your application. Then, open the App.xaml.cs. In the OnLaunched, I have to read the arguments and verify if the call is coming from a protocol call. If yes, I can check the URI and then redirect the application to the right page.

protected override async void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
{
    base.OnLaunched(args);

    var actEventArgs = Microsoft.Windows.AppLifecycle.AppInstance.GetCurrent().GetActivatedEventArgs();
    if (actEventArgs.Kind == ExtendedActivationKind.Protocol)
    {
        var d = actEventArgs.Data as IProtocolActivatedEventArgs;
        if (d != null)
        {
            var uri = d.Uri;
            var uriString = uri.AbsoluteUri;

            if (!string.IsNullOrEmpty(uriString) && uriString.Contains("liu"))
            {
                if (uriString.Contains("/addword"))
                {
                    await Shell.Current.GoToAsync($"{nameof(WordEdit)}{uri.Query}", true);
                }
            }
        }
    }
}

As you can see, I explicitly reference Microsoft.UI.Xaml.LaunchActivatedEventArgs because I want to add in this application a background service to receive push notifications also when the application is not open. This is a work in progress.

Wrap up

In this post Deep linking for NET8 MAUI, I show what I have done to implement deep linking in my MAUI application (here you have more details about it). I hope this can help someone else in not wasting time to find a solution as I did. Let me know what you think about it in the comment below or the Forum. If you want to support my work, please consider sponsoring me on GitHub.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.