firebase / flutterfire

🔥 A collection of Firebase plugins for Flutter apps.
https://firebase.google.com/docs/flutter/setup
BSD 3-Clause "New" or "Revised" License
8.69k stars 3.97k forks source link

[firebase_dynamic_links] [iOS] Deeplinks do not work if they are clicked while the app is killed #9110

Closed tudor07 closed 2 years ago

tudor07 commented 2 years ago

I have a deeplink that when clicked is supposed to open a specific screen.

On Android all scenarios work perfectly.

On iOS I see the following behaviours:

Working scenario: If the app was already opened, but was put in background, if I click the deeplink, it works fine, the app is put to foreground and shows my custom screen for the opened deeplink path. These are the steps:

  1. Open my app normally (tap app icon)
  2. Put the app to background (I just dissmiss my app to go to iOS main menu, note: I do not kill my app from memory)
  3. I go and open another app where I have my deeplink (eg. Messages or Mail)
  4. Tap the deeplink
  5. My app is brought to foreground and the correct screen is shown according to the tapped deeplink path

Broken scenario:

  1. Kill my app from memory (from app switcher, swipe up on my app so it's killed)
  2. I go and open another app where I have my deeplink (eg. Messages or Mail)
  3. Tap the deeplink
  4. My app opens on the first default screen, the custom screen for the deeplink is not shown (other way to put it: my handler for the deeplink is not called, onLink/getInitialLink not called)

Minimal code to reproduce:

main.dart ``` import 'dart:async'; import 'package:flutter/material.dart'; import 'package:firebase_core/firebase_core.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp( options: FirebaseOptions( apiKey: 'apiKey', appId: 'appId', messagingSenderId: 'messagingSenderId', projectId: 'projectId', authDomain: 'authDomain', databaseURL: 'databaseURL', storageBucket: 'storageBucket', measurementId: 'measurementId', androidClientId: 'androidClientId', iosClientId: 'iosClientId', iosBundleId: 'iosBundleId', ), ); runApp(MyApp()); } ```
my_app.dart ``` import 'package:firebase_dynamic_links/firebase_dynamic_links.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; class MyApp extends StatefulWidget { MyApp({ Key key, }) : super(key: key); @override _MyAppState createState() => _MyAppState(); } class _MyAppState extends State { @override void initState() { super.initState(); FirebaseDynamicLinks.instance.onLink.listen(_handleDeepLink); _getInitialLink(); } Future _getInitialLink() async { final PendingDynamicLinkData data = await FirebaseDynamicLinks.instance.getInitialLink(); _handleDeepLink(data); } @override Widget build(BuildContext context) { // return app widgets... } Future _handleDeepLink(PendingDynamicLinkData linkData) async { final Uri deepLink = linkData?.link; if (deepLink == null) { return; } if (deepLink.path == '/my/custom/path') { Navigator.of(context).pushNamed(MyCustomScreen.routeName); } } } ```
flutter doctor ``` Doctor summary (to see all details, run flutter doctor -v): [✓] Flutter (Channel stable, 2.10.3, on macOS 12.4 21F79 darwin-arm, locale en-RO) [✓] Android toolchain - develop for Android devices (Android SDK version 31.0.0) [✓] Xcode - develop for iOS and macOS (Xcode 13.4.1) [✗] Chrome - develop for the web (Cannot find Chrome executable at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome) ! Cannot find Chrome. Try setting CHROME_EXECUTABLE to a Chrome executable. [✓] Android Studio (version 2021.2) [✓] Android Studio (version 2021.2) [✓] IntelliJ IDEA Community Edition (version 2022.1.3) [✓] IntelliJ IDEA Community Edition (version 2022.1.2) [✓] IntelliJ IDEA Ultimate Edition (version EAP IC-222.3244.4) [✓] IntelliJ IDEA Ultimate Edition (version EAP IC-222.3048.13) [✓] VS Code (version 1.69.0) [✓] Connected device (1 available) [✓] HTTP Host Availability ```

Firebase libs:

firebase_core: 1.19.2
firebase_dynamic_links: 4.3.2
darshankawar commented 2 years ago

Thanks for the detailed report @tudor07

4. (other way to put it: my handler for the deeplink is not called, onLink/getInitialLink not called)

Does this mean getInitialLink() is null and onLink() is not called at all ?

Are you using dart initialization for your project or manual setup using google services file (GoogleService-Info.plist) ?

If the latter, are you using <key>FirebaseDynamicLinksCustomDomains</key> in info.plist file ?

tudor07 commented 2 years ago
  1. Yes - getInitialLink is null, onLink not called
  2. Yes - I use Dart initialization (no .plist file)
  3. Yes, I have that entry in the Info.plist file
darshankawar commented 2 years ago

Thanks for the update. Can you take a look at this solution and see if it works in your case ?

Also check this comment from team member indicating that initializing with flutterfire configure may not support all firebase products yet, so maybe you can try the same scenario using GoogleService-Info.plist ?

tudor07 commented 2 years ago

I already had the Firebase Installations API added to my API key. Using GoogleService-Info.plist is not an option for my project.

darshankawar commented 2 years ago

From your flutter doctor, I see that you are on 2.10.3. Can you possibly upgrade to latest stable and try ? Also, what iOS version are you seeing this on ? I am trying to know if this is iOS specific or not.

tudor07 commented 2 years ago

I'm using iOS 15.5

darshankawar commented 2 years ago

@tudor07 From the code sample you shared earlier, specially below section:

Future<void> _getInitialLink() async {
    final PendingDynamicLinkData data =
        await FirebaseDynamicLinks.instance.getInitialLink();
    _handleDeepLink(data);
  }

Future<dynamic> _handleDeepLink(PendingDynamicLinkData linkData) async {
    final Uri deepLink = linkData?.link;
    if (deepLink == null) {
      return;
    }

    if (deepLink.path == '/my/custom/path') {
      Navigator.of(context).pushNamed(MyCustomScreen.routeName);
    }
  }  

Can you try to first check if linkData is null and if not, what it prints and then something like below ?

if (linkData != null) {
  print (`initial link $linkData`);
  }

  final Uri deepLink = linkData?.link;

  if (deepLink != null) {
   // your routing logic here
  }
matthewfx commented 2 years ago

I have had the same problem. However, once I tested it with a real device instead of the simulator the initial link wasn't null. @darshankawar It would be nice if you could make it work on the simulator too though :)

tudor07 commented 2 years ago

@matthewfx I tested on a real device

matthewfx commented 2 years ago

@tudor07 uh oh... in that case I wonder why it sometimes works and sometimes doesn't... Quite concerning to be honest :(

tudor07 commented 2 years ago

@darshankawar linkData is null

darshankawar commented 2 years ago

Thanks for the update. Using the code sample provided, I am getting same behavior.

russellwheatley commented 2 years ago

I've just tested this with and without the GoogleService-Info.plist file, and it works when the app is killed (i.e. the deep link is present). I'm going to update the Dynamic Links example app so you can test it for yourself (double check what possible config you may have set incorrectly) as there a few things that need updating. A couple of things that are necessary:

  1. It needs to be run in release mode (i.e. flutter run --release) if you wish to test it from a terminated app state.
  2. It needs to run on an actual device.
tudor07 commented 2 years ago

@russellwheatley so you are saying that you can't reproduce this issue?

russellwheatley commented 2 years ago

@tudor07 Yes.

russellwheatley commented 2 years ago

I have updated the dynamic link example app. I think you should be able to run it on a device and check. Check out this branch locally: https://github.com/firebase/flutterfire/pull/9250

Here is the dynamic link: https://reactnativefirebase.page.link/bFkn

Steps:

  1. Clone FlutterFire repo and run melos bootstrap in the root of project.
  2. Root to dynamic link example app in terminal and run example app in release mode: flutter run --no-pub --release on an actual iOS device.
  3. Kill the app and click on the above noted link.
  4. The app should open up and immediately take you to a screen via onLink here
  5. The screen simply has the text node "Hello, World!" which is this widget here
tudor07 commented 2 years ago

I'll check but @darshankawar said he is also able to reproduce my issue, it would be great if he can also try the above example. It would be helpful to try on as many devices as we can.

darshankawar commented 2 years ago

I'll re-verify and confirm.

darshankawar commented 2 years ago

@tudor07 Based on @russellwheatley's comment earlier, I verified by running firebase_dynamic_links plugin example in release mode on iOS device (iphone 6s, OS 15.3.1) and was able to see expected behavior. See below:

https://user-images.githubusercontent.com/67046386/182354969-a81864af-9222-45da-a35d-983b106fe49d.MP4

I am on latest master version.

tudor07 commented 2 years ago

I might be doing something wrong. When I press the link this is what I see:

IMG_DDA2101B5E12-1

tudor07 commented 2 years ago

Screen recording:

https://user-images.githubusercontent.com/7441453/182387316-704edae1-c4da-471d-bc9d-dc9f414d86fc.mov

russellwheatley commented 2 years ago

@tudor07 - did you try this link?

reactnativefirebase.page.link/bFkn

tudor07 commented 2 years ago

Yes, same with that one

russellwheatley commented 2 years ago

@tudor07 You're using this branch https://github.com/firebase/flutterfire/pull/9250?

russellwheatley commented 2 years ago

Could you also change the default browser as well to chrome and see if that makes a difference.

tudor07 commented 2 years ago

I am using @russell/dynamic-9110 branch:

❯ git branch
* @russell/dynamic-9110

I get the same behaviour with Chrome.

Btw, this behaviour is for links generated inside the app, if I use reactnativefirebase.page.link/bFkn specifically I get to invertase.io/helloworld which is a 404 page.

russellwheatley commented 2 years ago

If you get the link invertase.io/helloworld then it worked correctly. That is the deeplink:

Screenshot 2022-08-03 at 08 31 15

It is a 404 hence why we use just the path to push you to another screen to show you it works: https://github.com/firebase/flutterfire/blob/master/packages/firebase_dynamic_links/firebase_dynamic_links/example/lib/main.dart#L60

maciejbrzezinski commented 2 years ago

I'm working on two apps at the moment and I spotted the same problem in one of them :v

That's weird, because scenarios when app is installed from app store or link is clicked when the app is in background works fine. Problem happens only when the app is terminated.

I also compared the two apps configs few times and I couldn't spot anything suspicious. The same plugin versions, the same firebase project, two different custom domains

I performed two tests with usage of app_links package on my apps. Both scenarios were launched by clicking dynamic link from terminated state. There was release mode and this launch option checked in XCode: Screen Shot 2022-08-03 at 11 03 46 AM

Code that I've used in both apps:

FirebaseDynamicLinks.instance
        .getInitialLink()
        .then((value) => print('getInitialLink: $value'))
        .onError((error, stackTrace) => print(error));

    _appLinks.getInitialAppLink().then((value) {
      print('app link: $value');
      if (value != null) {
        try {
          FirebaseDynamicLinks.instance.getDynamicLink(value).then(
              (dynamicLink) => print('app link as dynamic: $dynamicLink'));
        } catch (e) {
          print(e);
        }
      }
    });

    FirebaseDynamicLinks.instance.onLink
        .listen((event) async => print('onLink: $event'));

And there are results: Working app: Screen Shot 2022-08-03 at 10 54 59 AM

Not working. You can see that onLink is not called, but the app_links plugin works fine: Screen Shot 2022-08-03 at 10 53 57 AM

Any ideas where I can look for the problem cause?

tudor07 commented 2 years ago

@russellwheatley shouldn't it open the app? My issue was about opening the app and getting the link that was clicked in Dart

russellwheatley commented 2 years ago

@tudor07 Yes, it will open your app, and the deep link received is the one I mentioned.

tudor07 commented 2 years ago

@russellwheatley that's not what it happens with the example app, so how can I reproduce my issue using it? In my app, I want my app to open. In the example app, a webpage is opened even though the app is installed.

russellwheatley commented 2 years ago

are you running in flutter run --no-pub --release mode? Have you followed every one of these steps exactly?

krishna700 commented 2 years ago

I am also facing this issue. The dynamic link opens the app correctly, but doesn't navigate when app is in terminated state. So initial link is definitely null.

Android is working fine in all states. I have tried this on release builds. I can confirm it doesn't work in IOS terminated state.

This is a very crucial feature as 80% of our customers are in IOS. Please help.

[✓] Flutter (Channel stable, 2.10.5, on macOS 12.4 21F79 darwin-arm, locale en-IN) [✓] Android toolchain - develop for Android devices (Android SDK version 32.1.0-rc1) [✓] Xcode - develop for iOS and macOS (Xcode 13.4.1) [✓] Chrome - develop for the web [✓] Android Studio (version 2021.1) [✓] VS Code (version 1.69.2) [✓] Connected device (1 available) [✓] HTTP Host Availability

firebase_core: ^1.20.0 firebase_messaging: ^12.0.1 firebase_analytics: ^9.3.0 firebase_crashlytics: ^2.8.6 firebase_dynamic_links: ^4.3.3 firebase_performance: ^0.8.2+1

ArkadiuszDatka commented 2 years ago

Similarly for me, it works correctly on android and ios <= 15.5, but it does not work on ios 15.6. @krishna700 if you can verify that you can run on an older ios version.

russellwheatley commented 2 years ago

@ArkadiuszDatka - My device uses iOS 15.6. It works.

krishna700 commented 2 years ago

@ArkadiuszDatka I checked in a device running 15.4.1, it doesn't work in terminated state.

krishna700 commented 2 years ago

@russellwheatley My device uses 15.6, but it doesn't work :(

ArkadiuszDatka commented 2 years ago

@krishna700 if you can put 2 seconds delay before FirebaseDynamicLinks.instance.getInitialLink() and check if the link is non-null

holyboom1 commented 2 years ago

i'm found temporary solution this problem... i'm use library app_links

Future<String?> getInitialLink() async { final _appLinks = AppLinks(); final PendingDynamicLinkData? linkData = await FirebaseDynamicLinks.instance.getInitialLink(); if (linkData != null) { return linkData.link.queryParameters[payload]; } else { final Uri? uri = await _appLinks.getInitialAppLink(); if (uri != null) { final PendingDynamicLinkData? appLinkData = await FirebaseDynamicLinks.instance.getDynamicLink(uri); if (appLinkData != null) { return appLinkData.link.queryParameters[payload]; }}} return null; }

and we also can add platform check