Dynamic link web URL query item is empty in Swift iOS

Viewed 2554

I am getting deeplink URL from QRCODE scan but it is showing Web URL query is empty (implemented with firebase dynamic links). Associated domains set to "applinks:avrhub.page.link/Product".

"FirebaseDynamicLinksCustomDomains" in info.plist set to "https://avrhub.page.link" and "https://avrhub.page.link/Product"

enter image description here

Deeplink url:enter image description here

Code For Deeplinking:

func handleIncomingDynamicLink(_ dynamicLink:DynamicLink){
    
    
    
    guard let url = dynamicLink.url else {
            print("My Dynamic Link object has no url")
            return
        }
        print("Your Incoming link Parameter is \(url.absoluteString)")
        
        guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
        let queryItems = components.queryItems else {return}
        for queryItem in queryItems {
            
            Api.Params.inputProductId = Int(queryItem.value!)!
            print("Parameter \(queryItem.name) has a value of \(queryItem.value ?? "")")
        }
        
        Switcher.updateRootVC()
        Api.Params.isDeepLink = true

        if dynamicLink.matchType == .unique {
            print("The dynamic link is Unique")
        }
    }


func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
             
    if let incomingURL = userActivity.webpageURL {
        print("incoming URL is \(incomingURL)")
        let linkHandled = DynamicLinks.dynamicLinks().handleUniversalLink(incomingURL) { ( dynamicLink, error) in
            guard error == nil else {
                print("Found an error \(error!.localizedDescription)")
                return
            }
            if let dynamicLink = dynamicLink {
                self.handleIncomingDynamicLink(dynamicLink)
            }
            
        }
        if linkHandled {
  
            return true
        } else {
            return false
        }
    }
    
    return false
    
}


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

        ApplicationDelegate.shared.application(
            app,
            open: url,
            sourceApplication: options[UIApplication.OpenURLOptionsKey.sourceApplication] as? String,
            annotation: options[UIApplication.OpenURLOptionsKey.annotation]
        )
        
        
        
        if let dynamicLink = DynamicLinks.dynamicLinks().dynamicLink(fromCustomSchemeURL: url){
            self.handleIncomingDynamicLink(dynamicLink)
            return true
        } else {
            return GIDSignIn.sharedInstance().handle(url)
        }


    }

Even analytics is not giving any error

enter image description here

3 Answers

As mentioned in the comments you cannot create a dynamic link with variable product id in the firebase console, instead you need to generate it on the go. You can also include additional parameters if needed which may help in analytics for example if you would like to know which user share the link and what its impact...

In my opinion it's better to add product id as query parameter in the link. Moreover it will also be helpful in case of multi parameters. You can parse it easily with the parameter name for example:

var productId: String?
let urlComponents = URLComponents(string: urlString)
if let queryItems = urlComponents?.queryItems {
    for queryItem in queryItems {
        if queryItem.name == "product_id" {
           productId = queryItem.value
        }
    }
}

Here is my code for generating dynamic link programmatically.

func generateAndShareDynamicLink(event: Event, controller: UIViewController, presentationCompletion: @escaping (() -> Void), dismissCompletion: @escaping (() -> Void) ) {
        
        let user = Utility.shared.getCurrentUser()!
        let ownReferralCode = user.ownReferralCode.value
        let offerShareUrlString = youAPILink + "?referral=" + ownReferralCode + "&event_id=" + event.id.value + "&shared_by=" + user.userId.value + "&shared_by_name=" + user.fullName.value
        
        let url = URL(string: offerShareUrlString.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)!)!
        
        let iOSNavigationParams = DynamicLinkNavigationInfoParameters()
        iOSNavigationParams.isForcedRedirectEnabled = false
        
        let linkComponents = DynamicLinkComponents(link: url, domainURIPrefix: dynamicLinkGenaricDomain)!
        linkComponents.navigationInfoParameters = iOSNavigationParams
        linkComponents.iOSParameters = DynamicLinkIOSParameters(bundleID: bundleId)
        linkComponents.iOSParameters?.appStoreID = kAppStoreId
        linkComponents.iOSParameters?.customScheme = theBarCodeInviteScheme
        
        linkComponents.androidParameters = DynamicLinkAndroidParameters(packageName: androidPackageName)
        
        let descText = "\(user.fullName.value) has shared an event with you, check it out!"
        linkComponents.socialMetaTagParameters = DynamicLinkSocialMetaTagParameters()
        linkComponents.socialMetaTagParameters?.title = "App Name"
        linkComponents.socialMetaTagParameters?.descriptionText = descText
        linkComponents.socialMetaTagParameters?.imageURL = tbcLogoUrl
        
        linkComponents.otherPlatformParameters = DynamicLinkOtherPlatformParameters()
        linkComponents.otherPlatformParameters?.fallbackUrl = URL(string: barCodeDomainURLString)
        
        linkComponents.shorten { (shortUrl, warnings, error) in
            
            guard error == nil else {
                presentationCompletion()
                controller.showAlertController(title: "Share Event", msg: error!.localizedDescription)
                return
            }
            
            if let warnings = warnings {
                debugPrint("Dynamic link generation warnings: \(String(describing: warnings))")
            }
            
            let activityViewController = UIActivityViewController(activityItems: [descText, shortUrl!], applicationActivities: nil)
            activityViewController.popoverPresentationController?.sourceView = controller.view
            activityViewController.completionWithItemsHandler = { (activityType, completed:Bool, returnedItems:[Any]?, error: Error?) in
                dismissCompletion()
            }
            controller.present(activityViewController, animated: true, completion: {
                presentationCompletion()
            })
        }
        
    }

I had the same issue in that I always gets nil value while trying to get the Firebase Dynamic link from below function

DynamicLinks.dynamicLinks().dynamicLink(:)

I found on useful post on Github that explains that we need to add the domain in .plist file which is used in the dynamic link.

This works for me, may be this will help someone.

You can add your domain in plist like below:

<key>FirebaseDynamicLinksCustomDomains</key>
    <array>
        <string><YOUR_DOMAIN_NAME></string>
    </array>

Make sure that you are specifying https:// or http:// in the domain name.

Note: if you wish to explore how to handle Firebase dynamic links for iOS 13 and higher version iOS. You can visit this answer.

I agree with the other answer that the best way to solve this is to get the back end developer to use the Firebase Dynamic Links SDK to properly generate a dynamic link and embed in the QR code.

However, if you don't have control of that, then you can take the incoming link and parse it for the product ID that is embedded. In which case, you don't need to use some of the Dynamic link capabilities for example, you could instead do the below:

if let incomingURL = userActivity.webpageURL {
    print("incoming URL is \(incomingURL)")
    handleIncomingURL(incomingURL)
}

//quick and dirty parse - you could do something a bit more robust based on your circumstances

func handleIncomingURL(_ url:URL) {
    
    guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true), let host = components.host else {
        return
    }
    var pathComponents = components.path.components(separatedBy: "/")
    // the first component is empty
    pathComponents.removeFirst()
    
    if pathComponents[0] == "Product" && pathComponents[1] != nil {
        print("found productId: \(pathComponents[1])")
        //Action here: jump to the right page from here, now that you have a product ID to work with
    } else {
        print("invalid URL path for my use case....")
    }
}
Related