Implementing Custom Deep Link Handling Logic in Flutter

Deep links are an essential part of modern mobile applications, allowing users to navigate directly to specific sections within an app from external sources like websites, emails, or other apps. In Flutter, implementing deep link handling can greatly enhance user experience and engagement. This article provides a comprehensive guide to implementing custom deep link handling logic in Flutter.

What are Deep Links?

Deep links are Uniform Resource Identifiers (URIs) that take users to a specific location within an application. Unlike traditional web URLs, deep links are designed to work with mobile apps, allowing seamless navigation to particular screens or functionalities. They come in two main types:

  • Custom URL Schemes: Uses a custom scheme (e.g., myapp://) specific to your app.
  • Universal Links (Android App Links and iOS Universal Links): Standard HTTP/HTTPS links that associate with your app through a website.

Why Implement Custom Deep Link Handling?

  • Enhanced User Experience: Direct users to specific content within the app, avoiding unnecessary navigation.
  • Increased Engagement: Promote app features directly from external sources.
  • Improved Conversion Rates: Guide users directly to product pages, promotional offers, or specific content.

Setting Up Your Flutter Project for Deep Linking

Before diving into the implementation, you need to configure your Flutter project to handle deep links.

Step 1: Android Configuration

For Android, you need to modify your AndroidManifest.xml file.

Locate your AndroidManifest.xml file in android/app/src/main/ and add the following intent filter to the relevant <activity> tag (usually the main activity):

<activity
    android:name=".MainActivity"
    android:launchMode="singleTask"> <!-- Ensure singleTask launchMode to handle deep links correctly -->
    
    <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" /> <!-- Custom URL Scheme -->
    </intent-filter>
    
    <intent-filter android:autoVerify="true"> <!-- For Android App Links -->
        <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="/app" /> <!-- Universal Link -->
    </intent-filter>
    
</activity>

In this example:

  • android:scheme="myapp" android:host="open" configures the app to handle deep links with the myapp://open scheme.
  • android:scheme="https" android:host="www.example.com" android:pathPrefix="/app" configures the app to handle universal links with the domain www.example.com and the path prefix /app.

Step 2: iOS Configuration

For iOS, you need to configure both the URL schemes and universal links in Xcode.

Configuring URL Schemes
  1. Open your project in Xcode.
  2. Select your target, then go to the “Info” tab.
  3. Scroll down to “URL Types” and click the “+” button to add a new URL type.
  4. Enter a unique identifier and your URL scheme (e.g., myapp).
Configuring Universal Links
  1. Associate your app with your website by creating an apple-app-site-association file.
  2. Host the apple-app-site-association file at the root or the .well-known directory of your domain.
  3. Configure your app to handle universal links by enabling the “Associated Domains” entitlement and adding your domain (e.g., applinks:www.example.com).

Example apple-app-site-association file:

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

Implementing Deep Link Handling in Flutter

Now that your project is configured, let’s implement the deep link handling logic in Flutter.

Step 1: Add Dependencies

You’ll need the uni_links package to handle deep links. 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 2: Create a Deep Link Service

Create a service class to handle deep link routing. This will centralize the deep link logic and make your code more maintainable.

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

class DeepLinkService {
  final GlobalKey<NavigatorState> navigatorKey;

  DeepLinkService(this.navigatorKey);

  Future<void> init() async {
    await _handleInitialUri();
    _handleIncomingLinks();
  }

  Future<void> _handleInitialUri() async {
    try {
      final uri = await getInitialUri();
      if (uri != null) {
        _openPageBasedOnUri(uri);
      }
    } on PlatformException {
      // Handle exception, e.g., platform not supported.
    } on FormatException catch (err) {
      debugPrint('Malformed Initial URI received: $err');
    }
  }

  void _handleIncomingLinks() {
    uriLinkStream.listen((Uri? uri) {
      if (uri != null) {
        _openPageBasedOnUri(uri);
      }
    }, onError: (Object err) {
      debugPrint('Malformed URI received: $err');
    });
  }

  void _openPageBasedOnUri(Uri uri) {
    String route = uri.path;

    switch (route) {
      case '/product':
        String? productId = uri.queryParameters['id'];
        if (productId != null) {
          navigatorKey.currentState?.pushNamed('/productDetails', arguments: productId);
        }
        break;
      case '/promo':
        navigatorKey.currentState?.pushNamed('/promoPage');
        break;
      default:
        navigatorKey.currentState?.pushNamed('/home');
        break;
    }
  }
}

In this service:

  • init() initializes the deep link handling by fetching the initial URI (if any) and setting up a stream listener for incoming URIs.
  • _handleInitialUri() retrieves the initial URI when the app starts and routes the user accordingly.
  • _handleIncomingLinks() listens for incoming deep links while the app is running and routes the user to the appropriate page.
  • _openPageBasedOnUri(Uri uri) extracts the route and parameters from the URI and navigates to the corresponding screen.

Step 3: Integrate Deep Link Service into Your App

Initialize the DeepLinkService in your main.dart file and set up routing.

import 'package:flutter/material.dart';
import 'deep_link_service.dart';

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

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

class _MyAppState extends State<MyApp> {
  final GlobalKey<NavigatorState> _navigatorKey = GlobalKey<NavigatorState>();
  late DeepLinkService _deepLinkService;

  @override
  void initState() {
    super.initState();
    _deepLinkService = DeepLinkService(_navigatorKey);
    _deepLinkService.init();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      navigatorKey: _navigatorKey,
      title: 'Deep Link Example',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      routes: {
        '/home': (context) => HomeScreen(),
        '/productDetails': (context) {
          final productId = ModalRoute.of(context)!.settings.arguments as String;
          return ProductDetailsScreen(productId: productId);
        },
        '/promoPage': (context) => PromoPageScreen(),
      },
      home: HomeScreen(),
    );
  }
}

class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Home'),
      ),
      body: Center(
        child: Text('Home Screen'),
      ),
    );
  }
}

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('Product ID: $productId'),
      ),
    );
  }
}

class PromoPageScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Promo Page'),
      ),
      body: Center(
        child: Text('Promo Page Content'),
      ),
    );
  }
}

Here’s how this works:

  • The GlobalKey<NavigatorState> is used to access the navigator from anywhere in the app.
  • The DeepLinkService is initialized in the initState() method.
  • The MaterialApp sets up routes for different pages, which are navigated to by the DeepLinkService based on the URI.

Testing Your Deep Links

After implementing the deep link handling, it’s crucial to test it thoroughly.

Testing on Android

You can use the adb command to simulate deep links:

adb shell am start -W -a android.intent.action.VIEW -d "myapp://open/product?id=123" com.example.myapp

Replace myapp://open/product?id=123 with your deep link URI and com.example.myapp with your app’s package name.

Testing on iOS

You can use the xcrun command to simulate deep links:

xcrun simctl openurl booted "myapp://open/product?id=123"

Replace myapp://open/product?id=123 with your deep link URI.

Best Practices for Deep Link Handling

  • Handle Edge Cases: Implement proper error handling for malformed or unexpected URIs.
  • Ensure Security: Validate the data received through deep links to prevent malicious attacks.
  • Provide Fallbacks: If the deep link cannot be handled, navigate users to a relevant screen (e.g., home screen).
  • Document Deep Link Structure: Clearly document your app’s deep link structure for external developers.
  • Test Thoroughly: Test all deep link scenarios on both Android and iOS devices.

Conclusion

Implementing custom deep link handling logic in Flutter involves configuring your project, setting up a deep link service, and integrating it into your app. By following this comprehensive guide, you can create a seamless and engaging user experience, directing users to specific content within your app from external sources. Deep links are a powerful tool for enhancing user engagement, improving conversion rates, and promoting app features effectively. Proper implementation and thorough testing are key to leveraging the full potential of deep links in your Flutter applications.