Hassan uni links - A Flutter plugin project to help with App/Deep Links (Android) and Universal Links and Custom URL schemes

Overview

Uni Links

A Flutter plugin project to help with App/Deep Links (Android) and Universal Links and Custom URL schemes (iOS).

These links are simply web-browser-like-links that activate your app and may contain information that you can use to load specific section of the app or continue certain user activity from a website (or another app).

App Links and Universal Links are regular https links, thus if the app is not installed (or setup correctly) they'll load in the browser, allowing you to present a web-page for further action, eg. install the app.

Make sure you read both the Installation and the Usage guides, thoroughly, especiallly for App/Universal Links (the https scheme).

Installation

To use the plugin, add uni_links as a dependency in your pubspec.yaml file.

0.5.0 Breaking changes

Due to the migration to null safety, some APIs have changed. These changes mainly involve functions changing into getters, and types becoming explicitly nullable.

The changes to the example package are a good example of how to upgrade to this version.

Permission

Android and iOS require to declare links' permission in a configuration file.

Feel free to examine tha example app in the example directory for Deep Links (Android) and Custom URL schemes (iOS).

The following steps are not Flutter specific, but platform specific. You might be able to find more in-depth guides elsewhere online, by searching about App Links or Deep Links on Android; Universal Links or Custom URL schemas on iOS.

For Android

Uni Links supports two types of Android links: "App Links" and "Deep Links".

  • App Links only work with https scheme and require a specified host, plus a hosted file - assetlinks.json. Check the Guide links below.
  • Deep Links can have any custom scheme and do not require a host, nor a hosted file. The downside is that any app can claim a scheme + host combo, so make sure yours are as unique as possible, eg. HST0000001://host.com.

You need to declare at least one of the two intent filters in android/app/src/main/AndroidManifest.xml:

<manifest ...>
  <!-- ... other tags -->
  <application ...>
    <activity ...>
      <!-- ... other tags -->

      <!-- Deep Links -->
      <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <!-- Accepts URIs that begin with YOUR_SCHEME://YOUR_HOST -->
        <data
          android:scheme="[YOUR_SCHEME]"
          android:host="[YOUR_HOST]" />
      </intent-filter>

      <!-- App Links -->
      <intent-filter android:autoVerify="true">
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <!-- Accepts URIs that begin with https://YOUR_HOST -->
        <data
          android:scheme="https"
          android:host="[YOUR_HOST]" />
      </intent-filter>
    </activity>
  </application>
</manifest>

The android:host attribute is optional for Deep Links.

To further the specificity you can add an android:pathPrefix attribute:

<!-- Accepts URIs that begin with YOUR_SCHEME://YOUR_HOST/NAME/NAME... -->
<!-- Accepts URIs that begin with       https://YOUR_HOST/NAME/NAME... -->
<!-- note that the leading "/" is required for pathPrefix -->
<data
  android:scheme="[YOUR_SCHEME_OR_HTTPS]"
  android:host="[YOUR_HOST]"
  android:pathPrefix="/[NAME][/NAME...]" />

For more info read The Ultimate Guide. Pay close attention to the App Links section in the Guide regarding the required /.well-known/assetlinks.json file.

The Android developer docs are also a great source of information for both Deep Links and App Links.

For iOS

There are two kinds of links in iOS: "Universal Links" and "Custom URL schemes".

  • Universal Links only work with https scheme and require a specified host, entitlements and a hosted file - apple-app-site-association. Check the Guide links below.
  • Custom URL schemes can have... any custom scheme and there is no host specificity, nor entitlements or a hosted file. The downside is that any app can claim any scheme, so make sure yours is as unique as possible, eg. hst0000001 or myIncrediblyAwesomeScheme.

You need to declare at least one of the two.

--

For Universal Links you need to add or create a com.apple.developer.associated-domains entitlement - either through Xcode (see below) or by editing (or creating and adding to Xcode) ios/Runner/Runner.entitlements file.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <!-- ... other keys -->
  <key>com.apple.developer.associated-domains</key>
  <array>
    <string>applinks:[YOUR_HOST]</string>
  </array>
  <!-- ... other keys -->
</dict>
</plist>

This allows for your app to be started from https://YOUR_HOST links.

Creating the entitlements file in Xcode:

  • Open up Xcode by double-clicking on your ios/Runner.xcworkspace file.
  • Go to the Project navigator (Cmd+1) and select the Runner root item at the very top.
  • Select the Runner target and then the Signing & Capabilities tab.
  • Click the + Capability (plus) button to add a new capability.
  • Type 'associated domains` and select the item.
  • Double-click the first item in the Domains list and change it from webcredentials:example.com to: applinks: + your host (ex: my-fancy-domain.com).
  • A file called Runner.entitlements will be created and added to the project.
  • Done. Here's a screenshot.

For more information, read Apple's guide for Universal Links.

--

For Custom URL schemes you need to declare the scheme in ios/Runner/Info.plist (or through Xcode's Target Info editor, under URL Types):

<?xml ...>
<!-- ... other tags -->
<plist>
<dict>
  <!-- ... other tags -->
  <key>CFBundleURLTypes</key>
  <array>
    <dict>
      <key>CFBundleTypeRole</key>
      <string>Editor</string>
      <key>CFBundleURLName</key>
      <string>[ANY_URL_NAME]</string>
      <key>CFBundleURLSchemes</key>
      <array>
        <string>[YOUR_SCHEME]</string>
      </array>
    </dict>
  </array>
  <!-- ... other tags -->
</dict>
</plist>

This allows for your app to be started from YOUR_SCHEME://ANYTHING links.

For a little more information, read Apple's guide for Inter-App Communication.

I strongly recommend watching the Apple WWDC 2015, session 509 - Seamless Linking to Your App to understand how the Universal Links work (and are setup).

Usage

There are two ways your app will recieve a link - from cold start and brought from the background. More on these after the example usage in More about app start from a link.

ATTENTION: getInitialLink/getInitialUri should be handled ONLY ONCE in your app's lifetime, since it is not meant to change throughout your app's life.

Initial Link (String)

Returns the link that the app was started with, if any.

You should handle this very early in your app's life and handle it only once. Feel free to read the value as many times as you wish, but only handle it once.

import 'dart:async';
import 'dart:io';

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

// ...

  Future<void> initUniLinks() async {
    // Platform messages may fail, so we use a try/catch PlatformException.
    try {
      final initialLink = await getInitialLink();
      // Parse the link and warn the user, if it is not correct,
      // but keep in mind it could be `null`.
    } on PlatformException {
      // Handle exception by warning the user their action did not succeed
      // return?
    }
  }

// ...

Initial Link (Uri)

Same as the getInitialLink, but converted to a Uri.

NOTE: You should handle this very early in your app's life and handle it only once.

    // Uri parsing may fail, so we use a try/catch FormatException.
    try {
      final initialUri = await getInitialUri();
      // Use the uri and warn the user, if it is not correct,
      // but keep in mind it could be `null`.
    } on FormatException {
      // Handle exception by warning the user their action did not succeed
      // return?
    }
    // ... other exception handling like PlatformException

One can achieve the same by using Uri.parse(initialLink), which is what this convenience method does.

On change event (String)

Usually you would check the getInitialLink and also listen for changes.

import 'dart:async';
import 'dart:io';

import 'package:uni_links/uni_links.dart';

// ...

  StreamSubscription _sub;

  Future<void> initUniLinks() async {
    // ... check initialLink

    // Attach a listener to the stream
    _sub = linkStream.listen((String? link) {
      // Parse the link and warn the user, if it is not correct
    }, onError: (err) {
      // Handle exception by warning the user their action did not succeed
    });

    // NOTE: Don't forget to call _sub.cancel() in dispose()
  }

// ...

On change event (Uri)

Same as the linkStream, but transformed to emit Uri objects.

Usually you would check the getInitialUri and also listen for changes.

import 'dart:async';
import 'dart:io';

import 'package:uni_links/uni_links.dart';

// ...

  StreamSubscription _sub;

  Future<void> initUniLinks() async {
    // ... check initialUri

    // Attach a listener to the stream
    _sub = uriLinkStream.listen((Uri? uri) {
      // Use the uri and warn the user, if it is not correct
    }, onError: (err) {
      // Handle exception by warning the user their action did not succeed
    });

    // NOTE: Don't forget to call _sub.cancel() in dispose()
  }

// ...

More about app start from a link

If the app was terminated (or rather not running in the background) and the OS must start it anew - that's a cold start. In that case, getInitialLink will have the link that started your app and the Stream won't produce a link (at that point in time).

Alternatively - if the app was running in the background and the OS must bring it to the foreground the Stream will be the one to produce the link, while getInitialLink will be either null, or the initial link, with which the app was started.

Because of these two situations - you should always add a check for the initial link (or URI) and also subscribe for a Stream of links (or URIs).

Tools for invoking links

If you register a schema, say unilink, you could use these cli tools:

Android

You could do below tasks within Android Studio.

Assuming you've installed Android Studio (with the SDK platform tools):

adb shell 'am start -W -a android.intent.action.VIEW -c android.intent.category.BROWSABLE -d "unilinks://host/path/subpath"'
adb shell 'am start -W -a android.intent.action.VIEW -c android.intent.category.BROWSABLE -d "unilinks://example.com/path/portion/?uid=123&token=abc"'
adb shell 'am start -W -a android.intent.action.VIEW -c android.intent.category.BROWSABLE -d "unilinks://example.com/?arr%5b%5d=123&arr%5b%5d=abc&addr=1%20Nowhere%20Rd&addr=Rand%20City%F0%9F%98%82"'
adb shell 'am start -W -a android.intent.action.VIEW -c android.intent.category.BROWSABLE -d "unilinks://@@malformed.invalid.url/path?"'

If you don't have adb in your path, but have $ANDROID_HOME env variable then use "$ANDROID_HOME"/platform-tools/adb ....

Note: Alternatively you could simply enter an adb shell and run the am commands in it.

Note: I use single quotes, because what follows the shell command is what will run in the emulator (or device) and shell metacharacters, such as question marks (?) and ampersands (&), usually mean something different to your own shell.

adb shell communicates with the only available device (or emulator), so if you've got multiple devices you have to specify which one you want to run the shell in via:

  • The only USB connected device - adb -d shell '...'
  • The only emulated device - adb -e shell '...'

You could use adb devices to list currently available devices (similarly flutter devices does the same job).

iOS

Assuming you've got Xcode already installed:

/usr/bin/xcrun simctl openurl booted "unilinks://host/path/subpath"
/usr/bin/xcrun simctl openurl booted "unilinks://example.com/path/portion/?uid=123&token=abc"
/usr/bin/xcrun simctl openurl booted "unilinks://example.com/?arr%5b%5d=123&arr%5b%5d=abc&addr=1%20Nowhere%20Rd&addr=Rand%20City%F0%9F%98%82"
/usr/bin/xcrun simctl openurl booted "unilinks://@@malformed.invalid.url/path?"

If you've got xcrun (or simctl) in your path, you could invoke it directly.

The flag booted assumes an open simulator (you can start it via open -a Simulator) with a booted device. You could target specific device by specifying its UUID (found via xcrun simctl list or flutter devices), replacing the booted flag.

App Links or Universal Links

These types of links use https for schema, thus you can use above examples by replacing unilinks with https.

Contributing

For help on editing plugin code, view the documentation.

This plugin uses the federated plugin architecture. New implementations must add the uni_links_platform_interface package to their pubspec.yaml, extend the UniLinksPlatform class, and register it properly.
More information about federated plugins can be found in the proposal document, the Flutter plugin documentation, and Harry Terkelsen's Medium story describing the architecture.

License

BSD 2-clause

You might also like...

Deep Dive Into Flutter

Deep Dive Into Flutter

Deep Dive Into Flutter This my more formalized version of a Rosetta Stone of Flutter Demos to encourage you to take a deep dive into flutter to master

Nov 2, 2022

Deep AR SDK for Flutter.

This plugin is the official SDK for DeepAR. Platforms supported: Android & iOS. The current version of plugin supports: Live AR previews ✅ Take screen

Dec 14, 2022

App to generate url with OGP image.

About App to generate url with OGP image. Usage Clone this repository Connect to Firebase Setting Dynamic Links in Firebase Setting Storage in Firebas

Dec 5, 2021

Flutter frontend for downloading free album and playlists (based on a YouTube URL) and uploading them to a Plex server.

Flutter frontend for downloading free album and playlists (based on a YouTube URL) and uploading them to a Plex server.

Flutter frontend for downloading free album and playlists (based on a YouTube URL) and uploading them to a Plex server. (The project is currently in progress. There are some additional features and ideas I want to implement.)

Jan 9, 2022

A simple web API to generate images from a url, made using shelf package.

Webshot API Yet another simple webshot API but written in dart and built using Shelf library to generate images from url. This is really useful for li

Oct 19, 2022

Flutter plugin which helps you to find links in String using NSDataDetector and Linkify

Flutter plugin which helps you to find links in String using NSDataDetector and Linkify

Flutter's Native Linkify native_linkify is a Flutter plugin. Use it to find links in plain-text. The plugin uses NSDataDetector for iOS and macOS; Lin

Nov 29, 2022

best flutter / dart practices + Custom Painter + Sliver App Bar + Custom Scrollview

best flutter / dart practices + Custom Painter + Sliver App Bar + Custom Scrollview

Weekly Budget Flutter App A new Flutter project. Getting Started This project is a starting point for a Flutter application. A few resources to get yo

Oct 21, 2021

A customizable carousel slider for Flutter. Supports infinite sliding, custom indicators, and custom animations with many pre-built indicators and animations.

A customizable carousel slider for Flutter. Supports infinite sliding, custom indicators, and custom animations with many pre-built indicators and animations.

Flutter Carousel Slider A customizable carousel slider for flutter Screenshots Installing dependencies: flutter_carousel_slider: ^1.0.8 Demo Demo a

Nov 6, 2022

A customizable carousel slider widget in Flutter which supports inifinte scrolling, auto scrolling, custom child widget, custom animations and built-in indicators.

A customizable carousel slider widget in Flutter which supports inifinte scrolling, auto scrolling, custom child widget, custom animations and built-in indicators.

flutter_carousel_widget A customizable carousel slider widget in Flutter. Features Infinite Scroll Custom Child Widget Auto Play Horizontal and Vertic

Nov 26, 2022
Owner
Hassan Al-Sabti
My name is Hassan Al-Sabti. Coding is my hobby, Computer Science graduate
Hassan Al-Sabti
A simple project that will help you learn how to build a simple face filter app with Flutter and Deep AR

flutter_deepar This is a simple project that will help you learn how to build a simple face filter app with Flutter and Deep AR You can read an articl

mobile software engineer and team leader 3 Mar 11, 2022
A simple project demonstrating how to build a face filter app using Flutter and Deep AR

flutter_deepar This is a simple project that will help you learn how to build a simple face filter app with Flutter and Deep AR You can read an articl

Promise Amadi 32 Jan 1, 2023
Pneumonia detection android app based on deep learning API

pneumonia A new Flutter project. Getting Started This project is a starting point for a Flutter application. A few resources to get you started if thi

Mohammed Mahmood 0 Nov 7, 2021
It's a universal app template to have a great animated splash screen and liquid slider. Just change the animation if you want (rive) and change the images or colours according to your app.

liquid A new Flutter project. Getting Started This project is a starting point for a Flutter application. A few resources to get you started if this i

Zikyan Rasheed 28 Oct 7, 2022
Data Migrator - provide a universal translator for data by being portable, diverse, and efficient in migrating and converting data across discrete schemas

Data Migrator - provide a universal translator for data by being portable, diverse, and efficient in migrating and converting data across discrete schemas

Tanner Meade 77 Jan 2, 2023
Flutter remote control - The main use of LongPressDraggable and DragTarget to achieve the universal remote control interaction effect.

Flutter remote control - The main use of LongPressDraggable and DragTarget to achieve the universal remote control interaction effect.

唯鹿 165 Jan 2, 2023
Flutter package that provides you custom clippers to help you achieve various custom shapes.

flutter_custom_clippers Flutter package that provides you custom clippers to help you achieve various custom shapes. Usage To use this plugin, add flu

Damodar Lohani 291 Dec 23, 2022
An universal server management tool for Minecraft.

MinecraftCube Desktop A server management tool for Minecraft, that help players start any kind of minecraft server easier. Mainly support vanilla and

null 12 Nov 26, 2022
A flutter plugin about qr code or bar code scan , it can scan from file、url、memory and camera qr code or bar code .Welcome to feedback your issue.

r_scan A flutter plugin about qr code or bar code scan , it can scan from file、url、memory and camera qr code or bar code .Welcome to feedback your iss

PengHui Li 112 Nov 11, 2022