Let's imagine you have downloaded the YouTube mobile app. Your friend decided to share a funny cat video and sent you a link to the video in some messenger. You open that link on your smartphone, and you're almost instantly redirected to YouTube to show the video. You don't need to log in again in the mobile app or search for this video among millions of others on YouTube.

What happens if you don't have YouTube on your smartphone?

The correct answer is that you will be redirected to the AppStore or Google Play, depending on your device's operating system. Then, after downloading the YouTube app, the app should automatically open with the video that your friend sent you, unless, of course, something is broken.

In order to foresee this scenario in your mobile app, deep links come to the rescue.

Short Overview

Deep link is a hyperlink that redirects the user to a specific section of the mobile app or site. A deeplink is a URI (Uniform Resource Identifier).

URI structure
URI structure

This functionality optimizes the user experience by minimizing the need for repetitive actions and providing efficient navigation to the intended page with a reduced number of clicks.

Work scheme

When a user taps a link, it triggers a URL-based transition. This action prompts the referrer to interpret details like the operating system and the app type, subsequently directing the user to the precise in-app location or website.

In the event that the application is not installed on the smartphone, the transition will be directed to either Google Play or App Store, depending on the operating system of your mobile device: Android or iOS.

Deep link work scheme
Deep link work scheme

Types

In general, deep links can be divided into two types:

  • Basic deep links allow direct access to specific pages or sections within an app, making navigation smooth. If the app is not installed, users are redirected to the app store for download.
  • Deferred deep links work even if the app is not installed. Users are directed to the app store for installation and then taken to the desired screen or location within the app.

Examples

  • twitter:// - URI to launch Twitter mobile app
  • YouTube:// - URI to launch YouTube mobile app
  • fb://profile/33138223345 - URI to launch Facebook mobile app with further redirection to the profile page with code 33138223345

The URI format may vary across operating systems due to their unique handling of deep links. Android devices handle deep links with intents, iOS devices - through the openUrl application method.

Using eBay as an example, we can see the difference in URI formats depending on the operating system:
eBay://launch?itm=360703170135 - URI for iOS app
eBay://item/view?id=360703170135 - URI for Android app

Platform Specifics

iOS supports two types of deep linking:

  • Custom URL schemes
    The URI structure can accommodate any custom unique scheme and host.
    your_scheme://any_host
  • Universal Links
    These type allows to work only with https-scheme and specified host, permissions (entitlements) and hosted apple-app-site-association file.
    https://your_host
⚠️
Custom URL schemes only work in the installed app, as it does not allow you to make a redirect to App Store or website. 

To configure your application to work with deep links, you need to add a few lines to Info.plist. Taking Custom URL schemes as an example:

<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>custom.host</string>
<key>CFBundleURLSchemes</key>
<array>
<string>custom_scheme</string>
</array>
</dict>
</array> 

In this example, the deep link is custom_scheme://custom.host.

Android also supports two types of deep link handling, which are similar to their equivalents in iOS:

  • Deep links allow working with custom scheme with optional host. This is equivalent to Custom URL schemes in iOS.
  • App Links only work with http-scheme and specified host with hosted file. It is similar to Universal Links in iOS.

To configure deep link support in Android, you need to add a few lines to AndroidManifest.xml. Taking Deep links as an example:

<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="custom_scheme"
    android:host="my_app" />
</intent-filter> 

In this example, the deep link is custom_scheme://my_app.

Deep Linking in Flutter App

There are several ways to integrate deep links with Flutter. One option involves leveraging Platform channels, while another approach is to utilize third-party packages for this purpose.

Platform channels

You can read more about working with deep links using Platform channels in this article. In short:

  1. Set up the Platform channels on both Android and iOS sides.
  2. Prepare your Flutter app to interact with the Platform channels. Write Flutter code that can send and receive data via these channels.
  3. Implement a mechanism to catch deep links within your Flutter app. This can be done using any state management approach.
  4. Process the deep link in your Flutter code. Identify the paths contained in the deep link, and check if your application supports these paths.
  5. Handle path with named routes in Navigator 1.0 (routes parameter or onGenerateRoute) or Router widget in Navigator 2.0 (recommended way, more declarative). You can read about Navigator 2.0 handling here.
☝️
It's crucial to differentiate between a deep link that initiates the application and one intercepted while the application is already in operation.

To test deep linking on Android enter this command in a terminal or command line while emulator running:

adb shell am start -a android.intent.action.VIEW \
    -c android.intent.category.BROWSABLE \
    -d "scheme://myApp/page1/1" 

If you test this on iOS, enter the command in the terminal while simulator running:

xcrun simctl openurl booted customscheme://ourApp/page/2 
☝️
The scheme and URI host in the command must match the ones specified in AndroidManifest.xml/Info.plist.

Plugins

There are several plugins for working with deep links. This article covers uni_links and firebase_dynamic_links.

The most popular plugin in pub.dev for working with deep links. You do not need to write Platform channels to work with deep links. You just need to import this plugin, catch the deep links with the methods it provides and process them with Navigator 1.0 or 2.0.

Advantages of this plugin:

  1. Stable code base.
  2. Constantly updated.
  3. Clear documentation.
  4. Lots of online resources on how to work with this plugin.

The key disadvantage is lack of built-in functionality for redirecting users to app stores.

☝️
Use this package only when you need to process deep links in an installed application. Whether the application is open or closed by the user, this package does not provide a mechanism for redirecting to stores or to a web page.
💡
This plugin's limitation can be mitigated through a workaround involving your website's deep link processing. Let's say a user clicks on the URL. If the app is installed, he can choose "Open with application". If not, the site automatically launches in the browser. Then, depending on whether it's accessed through the app or browser (Chrome, Safari, etc.), either a custom app scheme (deep link) or a store redirect can be executed.

This plugin is the most functional for working with deep links in Flutter, it provides the ability to catch deep links both while the app is running, and in the background, and while the app is closed. If the app isn't installed, the deep link will navigate the user to the appropriate app store to download it. If the app is already installed, it will just open the app to the designated content.

Configuring Firebase and platforms

To use Firebase Dynamic Links, it is necessary to follow these steps to set up Firebase:

  1. Sign in to your Firebase account and select your project.
  2. On the left side menu, click on "Engage" to expand it, then select "Dynamic Links".
  3. Click on the "New Dynamic Link" button.
  4. Add URL prefix - the beginning of your URI, when you click on it the application will open.
☝️
The URL prefix is not the deep link that your application will handle. It is the link that the end user will see. The deep link for your application and the URL prefix can look completely different.

There are two options for URL prefixes: you can use your custom domain or a pre-created Firebase domain that ends with .page.link.

If you use a custom domain, you need to confirm that you are its owner.

Domain verification
Domain verification
  1. Next, you can create a deep link in Firebase and use it in the application. You can also create a deep link in the code, which will be described later in the article.
Creating a deep link
Creating a deep link

Configuring Android to work with firebase_dynamic_links does not require anything specific, just add a few lines in the AndoidManifest.xml as described above.

iOS setup requires an Apple developer account to integrate firebase_dynamic_links. Follow the steps below to set up Firebase and Xcode:

  1. In the Firebase console, open Project settings.
  2. Register App Store ID (possibly not yet existing) and Team ID.
Firebase iOS configuration
Firebase iOS configuration

Team ID can be found in the Membership tab on the website.

Membership details
Membership details 
  1. On the Apple Developer website, a provisioning profile must be created for your application, the Associated Domain option must be enabled.
  2. The next step is to open the ios folder with Xcode. Navigate to the Signing and Capabilities tab. Make sure that the Provisioning Profile and Team are correct. Add your URL prefix in the Associated Domains field, with applinks:.
Adding new domain
Adding new domain
  1. In the InfoURL Types tab, add a new URL type. The URL Schemes field must contain the bundle id of your application, in the Identifier field you can write the Bundle ID or something else.
URL settings
URL settings
  1. To verify that you have done everything correctly, you can insert a link like this in the search bar of your browser: https://yourApp.page.link/apple-app-site-association. The following Json should be displayed:
{
   "applinks":{
      "apps":[],
      "details":[
         {
            "appID":"Q8K34ZB576.com.sheepapps.reely",
            "paths":[
               "NOT /_/*",
               "/*"
            ]
         }
      ]
   }
}
  1. The next step is to add a few lines to Info.plist.
<?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>FirebaseDynamicLinksCustomDomains</key>
<array>
    <string>https://custom.domain.io/path1</string>
    <string>https://custom.domain.io/path2</string>
</array>

...other settings

</dict>
</plist>

For more details on configuring firebase_dynamic_links, you can refer to the official Firebase documentation, or check out this helpful Medium article.

Deep link can be created by two methods:

  • FirebaseDynamicLinks.buildLink
  • FirebaseDynamicLinks.buildShortLink

buildLink creates a long link that is hard for the user to read, while buildShortLink creates a short link like https://app.page.link/34566677.
Let's create a deep link like https://ourApp.com/page/?id=55.

static const String _androidPackageName = 'com.sheepapps.app';
static const String _iOSBundleId = 'com.sheepapps.app';
static const String _appStoreId = '1611717111';
static const String _uriPrefix = 'https://sheepapps.page.link'; //prefix in Firebase
static const String _deepLink = 'https://ourApp.com/page';
static const int _minimumVersion = 1;

static Future<Uri> createDeepLink({required String id}) async {
    final parameters = DynamicLinkParameters(
      uriPrefix: _uriPrefix,
      link: Uri.parse('$_deepLink/?id=$id'),
      androidParameters: const AndroidParameters(
        packageName: _androidPackageName,
        minimumVersion: _minimumVersion,
      ),
      iosParameters: IOSParameters(
        bundleId: _iOSBundleId,
        minimumVersion: _minimumVersion.toString(),
        appStoreId: _appStoreId,
      ),
    );
    final shortDynamicLink = await FirebaseDynamicLinks.instance.buildShortLink(parameters);
    final uri = shortDynamicLink.shortUrl;
    return uri;
} 

If you want an application running in the background to be able to catch deep links, you need to subscribe to the stream onLink provided by Firebase.

static void initDeepLinks() {
    FirebaseDynamicLinks.instance.onLink.listen((deepLinkData) {
      final deepLink = deepLinkData.link;
      log('Link received: $deepLink');
      var id = '';
      if (deepLink.queryParameters.containsKey('id')) {
        id = deepLink.queryParameters['id']!;
      }
      log('id from deep link: $id');
    }).onError((error) {
      log('onLink error:');
      log(error.message);
    });
} 

This method can be called in initState above the widget hierarchy.

If a user clicks a deep link while the app is fully closed (both in the foreground and background), use the FirebaseDynamicLinks.getInitialLink method to open and navigate to the content associated with the deep link.

Let's code a method that parses the deep link.

static Future<String?> retrieveDeepLinkId() async {
    String? id;
    try {
      final initialLink = await FirebaseDynamicLinks.instance.getInitialLink();
      final deepLink = initialLink?.link;

      if (deepLink != null) {
        if (deepLink.queryParameters.containsKey('id')) {
          id = deepLink.queryParameters['id'];
        }
      }
    } catch (e) {
      log(e.toString());
    }
    return id;
} 

It is best to call this method in main function and pass the required parameter in the constructor.

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  final id = await retrieveDeepLinkId();
  runApp(App(id: id));
} 

The methods mentioned above can be conveniently organized into an abstract static class, a singleton class, or any other appropriate class structure.

For testing the functionality of firebase_dynamic_links on iOS, it's necessary to use real device. This is due to the requirement of operating in a profile or release mode. On Android, deep links can be tested on an emulator. However, it's crucial to note that testing should be performed outside the debug mode.

To test an application launch via a deep link, you can create the deep link programmatically or in Firebase, and paste it into the emulator's search bar. This should open the application, or if the app isn't installed, Google Play/App Store should open. To test deep links while the app is in the background, keep the application in recent apps and follow the above steps.

To learn more about firebase_dynamic_links, check the Flutter-specific guide and the general Firebase documentation.

Summary

Deep linking provides a significant advantage by navigating users directly to specific sections of an app, enhancing their user experience by reducing redundant steps. Both iOS and Android have unique specifications when dealing with deep links, so a solid understanding of these specifics is key.

Flutter offers various ways to integrate deep linking, including the use of Platform channels and several third-party packages. Notably, firebase_dynamic_links provides a wide range of functionalities. It can catch deep links while the app is running, in the background, or even when the app is closed. If the app isn't installed, firebase_dynamic_links will guide the user to the appropriate app store for download. Setting up Firebase Dynamic Links requires following some critical steps, like choosing URL prefixes and confirming domain ownership.

For scenarios where redirection to the app store isn't necessary, uni_links can be a suitable choice.

All these tools, when properly leveraged, can help optimize navigation in your Flutter app, thus boosting user engagement and experience.

Share this post