Using Deep Linking for Seamless Navigation to Specific Content in Flutter

Deep linking is a powerful technique that enables users to navigate directly to specific content within an application from an external source, such as a web page, an email, or even another app. In Flutter, deep linking can significantly enhance user experience by providing seamless navigation and context-aware transitions to relevant content.

What is Deep Linking?

Deep linking refers to the process 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. These links are recognized by the operating system, which then opens the associated application and directs the user to the specified content.

Why Use Deep Linking in Flutter?

  • Improved User Experience: Navigates users directly to the content they need, reducing friction.
  • Contextual Navigation: Provides relevant information based on the deep link’s parameters.
  • Enhanced Marketing Campaigns: Tracks the effectiveness of campaigns by analyzing how users interact with deep links.
  • Seamless Integration: Connects your app with other platforms and applications, expanding functionality.

Types of Deep Linking

  • URI Schemes: Use custom schemes like myapp:// to open the app and navigate to a specific route.
  • App Links (Android): Standard HTTP URLs that link to specific content and are verified by the system to ensure the app owns the domain.
  • Universal Links (iOS): Similar to App Links on Android, but for iOS, using standard HTTP URLs verified through a domain association file.

How to Implement Deep Linking in Flutter

To implement deep linking in Flutter, follow these steps:

Step 1: Configure the Flutter App

First, configure your Flutter app to handle incoming deep links by modifying the native platform files (Android and iOS).

Android Configuration

Open the AndroidManifest.xml file located in android/app/src/main and add an intent filter to the Activity that should handle deep links. Typically, this is the MainActivity.


    <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" />
    </intent-filter>
    <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="https" android:host="www.example.com" android:pathPrefix="/products" />
    </intent-filter>
</activity>

Explanation:

  • The first intent-filter uses a URI scheme (myapp://open) to handle deep links.
  • The second intent-filter uses HTTPS (https://www.example.com/products) to handle web links.
  • The android:launchMode="singleTask" attribute ensures that a new instance of the activity isn’t created if the app is already running.
iOS Configuration

In the ios/Runner/Info.plist file, add the CFBundleURLTypes array to handle custom URI schemes:

<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>myapp</string>
        </array>
        <key>CFBundleURLName</key>
        <string>com.example.myapp</string>
    </dict>
</array>

<key>LSApplicationQueriesSchemes</key>
<array>
  <string>https</string>
</array>

To handle Universal Links (HTTP/HTTPS), you must associate your app with your website. This involves creating an apple-app-site-association file and placing it in the .well-known directory of your domain. Also, enable Associated Domains entitlement in Xcode.

For details about associating your website, refer to Apple’s documentation: Associated Domains Entitlement.

Step 2: Install the uni_links Package

Use the uni_links package to listen for incoming deep links in your Flutter app. Add it to your pubspec.yaml file:

dependencies:
  flutter:
    sdk: flutter
  uni_links: ^0.5.1

Then, run flutter pub get to install the package.

Step 3: Implement Deep Link Handling in Flutter

In your Flutter app, use the uni_links package to listen for incoming links and navigate accordingly.

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 {
  String? _initialUri;
  String? _latestUri;
  StreamSubscription? _sub;

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

  @override
  void dispose() {
    _sub?.cancel();
    super.dispose();
  }

  Future _initDeepLinkHandling() async {
    try {
      _initialUri = await getInitialUri();
      if (_initialUri != null) {
        print('Initial URI: $_initialUri');
        _handleUri(_initialUri!);
      }

      _sub = uriLinkStream.listen((Uri? uri) {
        if (uri != null) {
          print('Received URI: $uri');
          _latestUri = uri.toString();
          _handleUri(uri.toString());
        }
      }, onError: (err) {
        print('Error receiving URI: $err');
      });
    } on PlatformException {
      print('Platform Exception: Could not resolve initial URI.');
    }
  }

  void _handleUri(String uri) {
    // Example: Parse the URI and navigate to a specific screen
    if (uri.startsWith('myapp://open')) {
      final params = Uri.parse(uri).queryParameters;
      final productId = params['product_id'];

      if (productId != null) {
        Navigator.of(context).push(MaterialPageRoute(
          builder: (context) => ProductDetailsScreen(productId: productId),
        ));
      }
    } else if (uri.startsWith('https://www.example.com/products')) {
      final segments = Uri.parse(uri).pathSegments;
      if (segments.length > 1) {
          final productId = segments[1]; // Assuming the product ID is the second segment
          Navigator.of(context).push(MaterialPageRoute(
              builder: (context) => ProductDetailsScreen(productId: productId),
          ));
      }
  }
}

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Deep Linking Example'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text('Initial URI: $_initialUri'),
              Text('Latest URI: $_latestUri'),
            ],
          ),
        ),
      ),
    );
  }
}

class ProductDetailsScreen extends StatelessWidget {
  final String productId;

  ProductDetailsScreen({required this.productId});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Product Details'),
      ),
      body: Center(
        child: Text('Details for Product ID: $productId'),
      ),
    );
  }
}

In this example:

  • getInitialUri() retrieves the URI that launched the app.
  • uriLinkStream listens for incoming URIs while the app is running.
  • _handleUri(String uri) parses the URI and navigates to the appropriate screen.

Step 4: Handle App Links and Universal Links

To handle App Links and Universal Links, the Flutter app needs to be configured similarly to the URI schemes, but the handling will be triggered through HTTP/HTTPS URLs.


  void _handleUri(String uri) {
      // Handle HTTP/HTTPS links
      if (uri.startsWith('https://www.example.com/products')) {
          final segments = Uri.parse(uri).pathSegments;
          if (segments.length > 1) {
              final productId = segments[1]; // Assuming the product ID is the second segment
              Navigator.of(context).push(MaterialPageRoute(
                  builder: (context) => ProductDetailsScreen(productId: productId),
              ));
          }
      }
  }

Conclusion

Implementing deep linking in Flutter can significantly enhance your application’s usability and marketing effectiveness by enabling seamless navigation to specific content. By correctly configuring your app for both URI schemes and HTTP/HTTPS links and using the uni_links package to listen for incoming URIs, you can create a more engaging and user-friendly experience. Whether you are directing users from email campaigns or other applications, deep linking offers a streamlined pathway to the most relevant content in your app.