Using Deep Links to Navigate to Specific Content in Flutter

Deep linking is a powerful technique that allows users to navigate directly to specific content within an application from an external source, such as a web page, email, or another app. In Flutter, deep linking can significantly enhance the user experience by providing seamless transitions to relevant in-app content. This guide will explore how to implement deep links in a Flutter application.

What are Deep Links?

Deep links are URIs (Uniform Resource Identifiers) that direct users to a specific location within an application. Unlike regular links that open the app’s homepage, deep links take users directly to the relevant content. They can handle various navigation scenarios, such as directing users to a product page from an advertisement, a user profile from a friend invite, or a specific article from a news notification.

Why Use Deep Linking in Flutter?

  • Improved User Experience: Provides direct access to content, reducing user friction.
  • Increased Engagement: Drives users to specific features within your app, promoting usage.
  • Enhanced Marketing: Facilitates targeted campaigns that lead users to relevant in-app offers.
  • Seamless Integrations: Enables smooth transitions between apps or from web content to the app.

How to Implement Deep Links in Flutter

Implementing deep links in Flutter involves configuring both the app and the operating system to handle the incoming links correctly.

Step 1: Configure the Flutter Project

To begin, you need to configure your Flutter project to handle incoming deep links. This involves modifying the Android and iOS project settings.

Android Configuration

In your AndroidManifest.xml file (located at android/app/src/main/AndroidManifest.xml), add an intent filter to the main activity.


<activity
    android:name=".MainActivity"
    android:launchMode="singleTask"> <!-- Add singleTask to handle deep links properly -->
    <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
        <category android:name="android.intent.category.LAUNCHER"/>
    </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="your_scheme"
            android:host="your_host"/> <!-- Replace with your scheme and host -->
    </intent-filter>
</activity>

Replace your_scheme and your_host with the desired URL scheme and host. For example:


<data
    android:scheme="myapp"
    android:host="example.com"/>
iOS Configuration

In your Info.plist file (located at ios/Runner/Info.plist), add the CFBundleURLTypes array.


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

Replace your_scheme with the same URL scheme you used in the Android configuration. For example:


<string>myapp</string>

Also, ensure that you have a properly configured Apple App Site Association (AASA) file hosted at https://example.com/apple-app-site-association if you are using universal links (HTTPS-based deep links).

Step 2: Implement Deep Link Handling in Flutter

In your Flutter app, you need to add logic to handle incoming deep links. Use the uni_links package to listen for and process the links.

First, add the uni_links dependency to your pubspec.yaml file:


dependencies:
  flutter:
    sdk: flutter
  uni_links: ^0.5.1

Install the package by running flutter pub get.

Then, implement the deep link handling in your main app widget:


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

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

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

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

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

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

  Future<void> _initDeepLinkHandling() async {
    await _getInitialLink();
    _sub = uriLinkStream.listen((Uri? uri) {
      if (!mounted) return;
      setState(() {
        _latestLink = uri?.toString() ?? 'Unknown';
        _handleDeepLink(uri);
      });
    }, onError: (Object err) {
      if (!mounted) return;
      setState(() {
        _latestLink = 'Failed to get latest link: $err.';
      });
    });
  }

  Future<void> _getInitialLink() async {
    try {
      final initialLink = await getInitialUri();
      if (initialLink != null) {
        setState(() {
          _initialLink = initialLink.toString();
          _handleDeepLink(initialLink);
        });
      } else {
        setState(() {
          _initialLink = 'No initial link found.';
        });
      }
    } on PlatformException {
      _initialLink = 'Failed to get initial link.';
    } on FormatException {
      _initialLink = 'Malformed initial link.';
    }
  }

  void _handleDeepLink(Uri? uri) {
    if (uri != null) {
      // Example: Navigating based on the path
      if (uri.pathSegments.contains('product')) {
        String productId = uri.queryParameters['id'] ?? '';
        Navigator.of(context).push(MaterialPageRoute(
          builder: (context) => ProductPage(productId: productId),
        ));
      } else if (uri.pathSegments.contains('profile')) {
        String userId = uri.queryParameters['user'] ?? '';
        Navigator.of(context).push(MaterialPageRoute(
          builder: (context) => ProfilePage(userId: userId),
        ));
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Deep Linking in Flutter'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text('Initial Link: $_initialLink'),
              Text('Latest Link: $_latestLink'),
            ],
          ),
        ),
      ),
    );
  }
}

class ProductPage extends StatelessWidget {
  final String productId;

  ProductPage({required this.productId});

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

class ProfilePage extends StatelessWidget {
  final String userId;

  ProfilePage({required this.userId});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('User Profile'),
      ),
      body: Center(
        child: Text('User ID: $userId'),
      ),
    );
  }
}

Explanation:

  • The _initDeepLinkHandling() method is called in initState() to listen for incoming deep links.
  • getInitialUri() retrieves the initial link used to open the app.
  • uriLinkStream.listen listens for subsequent deep links while the app is running.
  • _handleDeepLink() parses the URI and navigates to the appropriate screen.
  • The example includes basic navigation for product and profile pages based on the URI parameters.

Step 3: Testing Deep Links

To test your deep links, you can use the following commands:

Android Testing

Use adb command to simulate a deep link:


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

Replace your_scheme, your_host, and com.example.myapp with your actual scheme, host, and package name.

iOS Testing

Use xcrun command to simulate a deep link:


xcrun simctl openurl booted "your_scheme://your_host/product?id=123"

Replace your_scheme and your_host with your actual scheme and host.

Conclusion

Deep linking provides a powerful mechanism for navigating users directly to specific content within your Flutter application. By configuring both Android and iOS projects and using the uni_links package, you can efficiently handle incoming deep links, enhancing the user experience and promoting engagement. Properly implemented deep links can significantly improve your app’s usability and marketing effectiveness.