Deep linking is “is the use of a hyperlink that links to a specific, generally searchable or indexed, piece of web content”. In the context of an app it’s making a link that opens the app with a specific task in mind.

For example: you want a button on your website that opens your app to send a message to a person. Or you need to handle push notifications. This is all done through deep linking!

Let's go!

To make things easy we're going to using the plugin uni_links by Evo Stamatov. Open your pubspec.yaml file and uni_links: ^0.2.0 under dependencies:. Run flutter packages get. We're now ready to use the plugin!

Throughout this post we'll be following Stamatov's instructions on how to use the plugin. If anything is unclear, refer to the official plugin page. I'm just trying to explain in as much detail as possible how I have managed to get it working.

Android

To begin with we need to define an intent. Open up android/app/src/main/AndroidManifest.xml and add the following:

<manifest ...>
    <application ...>
        <activity ...>
            <!-- other tags ...>
				 
                <!-- Deep Links -->
                      <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="[YOUR_SCHEME]"
                          android:host="[YOUR_HOST]" />
                      </intent-filter>

[YOUR_SCHEME] should be set to what the scheme of the link should be (for example, https). [YOUR_HOST] should be the address of the link (for example, peb.si).

iOS

To work with iOS it's best to use universal linking. In order to do this you need to add a file called apple-app-site-association to the root directory of your web server.

The file should contain something like the following:

{
    "applinks": {
        "apps": [],
        "details": [
            {
                "appID": "si.peb.app",
                "paths": [ "/news/*" ]
            },
        ]
    }
}

The appID should be the unique appID for your project. The paths should be an array containing each path (comma separated) that the app will use in order of priority.

Now modify ios/Runner/Runner.entitlements to look something like this:

<?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>
  <!-- ... other keys -->
  <key>com.apple.developer.associated-domains</key>
  <array>
    <string>applinks:[YOUR_HOST]</string>
  </array>
  <!-- ... other keys -->
</dict>
</plist>

For more information on Universal Linking, look at Apple's official guide.

Flutter code

Now, modify your app's main widget to import the plugin:

import 'package:uni_links/uni_links.dart';
import 'package:flutter/services.dart' show PlatformException;
import 'dart:async';

and include StreamSubscription _sub; before Widget build and this function after Widget build:

 Future<Null> initUniLinks() async {
    try {
      Uri initialLink = await getInitialUri();

      if (initialLink != null) {
     	handleLink(initialLink);
      }
    } on PlatformException {
      print("An exception occurred);
    }

    _sub = getUriLinksStream().listen((Uri uri) {
      handleLink(uri);
    });
  }

If it doesn't already exist, create an initState function, as below:

@override
void initState() {
	super.initState();
}

Add initUniLinks(); below super.initState();. This calls the above function and ensures that we're checking for a link on launch and setting up _sub to listen for links if the app is already open.

When a link that matches your scheme and host (e.g https://peb.si/) is opened on a device with the app installed it'll now open the app (or prompt the user on whether to open Chrome or the app, which they can select the app and "Always").

Finally, we need to handle the link. The above function calls handleLink which is shown below:

 void handleLink(Uri link) {
    var parameters = link.pathSegments;
	// we now have the parameters object to work with and can check the path segments!
	// e.g if we're sending https://peb.si/inbox/1 meaning 'open conversation #1' we can handle it like this:

	if (parameters[0] == "inbox" && parameters[1] != null) {
        Navigator.push(
            context,
            MaterialPageRoute(
                builder: (context) => ChatPage(
                    id: int.parse(parameters[1]))));
    }
}

If you're using parameters within the URL e.g ?a=inbox&id=1 then you can modify var parameters = link.queryParameters; and then access using parameters[index]. The above code would become...

if (parameters["a"] == "inbox" && parameters["id"] != null) {
	 Navigator.push(
            context,
            MaterialPageRoute(
                builder: (context) => ChatPage(
                    id: int.parse(parameters["id"]))));
}

Testing

You can test a link on a connected Android device by using this command:

adb shell 'am start -W -a android.intent.action.VIEW -c android.intent.category.BROWSABLE -d "scheme://host/path/subpath"'

Following the example code above, to open a Chat with id 1 the command would be:

adb shell 'am start -W -a android.intent.action.VIEW -c android.intent.category.BROWSABLE -d "https://peb.si/inbox/1"'

adb: command not found

If you get an error stating that adb isn't installed, follow this guide to ensure your path is set up correctly. As an example of ~/.bash_profile may look like if you have both flutter and adb set up, see below:

export PATH="$PATH:/Applications/SDK/flutter/bin"
export PATH=~/Library/Android/sdk/tools:$PATH
export PATH=~/Library/Android/sdk/platform-tools:$PATH

After modifying bash_profile you'll need to run this command: source ~/.bash_profile.