Swapping a deep linking SDK is the most engineering-intensive part of a platform migration. The SDK handles deep link reception, deferred deep linking, and often event tracking. Replacing it means changing how your app receives and processes links.
This guide covers the technical steps for swapping deep linking SDKs in iOS and Android apps, with patterns that minimize risk.
For the full migration guide, see Migrating to Tolinku from Branch, Firebase, and AppsFlyer. For the migration checklist, see Deep Linking Migration Checklist: 30 Steps.
The Abstraction Layer Approach
Before ripping out the old SDK, build an abstraction layer. This gives you a clean interface that your app code calls, with the implementation swappable behind it.
Define the Interface
iOS (Swift):
protocol DeepLinkHandler {
func handleURL(_ url: URL) -> DeepLinkResult?
func handleDeferredLink(completion: @escaping (DeepLinkResult?) -> Void)
}
struct DeepLinkResult {
let path: String
let parameters: [String: String]
let isDeferred: Bool
}
Android (Kotlin):
interface DeepLinkHandler {
fun handleUri(uri: Uri): DeepLinkResult?
fun handleDeferredLink(callback: (DeepLinkResult?) -> Unit)
}
data class DeepLinkResult(
val path: String,
val parameters: Map<String, String>,
val isDeferred: Boolean
)
Wrap the Old SDK
Create an implementation using your current SDK:
// iOS: Branch implementation (temporary)
class BranchDeepLinkHandler: DeepLinkHandler {
func handleURL(_ url: URL) -> DeepLinkResult? {
// Bridge Branch's data format to your DeepLinkResult
// This wraps existing Branch logic
}
func handleDeferredLink(completion: @escaping (DeepLinkResult?) -> Void) {
// Bridge Branch's deferred link callback
}
}
Update App Code
Change all deep link handling in your app to use the protocol:
class AppRouter {
let deepLinkHandler: DeepLinkHandler
init(handler: DeepLinkHandler) {
self.deepLinkHandler = handler
}
func handleIncomingURL(_ url: URL) {
guard let result = deepLinkHandler.handleURL(url) else { return }
navigate(to: result)
}
}
Now your app code is decoupled from any specific SDK. Swapping SDKs means creating a new DeepLinkHandler implementation.
Step 1: Remove the Old SDK
iOS Removal
CocoaPods:
# Remove from Podfile
# pod 'Branch' # or pod 'AppsFlyerFramework', pod 'FirebaseDynamicLinks'
Run pod install.
Swift Package Manager: Remove the package dependency in Xcode (File → Swift Packages → Remove).
Manual removal:
- Delete the framework files
- Remove from Build Phases → Link Binary With Libraries
- Remove any related build flags
After removing the dependency, clean the build folder (Cmd+Shift+K in Xcode) and search for any remaining imports:
grep -r "import Branch" .
grep -r "import AppsFlyerLib" .
grep -r "import FirebaseDynamicLinks" .
Remove all found references.
Android Removal
Remove from build.gradle:
// Remove these lines
// implementation 'io.branch.sdk.android:library:5.+'
// implementation 'com.appsflyer:af-android-sdk:6.+'
// implementation 'com.google.firebase:firebase-dynamic-links:21.+'
Sync the project and search for remaining imports:
grep -r "import io.branch" .
grep -r "import com.appsflyer" .
grep -r "import com.google.firebase.dynamiclinks" .
Cleanup Checklist
After removing the SDK:
- Remove initialization code from AppDelegate/Application class
- Remove delegate/listener implementations
- Remove event tracking calls
- Remove link creation calls
- Remove SDK-specific configuration from Info.plist / AndroidManifest.xml
- Remove SDK-specific build scripts or plugins
- Remove proguard rules for the old SDK (Android)
Step 2: Add the New SDK
Tolinku SDK Integration
iOS (Swift Package Manager): Add the TolinkuSDK package.
Associated Domains: Add your Tolinku domain to the Associated Domains entitlement:
applinks:go.yourapp.com
SceneDelegate (or AppDelegate for older apps):
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL else { return }
let handler = TolinkuDeepLinkHandler()
if let result = handler.handleURL(url) {
AppRouter.shared.navigate(to: result)
}
}
Android:
Add the dependency to build.gradle.
AndroidManifest.xml:
<activity android:name=".MainActivity">
<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="go.yourapp.com" />
</intent-filter>
</activity>
MainActivity:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
handleIntent(intent)
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
handleIntent(intent)
}
private fun handleIntent(intent: Intent) {
val uri = intent.data ?: return
val handler = TolinkuDeepLinkHandler()
handler.handleUri(uri)?.let { result ->
AppRouter.navigate(result)
}
}
Step 3: Implement the New Handler
Create the Tolinku implementation of your abstraction:
class TolinkuDeepLinkHandler: DeepLinkHandler {
func handleURL(_ url: URL) -> DeepLinkResult? {
let components = URLComponents(url: url, resolvingAgainstBaseURL: false)
let path = url.path
let params = components?.queryItems?.reduce(into: [String: String]()) { dict, item in
dict[item.name] = item.value
} ?? [:]
return DeepLinkResult(
path: path,
parameters: params,
isDeferred: false
)
}
func handleDeferredLink(completion: @escaping (DeepLinkResult?) -> Void) {
// Use Tolinku SDK to check for deferred deep link data
Tolinku.shared.checkForDeferredLink { url in
guard let url = url else {
completion(nil)
return
}
completion(self.handleURL(url))
}
}
}
The key difference from most old SDKs: Tolinku passes deep link data through standard URLs, so parsing is just URL component extraction. No proprietary data dictionaries to decode.
Step 4: Handle Edge Cases
Multiple URL Schemes
If your app registered custom URL schemes (e.g., yourapp://product/123) in addition to Universal Links/App Links, decide whether to keep them. Custom URL schemes are less secure and less reliable than Universal Links/App Links, but some older integrations may depend on them.
Deep Link Routing
Map your old routing patterns to the new URL structure:
| Old SDK Route | New URL Route |
|---|---|
Branch.data["product_id"] = "123" |
/product/123 or ?product_id=123 |
AppsFlyer.deepLinkValue = "promo" |
/promo |
Firebase.link = "https://yourapp.com/sale" |
/sale |
Event Tracking
If you tracked events through the old deep linking SDK, move event tracking to your analytics SDK or Tolinku's analytics. Don't mix deep link routing with event tracking responsibilities.
Testing the Swap
Test these scenarios on both iOS and Android:
- Cold start with link: App is not running; user taps a link → app launches and navigates to the correct screen
- Warm start with link: App is backgrounded; user taps a link → app comes to foreground and navigates
- Deferred deep link: App not installed; user taps link → installs → opens → sees correct content
- No-op link: User taps a link that doesn't match any route → app handles gracefully (home screen, not crash)
- Malformed link: Link with missing or invalid parameters → app handles gracefully
For a comprehensive comparison of deep linking SDKs, see Deferred Deep Linking SDK Comparison.
Get deep linking tips in your inbox
One email per week. No spam.