Using Packages Like uni_links to Simplify the Process of Handling Incoming Deep Links in Your Flutter App

Deep linking is a crucial aspect of modern mobile applications, allowing users to navigate directly to specific content within your app from external sources like web pages, emails, or social media posts. In Flutter, managing deep links can be streamlined with the help of packages like uni_links. This article explores how to use uni_links to simplify handling incoming deep links in your Flutter app, providing code samples and best practices.

What are Deep Links?

Deep links are URIs (Uniform Resource Identifiers) that direct users to a specific location within an app. They can be either:

  • Custom Scheme Deep Links: Use a custom scheme (e.g., myapp://content/123).
  • Universal Links (Android App Links and iOS Universal Links): Standard HTTP/HTTPS URLs that link directly to content within the app and verify the app’s ownership of the domain.

Why Use Deep Linking?

  • Improved User Experience: Direct users to relevant content quickly.
  • Increased Engagement: Drive app engagement from marketing campaigns.
  • Seamless Navigation: Provide smooth transitions from external sources.

Using the uni_links Package in Flutter

The uni_links package provides a straightforward way to handle both custom scheme and universal links in your Flutter application. Here’s how to get started:

Step 1: Add the uni_links Dependency

Add the uni_links package to your pubspec.yaml file:

dependencies:
  flutter:
    sdk: flutter
  uni_links: ^0.5.1  # Use the latest version

Then, run flutter pub get to install the package.

Step 2: Configure Deep Links on Each Platform

iOS Configuration (Universal Links)
  1. Associate Your App with Your Website:

    Create an apple-app-site-association file and place it in the .well-known directory of your domain. This file specifies the paths that should be handled by your app.

    Example apple-app-site-association file:

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

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

  2. Enable Associated Domains Entitlement:

    In Xcode, go to your target’s settings, select “Signing & Capabilities,” and add the “Associated Domains” capability. Add entries for each domain you want to associate with your app, prefixed with applinks:.

    Example:

    applinks:yourdomain.com
Android Configuration (App Links)
  1. Verify Your App with Google:

    Add an intent-filter to your AndroidManifest.xml file with the DEFAULT, BROWSABLE categories, and the ACTION_VIEW action. Configure the data tags with your scheme and host.

    
        
        
        
        
    

    Set android:autoVerify="true" to enable automatic verification. If automatic verification fails, you may need to use the Digital Asset Links file approach.

  2. Digital Asset Links File:

    Create a assetlinks.json file and place it in the .well-known directory of your domain.

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

    Replace com.example.myapp with your app’s package name and YOUR_SHA256_CERT_FINGERPRINT with your app’s SHA256 certificate fingerprint.

Step 3: Handle Incoming Links in Flutter

In your Flutter app, use the uni_links package to listen for incoming links. Here’s an example:

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? _latestLink = 'Unknown';
  String? _latestUri;

  @override
  void initState() {
    super.initState();
    _initURIHandler();
    _incomingLinkHandler();
  }

  Future _initURIHandler() async {
    // 1. Get the latest initial link
    final initialLink = await getInitialLink();
    // 2. If the link is not null, parse it and navigate
    if (initialLink != null) {
      _latestLink = initialLink;
      _latestUri = initialLink;
      print('Initial Link: $_latestLink');
      // TODO: Handle the initial link (parse and navigate)
    }
  }

  Future _incomingLinkHandler() async {
    // 1. Listen to the stream of incoming links
    uriLinkStream.listen((Uri? uri) {
      if (!mounted) return;
      print('Received URI: $uri');
      setState(() {
        _latestUri = uri.toString();
        _latestLink = uri.toString() ?? 'Unknown';
      });
      // TODO: Handle the link (parse and navigate)
    }, onError: (Object err) {
      if (!mounted) return;
      print('Got error : $err');
      setState(() {
        _latestLink = 'Failed to get latest link: $err.';
        _latestUri = 'Failed to get latest link: $err.';
      });
    });
  }


  @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(
                'Latest Link: $_latestLink',
                textAlign: TextAlign.center,
              ),
            ],
          ),
        ),
      ),
    );
  }
}

In this example:

  • Import the necessary packages (uni_links and flutter/services.dart).
  • The _initURIHandler() function fetches the initial link when the app starts.
  • The _incomingLinkHandler() function listens for incoming links using the uriLinkStream.
  • Handle the parsed URI to navigate to the appropriate screen in your app.

Parsing the URI

Once you receive the URI, you need to parse it to extract relevant information. For example, if your deep link is https://yourdomain.com/content/123, you need to extract the content ID (123). Here’s how you can parse the URI:


Uri uri = Uri.parse(_latestUri ?? "");
String? path = uri.path;
List pathSegments = uri.pathSegments;

if (pathSegments.isNotEmpty && pathSegments.contains('content')) {
  String contentId = pathSegments[pathSegments.indexOf('content') + 1];
  print('Content ID: $contentId');
  // Navigate to the content details screen
  navigateToContentDetails(context, contentId);
}

Best Practices

  • Error Handling: Implement robust error handling to gracefully handle invalid or malformed links.
  • Navigation: Use a navigation system that allows deep linking to specific content (e.g., using named routes in Flutter).
  • Testing: Thoroughly test your deep link implementation on both Android and iOS devices.
  • Security: Validate and sanitize deep link data to prevent security vulnerabilities.

Conclusion

Using the uni_links package simplifies the process of handling incoming deep links in your Flutter application, enabling seamless navigation and improved user experience. By configuring deep links on both Android and iOS platforms and implementing robust link handling in your Flutter code, you can effectively leverage deep linking to enhance app engagement and usability.