Feb 2021

Making use of Deep Links in Flutter for Web

What is Deep Linking?

The term deep links itself it not yet a very common term, but you will likely recognize straightaway the phenomenon it describes.

Suppose one day you are browsing the web and you come across an announcement about a new product from your favourite store. You click a link within the announcement in the browser on your phone, and your device opens the store’s app, exactly at the product page. The web link has somehow reached into the store's App.

More amazingly: if the store's app isn't already installed on your device, the link will seamlessly lead you to the App/Play Store page from which you can install it, and then once installed, will then open the store App at the product page!

First let us name this quasi-magical symbiosis between the web and mobile apps: it is called deep linking.

But how can you, as a programmer, achieve this fluent user experience for your apps?

Sense the deepness?


Firebase Dynamic Links

Google offers a free service which makes implementing deep linking straightforward. Take a look at : Firebase Dynamic Links

It is important to note that if you dont want to use Firebase database(s) at all, this does NOT prevent you from using Firebase Dynamic Links.

The preparatory steps for making use of Firebase Dynamic Links are:

- Create yourself a Firebase project if you don't already have one.

- Within the project console, enable Dynamic Links (see under "Engage" in the left sidebar)

- Decide on a URI prefix (essentially: domain and any path you want) you want to use for the dynamic links. Google offers the possibility of using a subdomain on page.link. For example, logiak.page.link. However, it is also possible to configure your own prefix, such as https://logiak.com/link.

Ok, now you are set up to create specific deep links. How? Well, there are a range of possibilities. Certainly, you can use the interactive support in the Firebase console to set up links.

Using Dynamic Links in Flutter

If you are, like us, big fans of Flutter, you will want to know how to make use of them in your Flutter apps.

The Firebase team has designed this extremely simple and useful firebase_dynamic_links Flutter plugin.

If you are, like us, huge fans of Flutter for web, you will be disappointed to note that the plugin works only in Android and iOS.

No matter, for Flutter for Web, there is still the following option: one can call the Firebase Dynamic Links REST API.

Creating a Dynamic Link with REST API

The REST API lets you choose between two different kinds of Dynamic Links: unguessable and short.

An unguessable Dynamic Link is randomly generated, which prevents the URLs from being guessed and crawled. Unfortunately, this also means that if you create an unguessable Dynamic Link from the same parameters twice, the resulting URL will be different.

On the other hand, short Dynamic Links paths are not randomly generated and their path is a string which is only as long as needed to be unique, with a minimum length of 4 characters. They are useful if you want to obtain the same link given the same parameters. They look like:

https://logiak.page.link/ABCD

In this article, we will illustrate using short links. These links can be created by accessing the REST API at the endpoint:

https://firebasedynamiclinks.googleapis.com/v1/shortLinks?key=UVWXYZ

where UVWXYZ is the web API key of your Firebase project. This key can be obtained via Project Settings in your Firebase project console.

Creating and receiving deep links programmatically

We can now create a simple class DynamicLinkService.

The class will contain two methods, createLink for creating short links and retrieveDynamicLink for receiving them.

Creating Deep Links

The method createLink receives a map containing the parameters we want to pass as part of the Dynamic LInk and an identifier:


import 'dart:convert';
import 'package:http/http.dart' as http;

class DynamicLinkService {
  static const String kDynamicLinkURL =
    'https://firebasedynamiclinks.googleapis.com/v1/shortLinks?key=UVWXYZ';
    
  Future createLink(Map parameters, String id) async {
    // Construct the link which will open when the Dynamic Link is used
    List parametersList = [];
    parameters?.forEach((key, value) => parametersList.add('$key=$value'));
    String link = 'https://logiak.com/$id';
    if (parametersList.isNotEmpty) {
      link = link + '?${parametersList.join('&')}';
    }
    Map headers = {'Content-Type': 'application/json'};

    // Configure the Dynamic Link
    Map body = {
      'dynamicLinkInfo': {
        'link': link,
        'domainUriPrefix': 'https://logiak.page.link',
        'androidInfo': {'androidPackageName':'com.myApp'},
        'iosInfo': { 'iosBundleId': 'com.myApp', 'iosAppStoreId': '1234567890' }
      },
      'suffix': {'option': 'SHORT'}
    };
    
    // Request the deep link
    http.Response response = await http.post(
      kDynamicLinkURL,
      body: jsonEncode(body),
      headers: headers,
      encoding: Encoding.getByName("utf-8"),
    );
    
    // Check if we generated a valid Dynamic Link
    if (response.statusCode == 200) {
      Map bodyAnswer = jsonDecode(response.body);
      return bodyAnswer['shortLink'];
    } else {
      return '';
    }
  }
}

Invoking createLink with parameters {‘a’:1, ‘b’:2} and id ‘foo’ results in a Dynamic Link of the form:

https://logiak.page.link/ABCD

Which, If we open on a desktop browser,  navigates us to:

https://logiak.com/foo?a=1&b=2

But, if we open the same Dynamic Link on a mobile device, we want it to navigate for us within our app. We are indicating which app to use by passing the Android package name, and the iOS bundle id and App Store id. Also, this additionally allows us to indicate which app to open in the App/Play Store, in case the app is not installed on the device.

Receiving Deep Links

We need now to enable our  app to recognise a Dynamic Link.

iOS Preparation

No previous configuration is needed for Android, but for iOS you need to fulfil these three steps:

1. Open the iOS/Runner.xcodeproj file in Xcode.

2. Navigate to the Info tab. There, expand the URL Types section and click the + button to add a new one. Set the identifier to “$(PRODUCT_BUNDLE_IDENTIFIER)“ and URL Schemes to the URL scheme you will use in your Dynamic Links (in our example, “com.myApp”).

3. Navigate to the Signing & Capabilities tab and enable Associated Domains. Add an applinks entry into the domain text field with the domain used by your Dynamic Links. In our example: applinks:logiak.page.link. Do not include “https://“ nor any other slash or path in your prefix.

Now, we are ready to receive and handle a Deep Link in our Flutter mobile app -

Include the Firebase dynamic links package

The firebase_dynamic_links plugin contains two important methods for retrieving dynamic links. If the app is closed, then getInitialLink method is called. On the other hand, if the Flutter app is running or in the background and we click of a dynamic link, onLink method will be called.

Therefore, we need to use both of them to be sure we are always catching a dynamic link. We can do both of these by defining the following retrieveDynamicLink method in our DynamicLinkService class:


import 'package:firebase_dynamic_links/firebase_dynamic_links.dart';
import 'package:flutter/foundation.dart' as foundation;
import 'package:flutter/material.dart';

class DynamicLinkService { 
  ...
  Future retrieveDynamicLink() async {
    if (!foundation.kIsWeb) {
      final PendingDynamicLinkData dynamicLink = await FirebaseDynamicLinks.instance.getInitialLink();
      if (dynamicLink != null) {
        doSomething(dynamicLink);
      }
      FirebaseDynamicLinks.instance.onLink(
        onSuccess: (PendingDynamicLinkData dynamicLink) async {
          if (dynamicLink != null) {
            doSomething(dynamicLink);
          }
        }
      );
    }
  }
}

If you intend to run this code on web as well as mobile, remember to check whether foundation.kIsWeb is true (since web is not supported by the Dynamic Links plugin).

You can now invoke retrieveDynamicLink method on your main class. You may want to invoke it on init or when your Flutter app is resumed:


class main {
  DynamicLinkService _service = DynamicLinkService();
  
  void initState() {
    super.initState();
    _service.retrieveDynamicLink();
  }
  
  void didChangeAppLifecycleState(AppLifecycleState state) {
    super.didChangeAppLifecycleState();
    if (state == AppLifecycleState.resumed) {
      _service.retrieveDynamicLink();
    }
  }
}

Of course, doSomething can perform any task you need with the  information encapsulated into the Dynamic Link. For example, following our example, we can output the values of parameters a and b:


Future doSomething(PendingDynamicLinkData dynamicLink) async {
  Map paramsMap = dynamicLink?.link?.queryParameters ?? {};
  print('Parameter a is ${paramsMap['a']} and parameter b is ${paramsMap['b']}');
  // Parameter a is 1 and parameter b is 2
}

This is just the beginning of what is possible with this intriguing bridge technology.

For us, Deep Links has facilitated provision of easy deployment of Apps in the Personal Plan of our no code system, Logiak.

A deep link is generated for each App and this gives the App creator the power to send a mail (or let Logiak send mails) with a link to the simplest deployment conceivable. It basically is: click here, that's it!