Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
197 changes: 195 additions & 2 deletions concepts/deep-links/attribution.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ description: "Learn how to use deep link attribution to track conversions events
import EnableConversionTracking from "/snippets/enable-conversion-tracking.mdx";
import GeneratePublishableKeyStep from "/snippets/steps/generate-publishable-key.mdx";
import AllowlistDomainsStep from "/snippets/steps/allowlist-domains.mdx";
import InstallationGuides from "/snippets/dub-client-mobile-installation-guides.mdx";
import InstallationGuides from "/snippets/dub-client-ios-installation-guides.mdx";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

🧩 Analysis chain

Verify snippet import path exists

Ensure /snippets/dub-client-ios-installation-guides.mdx is present (name suggests iOS‑only; you may have renamed it).

Run:


🏁 Script executed:

#!/bin/bash
fd -a 'dub-client-*-installation-guides.mdx' | sed -n '1,200p'

Length of output: 0


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "=== attribution.mdx (first 200 lines) ==="
if [ -f concepts/deep-links/attribution.mdx ]; then
  sed -n '1,200p' concepts/deep-links/attribution.mdx
else
  echo "MISSING: concepts/deep-links/attribution.mdx"
fi

echo
echo "=== snippets directory listing ==="
if [ -d snippets ]; then
  ls -la snippets | sed -n '1,200p'
else
  echo "MISSING: snippets/ directory"
fi

echo
echo "=== Searching repo for matching snippet filenames and references ==="
rg -n --hidden --no-ignore-vcs -S 'dub-client-.*installation-guides.*\.mdx' || true
rg -n --hidden --no-ignore-vcs -S 'installation-guides' || true
rg -n --hidden --no-ignore-vcs -S 'import InstallationGuides' || true

Length of output: 11766


Broken snippet import — update import path

concepts/deep-links/attribution.mdx imports "/snippets/dub-client-ios-installation-guides.mdx" (line 10) but that file does not exist; replace with "/snippets/dub-client-mobile-installation-guides.mdx" (present) or with "/snippets/dub-client-ios-install.mdx" if you intended an iOS‑only snippet.
Location: concepts/deep-links/attribution.mdx:10

🤖 Prompt for AI Agents
In concepts/deep-links/attribution.mdx around line 10, the import path
"/snippets/dub-client-ios-installation-guides.mdx" points to a non-existent
file; replace it with the correct snippet path. Update the import to either
"/snippets/dub-client-mobile-installation-guides.mdx" if you want the mobile
installation guides, or "/snippets/dub-client-ios-install.mdx" if you intended
an iOS-only snippet; make the change on line 10 so the file imports an existing
snippet.

import InstallIosSdkStep from "/snippets/steps/install-ios-sdk.mdx";
import InstallReactNativeSdkStep from "/snippets/steps/install-react-native-sdk.mdx";
import InitializeIosSdkStep from "/snippets/steps/initialize-ios-sdk.mdx";
import InitializeReactNativeSdkStep from "/snippets/steps/initialize-react-native-sdk.mdx";
import ViewConversions from "/snippets/view-conversions.mdx";

<Note>
Expand Down Expand Up @@ -52,7 +54,19 @@ Once these are set up, we can start tracking conversion events for your deep lin
## Step 1: Install the client-side Mobile SDK

<Tabs>
<Tab title="iOS (SwiftUI)">
<Tab title="React Native">

Install the [Dub React Native SDK](/sdks/client-side-mobile/installation-guides/react-native) and initialize it with your publishable key and short link domain.

<Steps titleSize="h3">

<InstallReactNativeSdkStep />

<InitializeReactNativeSdkStep />

</Steps>
</Tab>
<Tab title="iOS">

Install the [Dub iOS SDK](/sdks/client-side-mobile/installation-guides/swift) and initialize it with your publishable key and short link domain.

Expand All @@ -74,6 +88,60 @@ Call `trackOpen` on the `dub` instance to track deep link and deferred deep link

<CodeGroup>

```typescript React Native expandable
import { useState, useEffect, useRef } from "react";
import { Linking } from "react-native";
import AsyncStorage from "@react-native-async-storage/async-storage";
import dub from "@dub/react-native";

export default function App() {
useEffect(() => {
dub.init({
publishableKey: "<DUB_PUBLISHABLE_KEY>",
domain: "<DUB_DOMAIN>",
});

// Check if this is first launch
const isFirstLaunch = await AsyncStorage.getItem("is_first_launch");

if (isFirstLaunch === null) {
await handleFirstLaunch();
await AsyncStorage.setItem("is_first_launch", "false");
} else {
// Handle initial deep link url (Android only)
const url = await Linking.getInitialURL();

if (url) {
await handleDeepLink(url);
}
}

const linkingListener = Linking.addEventListener("url", (event) => {
handleDeepLink(event.url);
});

return () => {
linkingListener.remove();
};
}, []);

const handleFirstLaunch = async (
deepLinkUrl?: string | null | undefined
): Promise<void> => {
try {
const response = await dub.trackOpen(deepLinkUrl);

const destinationURL = response.link?.url;
// Navigate to the destination URL
} catch (error) {
// Handle error
}
};

// Return your app...
}
```
Comment on lines +91 to +143
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

RN example: async useEffect and undefined handleDeepLink

Same issues as other RN snippets: await in non‑async useEffect and missing handleDeepLink.

Apply this diff to the effect:

   useEffect(() => {
-    dub.init({
-      publishableKey: "<DUB_PUBLISHABLE_KEY>",
-      domain: "<DUB_DOMAIN>",
-    });
-    // Check if this is first launch
-    const isFirstLaunch = await AsyncStorage.getItem("is_first_launch");
-    if (isFirstLaunch === null) {
-      await handleFirstLaunch();
-      await AsyncStorage.setItem("is_first_launch", "false");
-    } else {
-      // Handle initial deep link url (Android only)
-      const url = await Linking.getInitialURL();
-      if (url) {
-        await handleDeepLink(url);
-      }
-    }
-    const linkingListener = Linking.addEventListener("url", (event) => {
-      handleDeepLink(event.url);
-    });
-    return () => {
-      linkingListener.remove();
-    };
+    const init = async () => {
+      dub.init({
+        publishableKey: "<DUB_PUBLISHABLE_KEY>",
+        domain: "<DUB_DOMAIN>",
+      });
+      const isFirstLaunch = await AsyncStorage.getItem("is_first_launch");
+      if (isFirstLaunch === null) {
+        await handleFirstLaunch();
+        await AsyncStorage.setItem("is_first_launch", "false");
+      } else {
+        const url = await Linking.getInitialURL();
+        if (url) await handleDeepLink(url);
+      }
+    };
+    init();
+    const linkingListener = Linking.addEventListener("url", (event) => {
+      handleDeepLink(event.url);
+    });
+    return () => linkingListener.remove();
   }, []);

And add handleDeepLink:

   const handleFirstLaunch = async (
@@
   };
+
+  const handleDeepLink = async (deepLinkUrl: string): Promise<void> => {
+    try {
+      const response = await dub.trackOpen(deepLinkUrl);
+      const destinationURL = response.link?.url;
+      // Navigate to the destination URL
+    } catch (error) {
+      // Handle error
+    }
+  };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
```typescript React Native expandable
import { useState, useEffect, useRef } from "react";
import { Linking } from "react-native";
import AsyncStorage from "@react-native-async-storage/async-storage";
import dub from "@dub/react-native";
export default function App() {
useEffect(() => {
dub.init({
publishableKey: "<DUB_PUBLISHABLE_KEY>",
domain: "<DUB_DOMAIN>",
});
// Check if this is first launch
const isFirstLaunch = await AsyncStorage.getItem("is_first_launch");
if (isFirstLaunch === null) {
await handleFirstLaunch();
await AsyncStorage.setItem("is_first_launch", "false");
} else {
// Handle initial deep link url (Android only)
const url = await Linking.getInitialURL();
if (url) {
await handleDeepLink(url);
}
}
const linkingListener = Linking.addEventListener("url", (event) => {
handleDeepLink(event.url);
});
return () => {
linkingListener.remove();
};
}, []);
const handleFirstLaunch = async (
deepLinkUrl?: string | null | undefined
): Promise<void> => {
try {
const response = await dub.trackOpen(deepLinkUrl);
const destinationURL = response.link?.url;
// Navigate to the destination URL
} catch (error) {
// Handle error
}
};
// Return your app...
}
```
import { useState, useEffect, useRef } from "react";
import { Linking } from "react-native";
import AsyncStorage from "@react-native-async-storage/async-storage";
import dub from "@dub/react-native";
export default function App() {
useEffect(() => {
const init = async () => {
dub.init({
publishableKey: "<DUB_PUBLISHABLE_KEY>",
domain: "<DUB_DOMAIN>",
});
const isFirstLaunch = await AsyncStorage.getItem("is_first_launch");
if (isFirstLaunch === null) {
await handleFirstLaunch();
await AsyncStorage.setItem("is_first_launch", "false");
} else {
const url = await Linking.getInitialURL();
if (url) await handleDeepLink(url);
}
};
init();
const linkingListener = Linking.addEventListener("url", (event) => {
handleDeepLink(event.url);
});
return () => linkingListener.remove();
}, []);
const handleFirstLaunch = async (
deepLinkUrl?: string | null | undefined
): Promise<void> => {
try {
const response = await dub.trackOpen(deepLinkUrl);
const destinationURL = response.link?.url;
// Navigate to the destination URL
} catch (error) {
// Handle error
}
};
const handleDeepLink = async (deepLinkUrl: string): Promise<void> => {
try {
const response = await dub.trackOpen(deepLinkUrl);
const destinationURL = response.link?.url;
// Navigate to the destination URL
} catch (error) {
// Handle error
}
};
// Return your app...
}
🤖 Prompt for AI Agents
In concepts/deep-links/attribution.mdx around lines 91–143, the useEffect
currently uses top-level await and references a missing handleDeepLink; fix by
making the effect synchronous and declaring an inner async function (e.g., async
function init()) that calls dub.init, reads
AsyncStorage.getItem("is_first_launch"), conditionally calls handleFirstLaunch
or Linking.getInitialURL()/handleDeepLink, and sets AsyncStorage when
appropriate, then invoke init() from useEffect; also add a handleDeepLink async
function (similar to handleFirstLaunch) that accepts a url string | null |
undefined, calls dub.trackOpen(url), extracts response.link?.url for navigation,
and handles errors; ensure the Linking event subscription is created and cleaned
up correctly (store subscription and call .remove() in the cleanup) and
await/await usage is confined to the inner async function and helper functions.


```swift iOS (SwiftUI) expandable
// ContentView.swift
import SwiftUI
Expand Down Expand Up @@ -121,6 +189,71 @@ struct ContentView: View {
}
```

```swift iOS (UIKit) expandable
import UIKit
import Dub

@main
class AppDelegate: UIResponder, UIApplicationDelegate {

var window: UIWindow?

private let dubPublishableKey = "<DUB_PUBLISHABLE_KEY>"
private let dubDomain = "<DUB_DOMAIN>"

private var isFirstLaunch: Bool {
get {
UserDefaults.standard.object(forKey: "is_first_launch") as? Bool ?? true
}
set {
UserDefaults.standard.set(newValue, forKey: "is_first_launch")
}
}

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
Dub.setup(publishableKey: dubPublishableKey, domain: dubDomain)

// Track first launch
if isFirstLaunch {
trackOpen()
isFirstLaunch = false
}

// Override point for customization after application launch.
return true
}

func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
handleDeepLink(url: url)
return true
}

func handleDeepLink(url: URL) {
trackOpen(deepLink: url)
}

private func trackOpen(deepLink: URL? = nil) {
// Call the tracking endpoint with the full deep link URL
Task {
do {
let response = try await Dub.shared.trackOpen(deepLink: deepLink)

print(response)

// Navigate to final link via link.url
guard let destinationUrl = response.link?.url else {
return
}

// Navigate to the destination URL
} catch let error as DubError {
print(error.localizedDescription)
}
}
}
}
```

</CodeGroup>

If the deep link was successfully resolved and correlated to the original click, the `response` object will contain the destination URL, which you can use to navigate the user to the appropriate screen.
Expand All @@ -133,6 +266,36 @@ You may track conversion events directly in your app with the `trackLead` and `t

<CodeGroup>

```typescript React Native expandable
import dub from "@dub/react-native";

function trackLead(user: User) {
try {
await dub.trackLead({
eventName: "User Sign Up",
customerExternalId: user.id,
customerName: user.name,
customerEmail: user.email,
});
} catch (error) {
// Handle sale tracking error
}
}

function trackSale(user: User, product: Product) {
try {
await dub.trackSale({
customerExternalId: user.id,
amount: product.price.amount,
currency: "usd",
eventName: "Purchase",
});
} catch (error) {
// Handle sale tracking error
}
}
```

```swift iOS (SwiftUI) expandable
// ContentView.swift
import SwiftUI
Expand Down Expand Up @@ -193,6 +356,36 @@ struct ContentView: View {
}
```

```swift iOS (UIKit) expandable
// ViewController.swift
import UIKit
import Dub

class ViewController: UIViewController {
// View controller lifecycle

private func trackLead(customerExternalId: String, name: String, email: String) {
Task {
do {
let response = try await dub.trackLead(customerExternalId: customerExternalId, name: name, email: email)
} catch let error as DubError {
print(error.localizedDescription)
}
}
}

private func trackSale(customerExternalId: String, amount: Int, currency: String = "usd", eventName: String? = "Purchase", customerName: String? = nil, customerEmail: String? = nil, customerAvatar: String? = nil) {
Task {
do {
let response = try await dub.trackSale(customerExternalId: customerExternalId, amount: amount, currency: currency, eventName: eventName, customerName: customerName, customerEmail: customerEmail, customerAvatar: customerAvatar)
} catch let error as DubError {
print(error.localizedDescription)
}
}
}
}
```
Comment on lines +359 to +387
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

UIKit conversion examples reference undefined dub instance

Use Dub.shared in UIKit instead of dub.

Apply this diff:

-                let response = try await dub.trackLead(customerExternalId: customerExternalId, name: name, email: email)
+                let response = try await Dub.shared.trackLead(customerExternalId: customerExternalId, name: name, email: email)
@@
-                let response = try await dub.trackSale(customerExternalId: customerExternalId, amount: amount, currency: currency, eventName: eventName, customerName: customerName, customerEmail: customerEmail, customerAvatar: customerAvatar)
+                let response = try await Dub.shared.trackSale(customerExternalId: customerExternalId, amount: amount, currency: currency, eventName: eventName, customerName: customerName, customerEmail: customerEmail, customerAvatar: customerAvatar)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
```swift iOS (UIKit) expandable
// ViewController.swift
import UIKit
import Dub
class ViewController: UIViewController {
// View controller lifecycle
private func trackLead(customerExternalId: String, name: String, email: String) {
Task {
do {
let response = try await dub.trackLead(customerExternalId: customerExternalId, name: name, email: email)
} catch let error as DubError {
print(error.localizedDescription)
}
}
}
private func trackSale(customerExternalId: String, amount: Int, currency: String = "usd", eventName: String? = "Purchase", customerName: String? = nil, customerEmail: String? = nil, customerAvatar: String? = nil) {
Task {
do {
let response = try await dub.trackSale(customerExternalId: customerExternalId, amount: amount, currency: currency, eventName: eventName, customerName: customerName, customerEmail: customerEmail, customerAvatar: customerAvatar)
} catch let error as DubError {
print(error.localizedDescription)
}
}
}
}
```
// ViewController.swift
import UIKit
import Dub
class ViewController: UIViewController {
// View controller lifecycle
private func trackLead(customerExternalId: String, name: String, email: String) {
Task {
do {
let response = try await Dub.shared.trackLead(customerExternalId: customerExternalId, name: name, email: email)
} catch let error as DubError {
print(error.localizedDescription)
}
}
}
private func trackSale(customerExternalId: String, amount: Int, currency: String = "usd", eventName: String? = "Purchase", customerName: String? = nil, customerEmail: String? = nil, customerAvatar: String? = nil) {
Task {
do {
let response = try await Dub.shared.trackSale(customerExternalId: customerExternalId, amount: amount, currency: currency, eventName: eventName, customerName: customerName, customerEmail: customerEmail, customerAvatar: customerAvatar)
} catch let error as DubError {
print(error.localizedDescription)
}
}
}
}
🤖 Prompt for AI Agents
In concepts/deep-links/attribution.mdx around lines 359 to 387, the UIKit
examples call an undefined variable dub; update those calls to use the shared
singleton (Dub.shared) instead (e.g., Dub.shared.trackLead(...) and
Dub.shared.trackSale(...)) so the examples compile; keep imports as-is and only
replace occurrences of dub with Dub.shared inside the Task blocks.


</CodeGroup>

Alternatively, you can [track conversion events server-side](/conversions/quickstart#step-3%3A-install-the-dub-server-side-sdk-%2B-track-conversion-events) for [lead events](/conversions/leads/introduction) and [sale events](/conversions/sales/introduction) by sending the `clickId` resolved from the deep link to your backend and then calling off to either:
Expand Down
130 changes: 118 additions & 12 deletions concepts/deep-links/quickstart.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -189,13 +189,67 @@ When a user opens your app from a deep link, you need to handle two main scenari
When your app is already installed, the deep link will open your app directly.
You may handle the deep link manually or with the supported mobile SDKs.

**Option 1**: Handle the deep link using a supported [Dub Mobile SDK](/sdks/client-side-mobile/introduction) (iOS only)
**Option 1**: Handle the deep link using a supported [Dub Mobile SDK](/sdks/client-side-mobile/introduction) (iOS & React Native only)

Follow our [Swift Installation Guide](/sdks/client-side-mobile/installation-guides/swift) to get started.
Follow our installation guide for [Swift](/sdks/client-side-mobile/installation-guides/swift) or [React Native](/sdks/client-side-mobile/installation-guides/react-native) to get started.

<CodeGroup>

```swift iOS (SwiftUI)
```typescript React Native expandable
import { useState, useEffect, useRef } from "react";
import { Linking } from "react-native";
import AsyncStorage from "@react-native-async-storage/async-storage";
import dub from "@dub/react-native";

export default function App() {
useEffect(() => {
dub.init({
publishableKey: "<DUB_PUBLISHABLE_KEY>",
domain: "<DUB_DOMAIN>",
});

// Check if this is first launch
const isFirstLaunch = await AsyncStorage.getItem("is_first_launch");

if (isFirstLaunch === null) {
await handleFirstLaunch();
await AsyncStorage.setItem("is_first_launch", "false");
} else {
// Handle initial deep link url (Android only)
const url = await Linking.getInitialURL();

if (url) {
await handleDeepLink(url);
}
}

const linkingListener = Linking.addEventListener("url", (event) => {
handleDeepLink(event.url);
});

return () => {
linkingListener.remove();
};
}, []);

const handleFirstLaunch = async (
deepLinkUrl?: string | null | undefined
): Promise<void> => {
try {
const response = await dub.trackOpen(deepLinkUrl);

const destinationURL = response.link?.url;
// Navigate to the destination URL
} catch (error) {
// Handle error
}
};

// Return your app...
}
```
Comment on lines +198 to +250
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

RN example: async useEffect and undefined handleDeepLink

Same fixes as above: wrap awaits in async init and add handleDeepLink.

Apply this diff to the effect:

   useEffect(() => {
-    dub.init({
-      publishableKey: "<DUB_PUBLISHABLE_KEY>",
-      domain: "<DUB_DOMAIN>",
-    });
-    // Check if this is first launch
-    const isFirstLaunch = await AsyncStorage.getItem("is_first_launch");
-    if (isFirstLaunch === null) {
-      await handleFirstLaunch();
-      await AsyncStorage.setItem("is_first_launch", "false");
-    } else {
-      // Handle initial deep link url (Android only)
-      const url = await Linking.getInitialURL();
-      if (url) {
-        await handleDeepLink(url);
-      }
-    }
-    const linkingListener = Linking.addEventListener("url", (event) => {
-      handleDeepLink(event.url);
-    });
-    return () => {
-      linkingListener.remove();
-    };
+    const init = async () => {
+      dub.init({
+        publishableKey: "<DUB_PUBLISHABLE_KEY>",
+        domain: "<DUB_DOMAIN>",
+      });
+      const isFirstLaunch = await AsyncStorage.getItem("is_first_launch");
+      if (isFirstLaunch === null) {
+        await handleFirstLaunch();
+        await AsyncStorage.setItem("is_first_launch", "false");
+      } else {
+        const url = await Linking.getInitialURL();
+        if (url) await handleDeepLink(url);
+      }
+    };
+    init();
+    const linkingListener = Linking.addEventListener("url", (event) => {
+      handleDeepLink(event.url);
+    });
+    return () => linkingListener.remove();
   }, []);

And add handleDeepLink:

   const handleFirstLaunch = async (
@@
   };
+
+  const handleDeepLink = async (deepLinkUrl: string): Promise<void> => {
+    try {
+      const response = await dub.trackOpen(deepLinkUrl);
+      const destinationURL = response.link?.url;
+      // Navigate to the destination URL
+    } catch (error) {
+      // Handle error
+    }
+  };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
```typescript React Native expandable
import { useState, useEffect, useRef } from "react";
import { Linking } from "react-native";
import AsyncStorage from "@react-native-async-storage/async-storage";
import dub from "@dub/react-native";
export default function App() {
useEffect(() => {
dub.init({
publishableKey: "<DUB_PUBLISHABLE_KEY>",
domain: "<DUB_DOMAIN>",
});
// Check if this is first launch
const isFirstLaunch = await AsyncStorage.getItem("is_first_launch");
if (isFirstLaunch === null) {
await handleFirstLaunch();
await AsyncStorage.setItem("is_first_launch", "false");
} else {
// Handle initial deep link url (Android only)
const url = await Linking.getInitialURL();
if (url) {
await handleDeepLink(url);
}
}
const linkingListener = Linking.addEventListener("url", (event) => {
handleDeepLink(event.url);
});
return () => {
linkingListener.remove();
};
}, []);
const handleFirstLaunch = async (
deepLinkUrl?: string | null | undefined
): Promise<void> => {
try {
const response = await dub.trackOpen(deepLinkUrl);
const destinationURL = response.link?.url;
// Navigate to the destination URL
} catch (error) {
// Handle error
}
};
// Return your app...
}
```
import { useState, useEffect, useRef } from "react";
import { Linking } from "react-native";
import AsyncStorage from "@react-native-async-storage/async-storage";
import dub from "@dub/react-native";
export default function App() {
useEffect(() => {
const init = async () => {
dub.init({
publishableKey: "<DUB_PUBLISHABLE_KEY>",
domain: "<DUB_DOMAIN>",
});
const isFirstLaunch = await AsyncStorage.getItem("is_first_launch");
if (isFirstLaunch === null) {
await handleFirstLaunch();
await AsyncStorage.setItem("is_first_launch", "false");
} else {
const url = await Linking.getInitialURL();
if (url) await handleDeepLink(url);
}
};
init();
const linkingListener = Linking.addEventListener("url", (event) => {
handleDeepLink(event.url);
});
return () => linkingListener.remove();
}, []);
const handleFirstLaunch = async (
deepLinkUrl?: string | null | undefined
): Promise<void> => {
try {
const response = await dub.trackOpen(deepLinkUrl);
const destinationURL = response.link?.url;
// Navigate to the destination URL
} catch (error) {
// Handle error
}
};
const handleDeepLink = async (deepLinkUrl: string): Promise<void> => {
try {
const response = await dub.trackOpen(deepLinkUrl);
const destinationURL = response.link?.url;
// Navigate to the destination URL
} catch (error) {
// Handle error
}
};
// Return your app...
}


```swift iOS (SwiftUI) expandable
// ContentView.swift
import SwiftUI
import Dub
Expand Down Expand Up @@ -242,16 +296,68 @@ struct ContentView: View {
}
```

```swift iOS (UIKit)
// AppDelegate.swift
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
handleDeepLink(url: url)
return true
}
```swift iOS (UIKit) expandable
import UIKit
import Dub

func handleDeepLink(url: URL) {
// Call the tracking endpoint with the full deep link URL
trackDeepLinkClick(deepLink: url.absoluteString)
@main
class AppDelegate: UIResponder, UIApplicationDelegate {

var window: UIWindow?

private let dubPublishableKey = "<DUB_PUBLISHABLE_KEY>"
private let dubDomain = "<DUB_DOMAIN>"

private var isFirstLaunch: Bool {
get {
UserDefaults.standard.object(forKey: "is_first_launch") as? Bool ?? true
}
set {
UserDefaults.standard.set(newValue, forKey: "is_first_launch")
}
}

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
Dub.setup(publishableKey: dubPublishableKey, domain: dubDomain)

// Track first launch
if isFirstLaunch {
trackOpen()
isFirstLaunch = false
}

// Override point for customization after application launch.
return true
}

func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
handleDeepLink(url: url)
return true
}

func handleDeepLink(url: URL) {
trackOpen(deepLink: url)
}

private func trackOpen(deepLink: URL? = nil) {
// Call the tracking endpoint with the full deep link URL
Task {
do {
let response = try await Dub.shared.trackOpen(deepLink: deepLink)

print(response)

// Navigate to final link via link.url
guard let destinationUrl = response.link?.url else {
return
}

// Navigate to the destination URL
} catch let error as DubError {
print(error.localizedDescription)
}
}
}
}
```

Expand Down
Loading