Handling Different Types of Deep Links in Flutter

Deep linking is a crucial feature in modern mobile applications, allowing users to navigate directly to specific content within an app from external sources such as web pages, email links, or social media posts. Properly handling different types of deep links ensures a seamless user experience and can significantly boost user engagement. In Flutter, handling deep links involves configuring your app to respond to specific URI schemes and routes, enabling navigation to the intended destination.

What is Deep Linking?

Deep linking refers to the mechanism of using a uniform resource identifier (URI) that links to a specific location within a mobile app rather than simply launching the app’s home screen. Deep links can be categorized into:

  • Custom Scheme Deep Links: Utilize a custom URI scheme (e.g., myapp://) to open the app and navigate to a specific location.
  • Universal Links (Android App Links and iOS Universal Links): Standard web links (https://) that also open the app directly if it is installed, otherwise, they open in a web browser.

Why Handle Different Types of Deep Links?

  • Enhanced User Experience: Provides seamless navigation to relevant content.
  • Increased User Engagement: Drives users directly to specific features, reducing friction.
  • Better App Promotion: Enables precise campaign tracking and personalized experiences.

How to Handle Different Types of Deep Links in Flutter

Handling deep links in Flutter involves setting up both custom scheme and universal links to cater to a wider range of use cases.

Step 1: Set Up Custom Scheme Deep Links

Custom scheme deep links require configuring your Flutter app to respond to a specific URI scheme.

1.1. Android Configuration

In your AndroidManifest.xml file (located in android/app/src/main/), add an intent filter to the activity that should handle the deep link:

<activity
    android:name=".MainActivity"
    android:launchMode="singleTask"> <!-- Add this line -->
    <!-- ... other configurations ... -->
    <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="open" /> <!-- Your custom scheme and host -->
    </intent-filter>
</activity>

Explanation:

  • android:launchMode="singleTask" ensures that only one instance of the activity is created.
  • <data android:scheme="myapp" android:host="open" /> specifies that this activity can handle URIs with the scheme myapp and host open (e.g., myapp://open?param1=value1).
1.2. iOS Configuration

In your Info.plist file (located in ios/Runner/), add a CFBundleURLTypes array to define your custom URL scheme:

<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleURLName</key>
        <string>com.example.myapp</string> <!-- Bundle identifier -->
        <key>CFBundleURLSchemes</key>
        <array>
            <string>myapp</string> <!-- Your custom scheme -->
        </array>
    </dict>
</array>

Explanation:

  • CFBundleURLName is a unique identifier for your app.
  • CFBundleURLSchemes is an array that lists the custom URL schemes your app can handle.

Step 2: Implement Universal Links

Universal links provide a more secure and standardized way to handle deep links using HTTPS.

2.1. Android App Links

To set up Android App Links, you need to:

  1. Associate Your App with Your Website:
  2. Create a assetlinks.json file and host it at /.well-known/assetlinks.json on your domain. This file verifies that your website is associated with your Android app.

    Example assetlinks.json file:

    [
      {
        "relation": ["delegate_permission/common.handle_all_urls"],
        "target": {
          "namespace": "android_app",
          "package_name": "com.example.myapp",  <!-- Your app package name -->
          "sha256_cert_fingerprints": ["YOUR_SHA256_CERT_FINGERPRINT"]
        }
      }
    ]

    To generate the SHA256 certificate fingerprint, use the following command:

    keytool -list -v -keystore mystore.jks -alias myalias
  3. Update AndroidManifest.xml:
  4. Add an intent filter to your activity to handle HTTPS URLs, and enable auto-verification:

    <activity
        android:name=".MainActivity"
        android:launchMode="singleTask"
        android:autoVerify="true"> <!-- Add autoVerify -->
        <!-- ... other configurations ... -->
        <intent-filter android:autoVerify="true"> <!-- Ensure autoVerify is set to 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="www.example.com" /> <!-- Your domain -->
        </intent-filter>
    </activity>

    Explanation:

    • android:autoVerify="true" enables automatic verification of the App Links.
    • <data android:scheme="https" android:host="www.example.com" /> specifies that this activity can handle HTTPS URLs from your domain.
2.2. iOS Universal Links

To set up iOS Universal Links:

  1. Associate Your App with Your Website:
  2. Create an apple-app-site-association file and host it at /.well-known/apple-app-site-association on your domain. This file verifies that your website is associated with your iOS app.

    Example apple-app-site-association file:

    {
      "applinks": {
        "apps": [],
        "details": [
          {
            "appID": "YOUR_TEAM_ID.com.example.myapp",  <!-- Your Team ID and Bundle Identifier -->
            "paths": ["*"]  <!-- Paths your app can handle -->
          }
        ]
      }
    }
  3. Enable Associated Domains Entitlement:
  4. In Xcode, enable the Associated Domains entitlement in your app’s target settings. Add the domain to the list of associated domains:

    applinks:www.example.com

Step 3: Handling Deep Links in Flutter Code

Once your app is configured to handle deep links, you need to implement the logic to process the incoming links and navigate the user accordingly.

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

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

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

class _MyAppState extends State<MyApp> {
  String? _initialLink;
  String? _latestLink;

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

  Future<void> _initDeepLinkHandling() async {
    // Get the initial link, if any.
    _initialLink = await getInitialLink();

    // Handle the initial link.
    if (_initialLink != null) {
      _handleDeepLink(_initialLink!);
    }

    // Subscribe to link stream to handle subsequent deep links.
    linkStream.listen((String? link) {
      setState(() {
        _latestLink = link ?? 'Unknown';
        if (link != null) {
          _handleDeepLink(link);
        }
      });
    }, onError: (err) {
      print('Error receiving URI: $err');
    });
  }

  void _handleDeepLink(String link) {
    final uri = Uri.parse(link);

    if (uri.scheme == 'myapp') {
      // Handle custom scheme deep link.
      if (uri.host == 'open') {
        final param1 = uri.queryParameters['param1'];
        // Navigate to a specific screen based on param1.
        Navigator.push(
          context,
          MaterialPageRoute(
            builder: (context) => DeepLinkScreen(param1: param1 ?? 'No parameter'),
          ),
        );
      }
    } else if (uri.scheme == 'https') {
      // Handle universal link.
      if (uri.host == 'www.example.com') {
        final path = uri.path;
        // Navigate to a specific screen based on the path.
        Navigator.push(
          context,
          MaterialPageRoute(
            builder: (context) => UniversalLinkScreen(path: path),
          ),
        );
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Deep Link Handling'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text('Initial Link: $_initialLink'),
              Text('Latest Link: $_latestLink'),
              ElevatedButton(
                onPressed: () {
                  // Manually open a deep link for testing.
                  _handleDeepLink('myapp://open?param1=test_value');
                },
                child: const Text('Open Deep Link'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

class DeepLinkScreen extends StatelessWidget {
  final String param1;

  const DeepLinkScreen({Key? key, required this.param1}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Deep Link Screen'),
      ),
      body: Center(
        child: Text('Parameter Value: $param1'),
      ),
    );
  }
}

class UniversalLinkScreen extends StatelessWidget {
  final String path;

  const UniversalLinkScreen({Key? key, required this.path}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Universal Link Screen'),
      ),
      body: Center(
        child: Text('Path: $path'),
      ),
    );
  }
}

Explanation:

  • Import uni_links Package: Import the uni_links package for deep link handling. Add uni_links: ^0.5.1 to your pubspec.yaml and run flutter pub get.
  • Initialize Deep Link Handling: In initState, call _initDeepLinkHandling() to initialize deep link processing.
  • Get Initial Link: Use getInitialLink() to retrieve the initial deep link that opened the app.
  • Handle Link Stream: Subscribe to linkStream to listen for incoming deep links and handle them in the _handleDeepLink() method.
  • Parse URI: Use Uri.parse() to parse the deep link string into a Uri object.
  • Handle Custom Scheme and Universal Links: Check the scheme and host properties of the Uri object to determine the type of deep link, and navigate accordingly.

Conclusion

Handling different types of deep links is essential for creating a seamless user experience in Flutter apps. By setting up both custom scheme and universal links, and properly handling them in your Flutter code, you can ensure that users are directed to the correct location within your app from various external sources. This leads to increased user engagement and a more satisfying app experience.