Implement Deep Link in Flutter Using Help of web without go_router

Sabitur RahmanSabitur Rahman
4 min read

Hey back again here , so I will share tutorial about flutter deep link without go_router package. now question is what is deep link so answer is “The use of (custom) URL schemes to direct users to specific pages within an app, enhancing user engagement, retention, and improving conversion rates”. to achieve this stuff i had faced some interesting challenge in navigation section.i used package called app_links.so let’s dive into the implementation

Main Key Point is: user hit web link (optional": with query parameters if app needed) and web browser will open target app

Firstly, I setup web part

Web:

make configuration file in following path <webdomain>/.well-known/assetlinks.json

assetlinks.json

[{
    "relation": ["delegate_permission/common.handle_all_urls"],
    "target": {
      "namespace": "test_deeplink",
      "package_name": "com.example.test_deeplink",
      "sha256_cert_fingerprints":
      ["BD:52:7F:86:E0:B9:12:63:EA:16:1A:B8:62:F2:B9:BD:86:E6:98:6A:1E:1B:04:E9:41:50:34:D4:A4:86:39:51"]
    }
  }]

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Redirecting...</title>
  </head>
  <body>
    <script>
      const PLAY_STORE_APP_ID = 'com.example.test_deeplink';
      const PLAY_STORE_URL = `market://details?id=${PLAY_STORE_APP_ID}`;
      const PLAY_STORE_WEB_URL = `https://play.google.com/store/apps/details?id=${PLAY_STORE_APP_ID}`;
      const PLAY_STORE_INTENT_URL = `intent://details?id=${PLAY_STORE_APP_ID}#Intent;scheme=market;package=com.android.vending;end;`;

      // Function to get query parameters
      function getQueryParams() {
        const params = new URLSearchParams(window.location.search);
        return {
          pageName: params.get('pageName'),
        };
      }

      // Function to construct DeepLinkUrlForTestAp URL with parameters
      function getDeepLinkUrlForTestApp() {
        const params = getQueryParams();
        const queryString = Object.entries(params)
          .filter(([_, value]) => value) // Only include non-null values
          .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
          .join('&');

        return `testdeeplink://deeplink-8d480.web.app/details?${queryString}`;
      }

      // Check if user is on Android device
      const isAndroid = /Android/i.test(navigator.userAgent);

      async function handleRedirect() {

        const currentPath = window.location.pathname;
        //check request come in uniquepath
        const isLoginPath = currentPath.includes('/uniquepath');
        if (isLoginPath && isAndroid) {
          try {

            const loginUrl = getDeepLinkUrlForTestApp();


            window.location.href = loginUrl;


            await new Promise(resolve => setTimeout(resolve, 2000));

            // Try to open Play Store using intent URL for Android 13+
            window.location.href = PLAY_STORE_INTENT_URL;

            await new Promise(resolve => setTimeout(resolve, 2000));

            window.location.href = PLAY_STORE_URL;

            await new Promise(resolve => setTimeout(resolve, 2000));

            window.location.href = PLAY_STORE_WEB_URL;
          } catch (error) {
            window.location.href = PLAY_STORE_WEB_URL;
          }
        }
      }

      handleRedirect();
    </script>
  </body>
</html>

Let's explain why I wrote this logic in the index.html file. In this file, we check if the target app is available on the device. If the app is available, it opens; otherwise, it opens the Play Store with the app's ID. This scenario occurs in production, which is why I created this tutorial. You might wonder why I use an extra uniquepath and check the logic there. The reason is that many users browse your application from a mobile device. We need to determine if the request is for a deep link or general browsing. That's why I created a unique path to check if the request comes from a mobile browser for a deep link, so other general users can't access this endpoint.

so deploy it in server

your request url will be like this https://deeplink-8d480.web.app/uniquepath?pageName=red

In Flutter App:

setup project and install package

dependencies:
  app_links: ^6.4.0

after install package move to androidmanifest.xml file to add following permission and configuration

<meta-data android:name="flutter_deeplinking_enabled" android:value="false" />
<!-- App Link sample -->
<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="testdeeplink" android:host="deeplink-8d480.web.app" />
       <data android:scheme="https" />
</intent-filter>

in main.dart file

import 'package:app_links/app_links.dart';
import 'package:flutter/material.dart';

final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

void main() {
   WidgetsFlutterBinding.ensureInitialized();
  deeplinkinitializer();
  runApp(const MyApp());
}

deeplinkinitializer() {
  final appLinks = AppLinks();
  appLinks.uriLinkStream.listen((uri) {
      _handleUri(uri);
    });
}

 void _handleUri(Uri uri) {
    var pageName=uri.queryParameters["pageName"];

    _navigateToView(pageName??"");
  }
  void _navigateToView(String pageName) {

  if (navigatorKey.currentState == null) {
    Future.delayed(const Duration(microseconds: 10), (){
      _navigateToView(pageName);
    });
    return;
  }

  if(pageName=="red"){
    navigatorKey.currentState?.push(
      MaterialPageRoute(
        builder: (context) => const RedPage(),
      ),
    );
  }
  if(pageName=="green"){
    navigatorKey.currentState?.push(
      MaterialPageRoute(
        builder: (context) => const GreenPage(),
      ),
    );
  }
}


class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      navigatorKey: navigatorKey,
      home: const RedPage(),
    );
  }
}

class RedPage extends StatelessWidget {
  const RedPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.red,
      body: Center(
        child: Container(
          color: Colors.white,
          child: const Text('Red Page'),
        ),
      ),
    );
  }
}

class GreenPage extends StatelessWidget {
  const GreenPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.green,
      body: Center(
        child: Container(
          color: Colors.white,
          child: const Text('Green Page'),
        ),
      ),
    );
  }
}

in app appLinks listener check data from deeplink url and process necessary things.and you will see that i use _navigateToView recursion function to check navigatorykey available or not. this is was the interesting part from me.you can try without recursion function then you got an error that is navigatorkey is null

Output:

Happy Coding :)

0
Subscribe to my newsletter

Read articles from Sabitur Rahman directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Sabitur Rahman
Sabitur Rahman

Software Engineer