Implement Deep Link in Flutter Using Help of web without go_router


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 :)
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