llfbandit / app_links

Android App Links, Deep Links, iOs Universal Links and Custom URL schemes handler for Flutter.
https://pub.dev/packages/app_links
Apache License 2.0
176 stars 68 forks source link

[iOS] when link is clicked while app is closed, the app is opened but initialAppLink is null #47

Closed LeGoffMael closed 6 months ago

LeGoffMael commented 1 year ago

Hi! Thank you for your work in developing this package, it is very helpful!

Describe the bug

Might be related to #19 and #32.

This issue occurs only on iOS. When i open my app with the link (iampet://app.iampet.io/feed/point-shop), the app open but the initialLink is null. Clicking on the same link while the app is running trigger the uriLinkStream properly.

FYI: I open those links from Slack.

What i tried :

My analysis

I noticed that the problem is that AppDelegate.application(app, url, options) is never triggered.

Here is the code of my `AppDelegate.swift` ```swift import UIKit import Flutter import AppTrackingTransparency import firebase_dynamic_links import NaverThirdPartyLogin @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { NSLog("[petbb] AppDelegate didFinishLaunchingWithOptions()") GeneratedPluginRegistrant.register(with: self) // to show foreground push notif with `flutter_local_notifications` if #available(iOS 10.0, *) { UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate } // below code is based on https://stackoverflow.com/a/47432726/7943785 if let url = launchOptions?[UIApplication.LaunchOptionsKey.url] as? URL { //Deeplink NSLog("[petbb] AppDelegate didFinishLaunchingWithOptions() test1 = " + url.absoluteString) } else if let activityDictionary = launchOptions?[UIApplication.LaunchOptionsKey.userActivityDictionary] as? [AnyHashable: Any] { //Universal link for key in activityDictionary.keys { if let userActivity = activityDictionary[key] as? NSUserActivity { if let url = userActivity.webpageURL { NSLog("[petbb] AppDelegate didFinishLaunchingWithOptions() test2 = " + url.absoluteString) } } } } return super.application(application, didFinishLaunchingWithOptions: launchOptions) } // open tracking popup (mandatory since ios 14) override func applicationDidBecomeActive(_ application: UIApplication) { NSLog("[petbb] AppDelegate applicationDidBecomeActive()") if #available(iOS 14, *) { ATTrackingManager.requestTrackingAuthorization { (status) in } } } // handle different dynamic links (firebase, kakao, naver login, applinks) override func application( _ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] ) -> Bool { NSLog("[petbb] AppDelegate application() = " + url.absoluteString) // use for kakao share (XXXXXX replace my token) if url.absoluteString.hasPrefix("XXXXXXXXXXXXX") { return super.application(app, open: url, options: options) } // use for naver login (XXXXXX replace my token) if url.absoluteString.hasPrefix("XXXXXXXXXXXX"){ return NaverThirdPartyLoginConnection.getSharedInstance().application(app, open: url, options: options) } // use for firebase if DynamicLinks.dynamicLinks().dynamicLink(fromCustomSchemeURL: url) != nil { return true } NSLog("[petbb] AppDelegate application() went until the end") return super.application(app, open: url, options: options) } override func application( _ app: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void ) -> Bool { NSLog("[petbb] AppDelegate restorationHandler()") return super.application(app, continue: userActivity, restorationHandler: restorationHandler) } } ```
And here are the logs generated from it Link opened from terminated state Link opened while app is running
Screenshot 2023-03-07 at 7 46 25 PM Screenshot 2023-03-07 at 7 47 01 PM

As you can see, the url can be founded in launchOptions but not in openLink.

Possible fixes

  1. From what i can see online, people tends to suggest to use SceneDelegate

  2. Another quick solution for me would be to be able to save manually the link in SwiftAppLinksPlugin. Maybe by exposing SwiftAppLinksPlugin.sharedInstance() and adding a setter ? I don't have much knowledge on swift development but i will probably try to go in this direction in my fork for the moment.

More information

Does it related to

[ ] App Links (Android)
[ ] Deep Links (Android)
[ ] Universal Links (iOS)
[x] Custom URL schemes? (iOS)

Does the example project work?

[x] Yes
[ ] No
[x] Irrelevant here

Did you fully read the instructions for the targeted platform before submitting this issue?

Uploaded your files to webserver, HTTPS, direct connection, scheme pattern setup, ...

[ ] Yes
[ ] No
[x] Irrelevant here

llfbandit commented 1 year ago

The multiple window feature (Scene) is a good hint. Be sure to have it disabled: https://stackoverflow.com/questions/75375167/sceneconfiguration-info-plist-contained-no-uiscene-configuration-dictionary-loo.

I saw that you have Firebase in your project. This link may worth a try: https://firebase.google.com/docs/cloud-messaging/ios/client?hl=fr#method_swizzling_in

More generally regarding the last hint, you seem to have lot of plugins that may interfer with application(_:open:options:) call: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623112-application#discussion

Let me know how it goes. Cocorico !

LeGoffMael commented 1 year ago

Thank you for your comment! I will try every point and let you know. 🐔 Cocorico

LeGoffMael commented 1 year ago

After a lot of pain, I finally found a solution that works for me.

  1. The multiple window feature (Scene) is a good hint. Be sure to have it disabled: https://stackoverflow.com/questions/75375167/sceneconfiguration-info-plist-contained-no-uiscene-configuration-dictionary-loo.

I tried the suggested solution but it did not change anything.

  1. I saw that you have Firebase in your project. This link may worth a try: https://firebase.google.com/docs/cloud-messaging/ios/client?hl=fr#method_swizzling_in

I tried to add

<key>FirebaseAppDelegateProxyEnabled</key>
<string>NO</string>

and i also tried

<key>FirebaseAppDelegateProxyEnabled</key>
<string>YES</string>

it did not change anything either.

3.

More generally regarding the last hint, you seem to have lot of plugins that may interfer with application(_:open:options:) call: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623112-application#discussion

This gave me a hint that led to my fix. The problem is that application(_:didFinishLaunchingWithOptions:) returns false which causes application(_:open:options:) not being called.

So i updated my AppDelegate application(_:didFinishLaunchingWithOptions:) to force returning true if there is an app link that starts with my app scheme :

override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    // HERE do other stuff

    // Custom URL
    if let url = launchOptions?[UIApplication.LaunchOptionsKey.url] as? URL {
      // check if url starts with my custom scheme (here iampet)
      if(url.absoluteString.hasPrefix("iampet://")) {
        super.application(application, didFinishLaunchingWithOptions: launchOptions)
        // force return true so that it is handled in application(_:open:options:)
        return true
      }
    }
    // Universal link
    else if let activityDictionary = launchOptions?[UIApplication.LaunchOptionsKey.userActivityDictionary] as? [AnyHashable: Any] { 
        for key in activityDictionary.keys {
            if let userActivity = activityDictionary[key] as? NSUserActivity {
                if let url = userActivity.webpageURL {
                  // TODO: add logic for universal link (i don't need it for now in my case)
                }
            }
        }
    }

    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }

open

Now initialLink is well set when i start my app with an applink 💪

My only concerns regarding this fix is that i don't know what consequences returning true could causes. That's why I open #49 PR proposal to add an easier way to fix it.

Imgkl commented 1 year ago

@LeGoffMael did you face any issues after setting it to true? I'm also facing the same issue.

LeGoffMael commented 1 year ago

@Imgkl not really, i tested the other dependencies i needed to handle in the AppDelegate and it all seemed to be fine

Imgkl commented 1 year ago

@LeGoffMael I tested the your solution, It didn't work in my case.

@llfbandit Can solutions from your end?

llfbandit commented 7 months ago

This should be somehow available in app_links 3.5.0-beta.1. Please have a look to the README file. Feedback appreciated!

llfbandit commented 6 months ago

Released in v3.5.0. Update your specifics to catch links.

nikorehnback commented 5 months ago

In the documentation it says:

Both methods must call super and return true to enable app link workflow.

and the code sample is following:

import app_links

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    ...

    // Retrieve the link from the parameters
    if let url = AppLinks.shared.getLink(launchOptions: launchOptions) {
      // We have a link, propagate it to your Flutter app
      AppLinks.shared.handleLink(url: url)
    }

    return false
  }
}

Should it be returning true here and also when should super.application be executed?

Or is it so that the library will automatically handle the launching link?