/ Mobile Development

How to create deep link and universal links for Flutter app with Node.js

Creator Image
Bashar Alshaibani
20 Nov 2024 -
10 min Reading time

A complete summary and step-by-step guide to implement universal links (Android/iOS) and dynamic previews for your vocabulary app, starting from setting up assetlinks.json and apple-app-site-association to implementing the backend and Flutter app functionality.


1.1. Android: Configure assetlinks.json

  1. Create a file named assetlinks.json:

    [
      {
        "relation": ["delegate_permission/common.handle_all_urls"],
        "target": {
          "namespace": "android_app",
          "package_name": "com.t9chnih.app",
          "sha256_cert_fingerprints": [
            "YOUR_APP_SHA256_CERT_FINGERPRINT"
          ]
        }
      }
    ]
    
    • Replace com.t9chnih.app with your app's package name.
    • Replace YOUR_APP_SHA256_CERT_FINGERPRINT with your app's SHA256 fingerprint, obtained using:
      keytool -list -v -keystore <keystore-path> -alias <key-alias>
      
  2. Host assetlinks.json at:

    https://www.t9chnih.com/.well-known/assetlinks.json
    
  3. Update AndroidManifest.xml:

    <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" android:host="www.t9chnih.com" android:pathPrefix="/share/vocabulary" />
    </intent-filter>
    

1.2. iOS: Configure apple-app-site-association

  1. Create a file named apple-app-site-association:

    {
      "applinks": {
        "apps": [],
        "details": [
          {
            "appID": "TEAMID.com.t9chnih.app",
            "paths": ["/share/vocabulary/*"]
          }
        ]
      }
    }
    
    • Replace TEAMID with your Apple Developer Team ID.
    • Replace com.t9chnih.vlug with your app’s bundle ID.
  2. Host apple-app-site-association at:

    https://www.t9chnih.com/.well-known/apple-app-site-association
    
  3. Add Associated Domains in Xcode:

    • Open Signing & Capabilities > Add Associated Domains.
    • Add:
      applinks:www.t9chnih.com
      

2. Backend Implementation

2.1. Dynamic Meta Tags for Previews

Modify the /share/vocabulary/:id endpoint to serve Open Graph and Twitter meta tags dynamically.

router.get('/share/vocabulary/:vocabularyId', async (req, res) => {
    const vocabularyId = req.params.vocabularyId;

    const vocabularyEntry = await pool.query(
        'SELECT * FROM vocabulary WHERE vocabulary_id = $1 AND is_deleted = FALSE',
        [vocabularyId]
    );

    if (vocabularyEntry.rowCount === 0) {
        return res.status(404).send('Vocabulary not found');
    }

    const { word, translation, example } = vocabularyEntry.rows[0];
    const imageUrl = `https://www.t9chnih.com/share/vocabulary/${vocabularyId}/image`;

    res.set('Content-Type', 'text/html');
    res.send(`
        <!DOCTYPE html>
        <html>
        <head>
            <title>${word} - Vocabulary</title>
            <meta property="og:title" content="${word}" />
            <meta property="og:description" content="Translation: ${translation}. Example: ${example}" />
            <meta property="og:image" content="${imageUrl}" />
            <meta property="og:url" content="https://www.t9chnih.com/share/vocabulary/${vocabularyId}" />
            <meta property="og:type" content="website" />
            <meta name="twitter:card" content="summary_large_image" />
            <meta name="twitter:title" content="${word}" />
            <meta name="twitter:description" content="Translation: ${translation}. Example: ${example}" />
            <meta name="twitter:image" content="${imageUrl}" />
        </head>
        <body>
            <h1>${word}</h1>
            <p><strong>Translation:</strong> ${translation}</p>
            <p><strong>Example:</strong> ${example}</p>
        </body>
        </html>
    `);
});

2.2. Dynamic Image Generation

Use node-canvas to create images dynamically with vocabulary details.

router.get('/share/vocabulary/:vocabularyId/image', async (req, res) => {
    const vocabularyId = req.params.vocabularyId;

    const vocabularyEntry = await pool.query(
        'SELECT * FROM vocabulary WHERE vocabulary_id = $1 AND is_deleted = FALSE',
        [vocabularyId]
    );

    if (vocabularyEntry.rowCount === 0) {
        return res.status(404).send('Vocabulary not found');
    }

    const { word, translation, example } = vocabularyEntry.rows[0];

    const canvas = createCanvas(1200, 628);
    const ctx = canvas.getContext('2d');
    ctx.fillStyle = '#ffffff';
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    ctx.fillStyle = '#000000';
    ctx.font = 'bold 50px Arial';
    ctx.textAlign = 'center';
    ctx.fillText(word, canvas.width / 2, 200);
    ctx.font = '30px Arial';
    ctx.fillText(`Translation: ${translation}`, canvas.width / 2, 300);
    ctx.font = 'italic 25px Arial';
    ctx.fillText(`Example: ${example}`, canvas.width / 2, 400);

    res.setHeader('Content-Type', 'image/png');
    res.send(canvas.toBuffer());
});

3. Flutter Implementation

3.1. Handle Deep Links

Use the app_links package to handle universal links.

class DeepLinkController extends GetxController {
  final AppLinks _appLinks = AppLinks();

  @override
  void onInit() {
    super.onInit();
    _initDeepLinks();
  }

  void _initDeepLinks() async {
    _appLinks.uriLinkStream.listen((Uri? uri) {
      if (uri != null) _handleDeepLink(uri);
    });

    final Uri? initialUri = await _appLinks.getInitialAppLink();
    if (initialUri != null) _handleDeepLink(initialUri);
  }

  void _handleDeepLink(Uri uri) {
    if (uri.host == 'www.t9chnih.com' && uri.pathSegments.length == 3 && uri.pathSegments[0] == 'share' && uri.pathSegments[1] == 'vocabulary') {
      final vocabId = uri.pathSegments[2];
      Get.toNamed('/vocabularyDetail', arguments: {'vocabId': vocabId});
    }
  }
}

4. Testing

4.1. Android

Test universal links:

adb shell am start -W -a android.intent.action.VIEW -d "https://www.t9chnih.com/share/vocabulary/587" com.t9chnih.vlug

4.2. iOS

Open the link in Safari:

https://www.t9chnih.com/share/vocabulary/587

4.3. Social Media

Use tools to test previews:


This complete guide sets up universal links, dynamic meta tags, and Flutter app handling for your vocabulary app. Let me know if you need further assistance! 🚀