Handling Different Types of Deep Links (URL Schemes, App Links, Universal Links) in Flutter

Deep linking is a crucial feature for mobile applications, allowing users to navigate directly to specific content within the app from external sources such as web pages, emails, or other apps. In Flutter, handling different types of deep links involves configuring your app to respond to URL schemes (also known as custom schemes), Android App Links, and iOS Universal Links. This guide provides a comprehensive overview of implementing deep linking in Flutter apps to ensure a seamless user experience.

Understanding Deep Links

Before diving into the implementation, let’s understand the different types of deep links:

  • URL Schemes (Custom Schemes): These are custom URL prefixes that an app registers to handle. For example, myapp://content/123.
  • Android App Links: Standard HTTP URLs that point to both your website and your app. When a user clicks on an App Link, Android will directly open your app (if installed) without showing the disambiguation dialog.
  • iOS Universal Links: Similar to Android App Links, Universal Links use standard HTTP/HTTPS URLs that link to both your website and your app on iOS.

Setting Up URL Schemes in Flutter

URL schemes are the simplest form of deep linking. Here’s how to configure them:

Step 1: Update Native Project Configurations

Android (AndroidManifest.xml)

Add an intent filter to your AndroidManifest.xml (located in android/app/src/main/) inside the <activity> tag:

<activity
    android:name=".MainActivity"
    android:launchMode="singleTop">
    <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="myapp" android:host="content" />
    </intent-filter>
</activity>

In this example:

  • android:scheme="myapp" defines the custom scheme.
  • android:host="content" defines the host.
iOS (Info.plist)

Add the following to your Info.plist (located in ios/Runner/):

<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>myapp</string>
        </array>
        <key>CFBundleURLName</key>
        <string>com.example.myapp</string> <!-- Replace with your app's bundle identifier -->
    </dict>
</array>

Replace com.example.myapp with your app’s bundle identifier.

Step 2: Implement Flutter Code to Handle the Deep Link

Use the uni_links package to listen for incoming deep links.

Add the uni_links package to your pubspec.yaml:

dependencies:
  flutter:
    sdk: flutter
  uni_links: ^0.5.1

Now, implement the code to listen for deep links in your Flutter app:

import 'package:flutter/material.dart';
import 'package:uni_links/uni_links.dart';
import 'package:flutter/services.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  String? _latestLink = 'Unknown';

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

  Future<void> _initUniLinks() async {
    try {
      final initialLink = await getInitialLink();
      if (initialLink != null) {
        _latestLink = initialLink;
      }

      // Attach a listener to the stream
      linkStream.listen((String? link) {
        setState(() {
          _latestLink = link ?? 'Unknown';
        });
      }, onError: (err) {
        setState(() {
          _latestLink = 'Failed to get latest link: $err.';
        });
      });
    } on PlatformException {
      setState(() {
        _latestLink = 'Failed to get initial link.';
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Deep Linking in Flutter'),
        ),
        body: Center(
          child: Text('Latest deep link: $_latestLink'),
        ),
      ),
    );
  }
}

In this code:

  • getInitialLink() fetches the link used to open the app initially.
  • linkStream listens for subsequent deep links while the app is running.
  • The UI updates to display the latest deep link.

Setting Up Android App Links

Step 1: Configure AssetLinks File

Create a assetlinks.json file and place it in the .well-known directory of your domain (e.g., https://yourdomain.com/.well-known/assetlinks.json). Replace yourdomain.com and package name with your actual domain and app’s package name:

[
  {
    "relation": ["delegate_permission/common.handle_all_urls"],
    "target": {
      "namespace": "android_app",
      "package_name": "com.example.myapp",
      "sha256_cert_fingerprints": [
        "YOUR_SHA256_FINGERPRINT"
      ]
    }
  }
]

To get your SHA256 certificate fingerprint, use the following command:

keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey

Replace ~/.android/debug.keystore with the path to your keystore file if necessary. Also ensure you get the SHA256 from your release keystore when publishing the app.

Step 2: Verify the assetlinks.json File

Ensure that the assetlinks.json file is accessible over HTTPS. You can verify this by opening https://yourdomain.com/.well-known/assetlinks.json in a browser. If the file is not accessible, Android will not verify the App Links.

Step 3: Update AndroidManifest.xml

Add the android:autoVerify="true" attribute to your intent filter in AndroidManifest.xml and include the android:host attribute:

<activity
    android:name=".MainActivity"
    android:launchMode="singleTop">
    <intent-filter android:autoVerify="true">
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="https" android:host="yourdomain.com" android:pathPrefix="/content" />
    </intent-filter>
</activity>

Here, android:scheme="https" and android:host="yourdomain.com" define the App Link parameters. Make sure to include pathPrefix or other attributes to define the exact URLs your app can handle. android:autoVerify=”true” tells the system to verify that the app is approved to handle the URLs

Step 4: Handle the App Link in Flutter

Reuse the uni_links package to handle the incoming App Links in Flutter, as shown in the URL Schemes setup. The key is that your Flutter code will receive the full HTTPS URL, which you can then parse and use to navigate within your app.

Setting Up iOS Universal Links

Step 1: Configure Associated Domains

In your Xcode project, go to the Signing & Capabilities tab. Add the “Associated Domains” capability, and add an entry for each domain that you want to support:

applinks:yourdomain.com

Ensure that you prefix applinks: to your domain.

Step 2: Create and Upload apple-app-site-association File

Create an apple-app-site-association (AASA) file and place it in the .well-known directory or root directory of your domain (e.g., https://yourdomain.com/.well-known/apple-app-site-association or https://yourdomain.com/apple-app-site-association). The file must be served over HTTPS without redirects and with the application/json content type. Here’s an example of the AASA file content:

{
  "applinks": {
    "apps": [],
    "details": [
      {
        "appID": "YOUR_TEAM_ID.com.example.myapp",
        "paths": ["/content/*"]
      }
    ]
  }
}

Replace YOUR_TEAM_ID with your team ID and com.example.myapp with your app’s bundle identifier.

Step 3: Handle the Universal Link in Flutter

Use the uni_links package to handle incoming Universal Links in Flutter, just as you did with URL schemes and App Links. The same linkStream will emit the Universal Link URL, which your app can then use to navigate accordingly.

Additional Tips and Best Practices

  • Error Handling: Implement robust error handling to gracefully handle cases where deep links are malformed or lead to invalid content.
  • Testing: Thoroughly test deep linking on both Android and iOS devices, covering different scenarios, such as the app being in the background or not installed.
  • Navigation: Use a consistent navigation pattern in your Flutter app to ensure users are taken to the correct destination when a deep link is opened.
  • Deferred Deep Linking: Consider using services like Adjust or Branch for deferred deep linking, which handles the case where the app is not yet installed and navigates the user to the correct content after installation.

Conclusion

Handling different types of deep links in Flutter requires careful configuration of both the native Android and iOS projects, as well as Flutter code for listening to incoming links. By implementing URL schemes, Android App Links, and iOS Universal Links, you can create a seamless and engaging user experience, allowing users to navigate directly to relevant content within your app from various sources. Leverage the uni_links package for easy handling of deep links in Flutter, and remember to test thoroughly to ensure everything works as expected.