Flutterfire: [dynamic_link] New iOS install doesn't open with dynamic link after install

Created on 23 Jan 2020  Â·  26Comments  Â·  Source: FirebaseExtended/flutterfire

When an iOS user clicking my dynamic link it works fine when the app is currently installed. The app opens and can respond to the dynamic link and this works if the app is running in the background or closed in the background. However, if the app is not installed the user is correctly navigated to the app store however once they tap the 'Open' button after the install completes the dynamic link is not loaded. The user can tap the dynamic link for a 2nd time at this point and it will work, however I don't believe the user should have to tap the dynamic link multiple time I thought that should just work and be passed to the app after the install. I have tried with both "efr=1" and without that. When the efr=1 is omitted that also correctly brings me to a preview.page.link in safari that then asks "Save my place in the app. A link will be copied to continue to this page". And once I click the "Open" button from this screen that takes me to the app store and my dynamic link is in the clipboard. With the efr=1 there seems to be nothing in the clipboard. However this has no impact on the final result as opening the app after install never receives the link.

My dynamic link is:
https://startingxi.page.link/?link=https://starting-xi-6d6e1.web.app/game/AhXSri7rXWar64GyTHOk&apn=com.koncal.starting_xi&isi=1489245604&ibi=com.koncal.startingXi&st=Team%203%20vs%20&sd=1%20-%200&efr=1

My code to handle the dynamic link from my main.dart is:

  @override
  void initState() {
    super.initState();
    _configure();
    initDynamicLinks();

    futureInitState = initStateAsync();
  }

  void initDynamicLinks() async {
    final PendingDynamicLinkData data = await FirebaseDynamicLinks.instance.getInitialLink();
    final Uri deepLink = data?.link;
    processDeepLink(deepLink);

    FirebaseDynamicLinks.instance.onLink(onSuccess: (PendingDynamicLinkData dynamicLink) async {
      final Uri deepLink = dynamicLink?.link;
      await processDeepLink(deepLink);
    }, onError: (OnLinkErrorException e) async {
      print('onLinkError');
      print(e.message);
      displayFlushBar('$e.message');
    });
  }

my pubspec.yaml
```dependencies:
flutter:
sdk: flutter

# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^0.1.2
sqflite: ^1.2.0
path: ^1.6.2
path_provider: any
intl: ^0.16.0
font_awesome_flutter: ^8.5.0
flushbar: ^1.9.1
flutter_slidable: ^0.5.4
auto_size_text: ^2.1.0
flutter_icons: ^1.0.0+1
provider: ^3.2.0
flutter_typeahead: ^1.7.0
firebase_core: ^0.4.3+2
cloud_firestore: ^0.13.0+1
firebase_dynamic_links: ^0.5.0+9
share: ^0.6.3+5
uuid: 2.0.4
shared_preferences: ^0.5.6
lint: ^1.1.1
matrix_gesture_detector: ^0.1.0


Per another issue I tried adding
```FirebaseApp.configure()``` to the AppDelegate.swift.

I have tried changing the where the initDynamicLinks() is called from originally being within my FutureBuilder to just calling from the main initState.

At this point I'm not sure what else to try or if my expectations are just off.  From the documentation it seems like this should work without having to click the link a 2nd time after install, but it's possible I'm reading the documentation incorrectly and this is indeed working as designed and the end user is required to tap the link the 1st time to install and a 2nd time to load the dynamic link vs tapping the link to install and then just opening the app.  So I would also like clarification on that.

flutter doctor:

[√] Flutter (Channel stable, v1.12.13+hotfix.5, on Microsoft Windows [Version 10.0.17763.973], locale en-US)
• Flutter version 1.12.13+hotfix.5 at C:src\flutter
• Framework revision 27321ebbad (6 weeks ago), 2019-12-10 18:15:01 -0800
• Engine revision 2994f7e1e6
• Dart version 2.7.0

[!] Android toolchain - develop for Android devices (Android SDK version 29.0.2)
• Android SDK at C:\Users\Steve\AppData\Local\Android\Sdk
• Android NDK location not configured (optional; useful for native profiling support)
• Platform android-29, build-tools 29.0.2
• ANDROID_SDK_ROOT = C:\Users\Steve\AppData\Local\Android\Sdk
• Java binary at: C:\Program Files\Android\Android Studio\jre\bin\java
• Java version OpenJDK Runtime Environment (build 1.8.0_202-release-1483-b03)
! Some Android licenses not accepted. To resolve this, run: flutter doctor --android-licenses

[√] Android Studio (version 3.5)
• Android Studio at C:\Program Files\Android\Android Studio
• Flutter plugin version 42.1.1
• Dart plugin version 191.8593
• Java version OpenJDK Runtime Environment (build 1.8.0_202-release-1483-b03)

[!] IntelliJ IDEA Community Edition (version 2018.2)
• IntelliJ at C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2018.2.4
X Flutter plugin not installed; this adds Flutter specific functionality.
X Dart plugin not installed; this adds Dart specific functionality.
• For information about installing plugins, see
https://flutter.dev/intellij-setup/#installing-the-plugins

[!] VS Code, 64-bit edition (version 1.41.1)
• VS Code at C:\Program Files\Microsoft VS Code
X Flutter extension not installed; install from
https://marketplace.visualstudio.com/items?itemName=Dart-Code.flutter

[√] Connected device (1 available)
• AOSP on IA Emulator • emulator-5554 • android-x86 • Android 9 (API 28) (emulator)

! Doctor found issues in 3 categories.
Process finished with exit code 0
```

Thanks!

crowd dynamic_links bug

Most helpful comment

To clarify I believe there are 3 user cases per device that need to work:

  1. app is not installed on device
  2. app is installed on device and currently closed
  3. app is installed on device and currently open

On Android all 3 of these use cases work fine. The user receives the dynamic link via a txt message (or whatever medium the sender chose to share via) and the receiver of the dynamic link can tap the link and the app install and opens or just opens with the link context as expected so I can load the share details.
On iOS use case #2 and #3 work properly, the user receives the link and upon tapping the link the app opens and can load the share details. However #1 does not work. The user is properly directed to install the app, but upon opening the app the link details are not properly sent to the app so it's as if the share didn't happen. At this point the user could choose to tap the shared link again and since the app is now installed the share would work. But I believe iOS should work similar to how Android works that after the install the app opens and the contents of the share link should be available without having to tap the link a 2nd time.

I have not tested what @ToniTornado mentions to add a delay though. If that does work at least a workaround exists. As it stands right now the user experience for iOS receiving a share link for app that isn't installed leads to a quite unpleasant user experience and unfortunately a bad user experience on the initial install isn't going to help my app.

All 26 comments

Same here.

same here, works on android though.

I have no problems unless I make a fresh install. In my app getInitialLink returns null if a link is waiting in the clipboard while the app is being installed and then opened. The link is cleared from the clipboard as expected after opening the app. Any later calls to getInitialLink return the correct link again.

I tried to understand the Obj-C code under the hood but did not find out where the URL is read and cleared from the clipboard. Although Flutter getInitialLink is asynchronous, it looks like it is not really waiting for any link to be retrieved. It transforms and returns a variable called _initialLink. This var in turn is set by application:openURL:options: that by documentation is called from the AppDelegate after application:willFinishLaunchingWithOptions:. Setting a variable and reading the same variable at another point in code and time may cause a timing issue?

I added an arbitrary delay before reading the var and it seems to work then (but of course 2 seconds too late though):

await Future.delayed(Duration(seconds: 2));
final PendingDynamicLinkData data = await FirebaseDynamicLinks.instance.getInitialLink();

I'm having a similar issue to this. Its not opening the app when clicking on the dynamic link. Anyone solve this issue yet? Works on android not on iOS. I think we are missing something on how to make this work. Some documentation is missing.

To clarify I believe there are 3 user cases per device that need to work:

  1. app is not installed on device
  2. app is installed on device and currently closed
  3. app is installed on device and currently open

On Android all 3 of these use cases work fine. The user receives the dynamic link via a txt message (or whatever medium the sender chose to share via) and the receiver of the dynamic link can tap the link and the app install and opens or just opens with the link context as expected so I can load the share details.
On iOS use case #2 and #3 work properly, the user receives the link and upon tapping the link the app opens and can load the share details. However #1 does not work. The user is properly directed to install the app, but upon opening the app the link details are not properly sent to the app so it's as if the share didn't happen. At this point the user could choose to tap the shared link again and since the app is now installed the share would work. But I believe iOS should work similar to how Android works that after the install the app opens and the contents of the share link should be available without having to tap the link a 2nd time.

I have not tested what @ToniTornado mentions to add a delay though. If that does work at least a workaround exists. As it stands right now the user experience for iOS receiving a share link for app that isn't installed leads to a quite unpleasant user experience and unfortunately a bad user experience on the initial install isn't going to help my app.

We're having some issues. Android is currently working fine, but iOS is sometimes not working.
I feel that the iOS dynamic link is working fine when open it from the Slack app (Twitter, Instagram, Messenger, LINE are not okay). There are existed some hints to solve the problem? 🤔

I can confirm that await getInitialLink() returns the desired value only after a delay even though I'm waiting for it. The 2 seconds I use in my code are just a "worksforme" guess. Awaiting getInitialLink() should not return null before a link is available or no valid link is present in the clipboard. This has to be fixed somewhere in the underlying iOS code.
As @skoncal correctly describes this is _only_ the case for iOS and his scenario 1) when the app was not installed when clicking the link (= browser opens, you click the button, the link is copied to clipboard, you get redirected to the app store, and the link is finally read by the app on startup after installation finished and the clipboard gets cleared).

var earlyLink = await FirebaseDynamicLinks.instance.getInitialLink();  // returns null
await Future.delayed(Duration(seconds: 2));
var laterLink = await FirebaseDynamicLinks.instance.getInitialLink(); // returns the link

await Future.delayed(Duration(seconds: 2));

This doesn't work for me @ToniTornado

I tried the native method from here which works. But the firebase dynamic link sdk doesn't give anything for IOS on fresh install.

I'm having this same issue. Any idea of a work around or info on a fix?

I've spent some time on this, here is fixed implementation of onInitialLink:

- (BOOL)onInitialLink:(NSUserActivity *)userActivity {
  BOOL handled = [[FIRDynamicLinks dynamicLinks]
      handleUniversalLink:userActivity.webpageURL
               completion:^(FIRDynamicLink *_Nullable dynamicLink, NSError *_Nullable error) {
                 if (error) {
                   self.flutterError = getFlutterError(error);
                 } else if (self.initiated) {
                   NSMutableDictionary *dictionary = getDictionaryFromDynamicLink(dynamicLink);
                   [self.channel invokeMethod:@"onLinkSuccess" arguments:dictionary];
                 }
                 self.initialLink = dynamicLink;
               }];
  return handled;
}

Not an elegant solution, but it works, however you will get your link in FirebaseDynamicLinks.instance.onLink not in getInitialLink()

Alternatively, you could probably just call onLink before getInitialLink, but I didn't test it

I am having same issue here on iOS 13.3. Sometimes deep links are identified, sometime not. Any updates on it?

@rostopira works for me too thanks!

- (BOOL)onInitialLink:(NSUserActivity *)userActivity {
  BOOL handled = [[FIRDynamicLinks dynamicLinks]
      handleUniversalLink:userActivity.webpageURL
               completion:^(FIRDynamicLink *_Nullable dynamicLink, NSError *_Nullable error) {
                 if (error) {
                   self.flutterError = getFlutterError(error);
                 } else if (self.initiated) {
                   NSMutableDictionary *dictionary = getDictionaryFromDynamicLink(dynamicLink);
                   [self.channel invokeMethod:@"onLinkSuccess" arguments:dictionary];
                 }
                 self.initialLink = dynamicLink;
               }];
  return handled;
}

to explain more : need to change onInitialLink (line 160) method FLTFirebaseDynamicLinksPlugin.mfile inside firebase_dynamic_links package:

~/development/flutter/.pub-cache/hosted/pub.dartlang.org/firebase_dynamic_links-0.5.0+9/ios/Classes/FLTFirebaseDynamicLinksPlugin.m

thanks for the possible solution, @rostopira, but i can't get that to work for me. i also tried calling onLink() first.

per https://github.com/firebase/firebase-ios-sdk/issues/3918 might this be an issue with:

- (BOOL)application:(UIApplication *)application
    continueUserActivity:(NSUserActivity *)userActivity
      restorationHandler:(void (^)(NSArray *))restorationHandler {
  if (_initiated) {
    return [self onLink:userActivity];
  }
  return [self onInitialLink:userActivity];
}

which is right below - (BOOL)onInitialLink?

pinging @kroikie - the payload is definitely not being preserved when coming from an AppStore install. getInitialLink() is otherwise working fine on cold opens though.

I got it working by adding WidgetsFlutterBinding.ensureInitialized();
Just before calling await FirebaseDynamicLinks.instance.getInitialLink();

@ambernardino were you not calling WidgetsFlutterBinding.ensureInitialized() elsewhere, or did you double it up?

@blaneyneil wasn't calling it anywhere

I was already calling WidgetsFlutterBinding.ensureInitialized() in my main(), before runApp(), but just out of desperation i called it again on my home screen, right before getInitialLink(). no luck. the payload after an AppStore install is still not there. post install, it works exactly like it should on a cold open.

For anyone having trouble with this, i had the same issue.
My issue was that i had overriden the method
override func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any]) -> Bool {}
in my AppDelegate
If you have and need to make sure you call super.

It works for me now, hope it helps someone

For anyone having trouble with this, i had the same issue.
My issue was that i had overriden the method
override func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any]) -> Bool {}
in my AppDelegate
If you have and need to make sure you call super.

It works for me now, hope it helps someone

My issue was also due to this.

It worked for me when adding this at the last line in the method.

return super.application(app, open: url, options: options)

For anyone having trouble with this, i had the same issue.
My issue was that i had overriden the method
override func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any]) -> Bool {}
in my AppDelegate
If you have and need to make sure you call super.
It works for me now, hope it helps someone

My issue was also due to this.

It worked for me when adding this at the last line in the method.

return super.application(app, open: url, options: options)

Before the fix, was it working inconsistently or not working at all?

@dpedrinha
In my case it was not working at all after new app installing and first launching. It was working only when the app was already installed.

firebase_dynamic_links: 0.6.0
the issue is still there

Just in case, maybe it can be affected by other firebase-related plugins, here is my list of used plugins with versions:

  firebase_core: 0.5.0
  firebase_crashlytics: 0.1.4+1
  firebase_analytics: 6.0.0
  firebase_messaging: 7.0.0
  firebase_dynamic_links: 0.6.0
  firebase_auth: 0.18.0+1

Update:

I am an Android Developer originally and have less then 0 experience in iOS. But still I was thinking to try to implement dynamic links integration natively on iOS as it is described here: https://firebase.google.com/docs/dynamic-links/ios/receive#open-dynamic-links-in-your-app

When I started and had added the following methods (which does not make sense for me) it made my FirebaseDynamicLinks.instance.getInitialLink(); suddenly working like a charm!

Removing the following code brings the issue back.

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {

    @available(iOS 9.0, *)
    override func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any]) -> Bool {
      return application(app, open: url,
                         sourceApplication: options[UIApplication.OpenURLOptionsKey.sourceApplication] as? String,
                         annotation: "")
    }

    override func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool {
        if DynamicLinks.dynamicLinks().dynamicLink(fromCustomSchemeURL: url) != nil {
        // Handle the deep link. For example, show the deep-linked content or
        // apply a promotional offer to the user's account.
        // ...
        return true
      }
      return false
    }
....
}

Hope it could help someone.

Same issue with v0.6.0. None of the above fixed it. 'onInitialLink' doesn't seem to exist in FTLFirebaseDynamicLinksPlugin.m in this version?

Yes, having the same issue in v0.6.0.

Works fine in Android, works fine in iOS if app is in the background, but not getting any link data when app is closed.

firebase_dynamic_links: 0.6.0
the issue is still there

Just in case, maybe it can be affected by other firebase-related plugins, here is my list of used plugins with versions:

  firebase_core: 0.5.0
  firebase_crashlytics: 0.1.4+1
  firebase_analytics: 6.0.0
  firebase_messaging: 7.0.0
  firebase_dynamic_links: 0.6.0
  firebase_auth: 0.18.0+1

Update:

I am an Android Developer originally and have less then 0 experience in iOS. But still I was thinking to try to implement dynamic links integration natively on iOS as it is described here: https://firebase.google.com/docs/dynamic-links/ios/receive#open-dynamic-links-in-your-app

When I started and had added the following methods (which does not make sense for me) it made my FirebaseDynamicLinks.instance.getInitialLink(); suddenly working like a charm!

Removing the following code brings the issue back.

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {

    @available(iOS 9.0, *)
    override func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any]) -> Bool {
      return application(app, open: url,
                         sourceApplication: options[UIApplication.OpenURLOptionsKey.sourceApplication] as? String,
                         annotation: "")
    }

    override func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool {
        if DynamicLinks.dynamicLinks().dynamicLink(fromCustomSchemeURL: url) != nil {
        // Handle the deep link. For example, show the deep-linked content or
        // apply a promotional offer to the user's account.
        // ...
        return true
      }
      return false
    }
....
}

Hope it could help someone.

Can you please elaborate more? did you just those code to AppDelegate.swift and it worked?

I tried adding the same code to AppDelegate.swift but it did not work for me.

There is nothing wrong on Flutter or Firebase side, it's an iOS bug.

Sometimes, on any iOS 11.2+, the AASA file is not being updated after the application is installed. In that case, the only way to update it is to delete the app, restart the device, and install the app.
This was reproduced on multiple iPhone devices.

Applications that were installed on the device before the update will continue working, however, upon reinstalling - they may stop working.

More info here
https://stackoverflow.com/questions/59079521/ios-firebase-dynamic-link-not-opening-installed-app
http://www.openradar.me/radar?id=4999496467480576

Everything is working correctly for me now after I deleted, restart and reinstalled the app.

I am using firebase_dynamic_links: ^0.6.0+1.

Was this page helpful?
0 / 5 - 0 ratings