Implementing App and Universal Links in Flutter
Tags: flutter, beginners, deeplink, mobile
Published: 2024-04-08 15:06:04

It's no news that, come `August 25, 2025`, `Firebase` will no longer support `dynamic links` for Flutter applications. To that effect, we will need to handle the app link (for Android) and the universal link (for iOS) ourselves. And in this article, we will be doing so with the help of onGenerateRoute.

### Table of Content
- 🎉 [Introduction](#intro)
- ✨ [AppLink Overview](#applink)
- ⚙️  [Updating AndroidManifest.xml](#xml)
- 🍦 [Creating SHA256](#sha)
- 💪 [Creating & Hosting assetlinks.json file](#json)
- 🧪 [Testing and Confirming assetlinks.json](#confirm)
- 🚀 [Deploy app to PlayStore internal testing](#playstore)
- ✅ [Confirm the link is auto verified in the device hardware app settings](#auto)

- 🌐 [Universal Link Overview](#universal)
- 👮🏾‍♀️ [Adding capability](#capability)
- 📡 [Creating & Hosting AASA file](#aasa)
- 🔌 [Confirming Our AASA file](#confirmAASA)
- ⚒️ [Testing Via cmd xcrun ](#testOnIos)

- ⛓️ [OnGenerateRoute Setup and Routing to a dynamic page with data ](#onGen)

### 🎉 Introduction <a name="intro"></a>
Deep or dynamic linking is a great feature that Flutter offers in its application to enable a continuance in data consumption from a website to a mobile application. 

For instance, say you have an e-commerce website with products to be sold or an online campaign website where users need to vote or be voted for. A user could share a link to that product/profile with another user, who then clicks on the link to be led to the website to continue their business. In a situation where you have both a website and a mobile app and you want the users to still continue their business through the mobile interface, that is where a deep or dynamic link comes in. 

NB: In situations where the shared user does not have the app installed, they'd be redirected to their respective device's stores (Play or Apple Store) to download and continue with the application.

PS: In this article, we will be handling the `applink` and `universal link` for Android and iOS, respectively.

### ✨ AppLink Overview <a name="applink"></a>
We will be following the official [Android Guidelines](https://developer.android.com/training/app-links#android-app-links).


### ⚙️ Updating AndroidManifest.xml <a name="xml"></a>
Navigate to your `AndroidManifest.xml` file and add the below intent inside your `<android>` block.

```dart
<meta-data android:name="flutter_deeplinking_enabled" android:value="true" /> 
<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" />

    <data android:scheme="https" />
    <data android:scheme="http" />
    <data android:host="gikwegbu.com" />
    <data android:host="www.gikwegbu.com" />
</intent-filter>
```

### 
    NB: You need `flutter_deeplinking_enabled` for it to work; that way, the route can be passed from the Android routing system into our Flutter application.

    NB: Please update the `gikwegbu` host with your personal domain name.

### 🍦 Creating SHA256 <a name="sha"></a>

We will need to create an `SHA256` for our application. To do that, open your `root` project in the `terminal` and run the below code:

```dart
keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android
```
Then copy the `SHA256` value that comes up.


### 💪 Creating & Hosting AssetLink.json file <a name="json"></a>

You can either use the [Statement List Generator and Tester](https://developers.google.com/digital-asset-links/tools/generator) link to `Generate your statement` and in future, `Test your statement`. Or simply update the below json file with your own details;

NB: This code `MUST` be saved in the `assetLinks.json` file.

```json
[{
    "relation": ["delegate_permission/common.handle_all_urls"],
    "target": {
      "namespace": "android_app",
      "package_name": "com.gikwegbu.app",
      "sha256_cert_fingerprints":
      ["PASTE YOUR OWN SHA256 HERE"]
    }
  }]
  
```

NB: Update the `package_name` with your app's own.


Congratulations if you've gotten to this place so far; you're a genius. But before we host our `json` file, we need to do something first. I assume you already have a `PlayStore` account and an `APP` created for your currently built application. If you don't, please pause and do so.

There is every tendency that your app already exists in the store, and you just want to include this feature, which is perfect. All I need you to do is get the `SHA256` google Auto-created for you application in the `integrity` section of the console and update the `sha256_cert_fingerprints` in your `AssetLink.json`. Remember, it's an array of `SHA256` values, so just a `comma` and the new `SHA256` value from `PlayStore console`.

```dart
    [{
      "relation": 
      ["delegate_permission/common.handle_all_urls"],
       "target": {
      "namespace": "android_app",
      "package_name": "com.gikwegbu.app",
      "sha256_cert_fingerprints":
      ["PASTE YOUR OWN SHA256 HERE", "PASTE GOOGLE PLAY CONSOLE SHA256 HERE TOO"]
    }
  }]
  
```

#### Hosting your AssetLink.json file;
For this, you will have to reach out to the person managing the website, as we need to upload our file to a specific directory.

```dart
http://gikwegbu.com/.well-known/assetlinks.json
```

NB: It is very important that you place `assetlinks.json` inside the `.well-known/` directory.

NB: The domain must match what you provided in your `AndroidManifest.xml` `android:host` data as well.

NB: Once it has been hosted, make sure it's accessible by visiting `http://gikwegbu.com/.well-known/assetlinks.json`. If it's accessible, you'll see the `json` data displayed.


![Accessible assetlink.json](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ciy1b36o129zaossdn1b.png)

### 🧪 Testing and Confirming AssetLink.json file <a name="confirm"></a>

To test if your `assetlink.json` will be OK, provide the required details in the [Statement List Generator and Tester](https://developers.google.com/digital-asset-links/tools/generator), then click on `Test Statement` to confirm. 

This will crawl your website, to confirm the `assetlink.json` file is present, and the `SHA256` keys are also valid.

As a bonus, try inputing the `SHA256` you got from the `Google Play Console`, to confirm it works too.


![Statement List Generator](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rbcnmnqumpg9jrfvqml7.png)

### 🚀 Deploy app to PlayStore internal testing <a name="playstore"></a>

Based on my earlier assumption that you just created the app on your `Google Console`, we would be deploying to `internal testing`, just so we could test out our `deep links` before releasing them to the actual store. 

NB: This article won't cover how to build and upload your `appbundle` to the store.

PS: If you have already released your app, no problem, just continue with the next step. 

On your `Google Play Console`, go to the `Deep Links` on `Side Navigation`, If everything we've done so far has worked out well, you should get a pretty dashboard like below.


![Deep Link Get started](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d6lw7v06byyonb7lcdxm.png)
Getting Started


![Complete File upload](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4k0if3yw1ua5hkj0dg6t.png)
Successful Upload

NB: If there's still an issue, please reach out to me, and I'll be glad to help out.

NB: Once you have uploaded the `appbundle` to your internal testing, wait for a while (say an hour; honestly, I don't know how long it takes for Google to verify your assetlink and web domains) before installing it.

NB: When you have installed it on your device, go to your `flutter app's` details, select `Set as default`, > `Supported web addresses` and see if the `host` you provided in the `AndroidManifest.xml` is/are present there.


![Your flutter app settings](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xzqju82vahguvvzevndq.png)
Your flutter app settings


![Set as default](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/24n4ihwjio10mzeq3j79.png)
Set as default


![Approved Hosts](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rxuwalm5g1v6k10zdpn6.png)
Approved Hosts

### ✅ Confirm the link is auto verified in the device hardware app settings <a name="auto"></a>

Because in our `AndroidManifest.xml`, we set the `<intent-filter android:autoVerify="true">`, therefore, you shouldn't see the `toggle/switch` button beside your `host`.
![Approved Hosts](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rxuwalm5g1v6k10zdpn6.png)
Approved Hosts

Whoop Whoooop!!!! We just finished Applink on our Android.

![Celebration Gif](https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExYXJkbW5qeThyeXlxbHkxYzJqbW1yZnVvZ2h0b3Bob203NzdneTl1OCZlcD12MV9naWZzX3NlYXJjaCZjdD1n/t3sZxY5zS5B0z5zMIz/giphy.gif)

PS: Stick around to the end of the article to learn how the routing works with the `onGenerateRoute` configs in `Flutter`.

### 🌐 Universal Link Overview <a name="universal"></a>

For our iOS application, we will be using a `Universal link`, which is an exclusive type of deep link to Apple devices that uses either the `http` or the `https` schemes.

### 👮🏾‍♀️ Adding capability <a name="capability"></a>
In the next few steps, we will be working closely with `XCode` via the following steps: 

- Open your project in `XCode`,
- In your `Project Navigator`, click on the `Runner`.
- Click the `Runner` in the `TARGETS` section.
- Select `Signing & Capabilities`.
- Click on the `+ Capability`,
- When the `Modal` pops up, search for `Associated Domains` and add it.


![Getting to capabilities modal in XCode](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1tvnhdn3assmbz350ngl.png)


![Searching for the Associated Domain inside capabilities in XCode](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vfe1o062bu8hzu4j7f2r.png)


After adding the `Associated Domains`, update the current `web credential` `Domains` with;

```dart
applinks:gikwegbu.com // first input
applinks:www.gikwegbu.com // second input
```

NB: I like adding more options so my app will be recognized when users try to access it with or without the `www`.


![AppLinks values](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6zqyq0no47wr5xrzx0ob.png)

NB: While the above is the easiest and UI approach of adding the `Runner.entitlements` to your app, you could just create. a `Runner.entitlements` file within your `ios/Runner/` directory, ad add the below code manually:

```dart
<?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>
	<key>com.apple.developer.associated-domains</key>
	<array>
		<string>applinks:gikwegbu.com</string>
		<string>applinks:www.gikwegbu.com</string>
	</array>
</dict>
</plist>

```

NB: `gikwegbu.com` should be your own `domain`.

From our `Step 3` above, whilst still on the `Runner` in the `TARGET` section:

- Click on the `info` tab.
- Open the `URL Types` dropdown and complete the form with below data:

`identifier` : your app's bundle Identifier, 
`URL Schemes`: https.


NB: You can get you `bundle Identifier` from your `Signing & Capabilities` tab in `XCode`.


![AppLinks values](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6zqyq0no47wr5xrzx0ob.png)

####
    NB: For the iOS routing system to successfully parse the dynamic url back into our Flutter application, we need to add the below data into our info.plist file;


```dart
<key>FlutterDeepLinkingEnabled</key>
<true/>
```


### 📡 Creating & Hosting AASA file <a name="aasa"></a>

Open your `project root` in the `terminal` and run the below code:

```dart
$ touch apple-app-site-association
```

Then copy and paste  the below code, with your own `data`;

```dart
{   
  "applinks": {       
    "apps": [],
       
    "details": [{           
        "appID": "[TEAM_ID].[APP_BUNDLE_ID]",           
        "paths": ["[PATH_FOR_REDIRECTION]"]
    }]
  }
}
```

NB: To get your `Team_ID`, just open your [apple dev account](https://developer.apple.com/account/resources/certificates/list), you'd see the ID by the `top right`, `underneath your name` of your screen, 

###
    "paths": [
           "/your_path", // Users will be be redirected if they visit <https://gikwegbu.com/your_path>
           "/your_path/*", // Users will be redirected if they visit  <https://gikwegbu.com/your_path/[shduewjakssd]>
           "*", // will be redirected to any link <https://gikwegbu.com/[wild card links]>
           "NOT /your_path", // Users will be be redirected  to any link other than <https://gikwegbu.com/your_path>
           "NOT /your_path/*", // Users be redirected to any link other than <https://gikwegbu.com/your_path/[wild card]>
]


Once you're done with the above, host the AASA file on your domain, within the `.well-known/` directory, just like you did with your `Android` `assetlinks.json` file.



### 🔌 Confirming Our AASA file<a name="confirmAASA"></a>

To confirm if the AASA file was properly hosted, once you visit the `http://gikwegbu.com/.well-known/apple-app-site-association`, the file will automatically be downloaded onto your machine.

NB: `gikwegbu` will be your own domain.


### ⚒️ Testing Via cmd xcrun <a name="testOnIos"></a>

First make sure your `XCode` is running your `Flutter app`, Open your `terminal` and run the below code:

```dart
$ xcrun simctl openurl booted https://gikwegbu.com/guest/dsewe
```

###
    NB: Since we haven't worked on the Routing system in our Flutter app, the above command should simply launch your app.

### ⛓️ OnGenerateRoute Setup and Routing to a dynamic page with data<a name="onGen"></a>

For an efficient and cleaner codebase, I like to keep all my routes in a separate file.

Within your `MaterialApp`, in your `main.dart` file, you can add your `onGenerateRoute` data and point it to the external `routes` file.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1fnaibp0gt351pfr1k22.png)

Image of the main.dart file

Here, we will create a class called `Routes`;

```dart
class Routes {
  static Route<dynamic> generateRoute(RouteSettings settings) {
    debugPrint('${settings.name} Is the current route' );

    if (settings.name!.contains('guest')) {
      final page = PreInviteInfoScreen(
        code: settings.name!.split('/').last,
      );
      return MaterialPageRoute(builder: (_) => page);
    }

    final args = settings.arguments;  // For screens that requires arguements to be passed
    switch (settings.name) {
      case HomeView.routeName:
        const page = HomeView();
        return MaterialPageRoute(builder: (_) => page);
      default:
        const page = HomeView();
        return MaterialPageRoute(builder: (_) => page);
    }
  }
}

```

NB: For more context, say the  link our users clicks on is `https://www.gikwegbu.com/guest/Uhdys`,  the `if statement`, will check if that route has the `guest` keyword, then extract to get the code as it a `URL PATH`.

This way, when a user clicks on the link above,  only the `Uhdys` is now being passed as the `argument` for the next screen immediately the app launches.


## THE END

![Congratulations](https://media.giphy.com/media/26h0pHNtHKjmDo4WQ/giphy.gif?cid=790b7611ardmnjy8ryyqly1c2jmmrfuoghtophom777gy9u8&ep=v1_gifs_search&rid=giphy.gif&ct=g)

So with the above sets of instructions and examples, you should be able to get the Applink and Universal link ready for your FLutter application. CONGRATULATIONS AND GOOD LUCK.