flutterfire icon indicating copy to clipboard operation
flutterfire copied to clipboard

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

Open tudor07 opened this issue 3 years ago • 37 comments

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<MyApp> {
  @override
  void initState() {
    super.initState();

    FirebaseDynamicLinks.instance.onLink.listen(_handleDeepLink);
    _getInitialLink();
  }

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

  @override
  Widget build(BuildContext context) {
    // return app widgets...
  }

  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);
    }
  }
}
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

tudor07 avatar Jul 15 '22 07:07 tudor07

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 ?

darshankawar avatar Jul 15 '22 11:07 darshankawar

  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

tudor07 avatar Jul 19 '22 11:07 tudor07

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 ?

darshankawar avatar Jul 20 '22 10:07 darshankawar

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

tudor07 avatar Jul 20 '22 13:07 tudor07

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.

darshankawar avatar Jul 21 '22 11:07 darshankawar

I'm using iOS 15.5

tudor07 avatar Jul 21 '22 11:07 tudor07

@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
  }

darshankawar avatar Jul 22 '22 11:07 darshankawar

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 :)

matthewfx avatar Jul 26 '22 22:07 matthewfx

@matthewfx I tested on a real device

tudor07 avatar Jul 27 '22 07:07 tudor07

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

matthewfx avatar Jul 27 '22 07:07 matthewfx

@darshankawar linkData is null

tudor07 avatar Jul 27 '22 09:07 tudor07

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

darshankawar avatar Jul 27 '22 10:07 darshankawar

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.

russellwheatley avatar Jul 28 '22 14:07 russellwheatley

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

tudor07 avatar Aug 01 '22 06:08 tudor07

@tudor07 Yes.

russellwheatley avatar Aug 01 '22 12:08 russellwheatley

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

russellwheatley avatar Aug 01 '22 12:08 russellwheatley

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.

tudor07 avatar Aug 02 '22 07:08 tudor07

I'll re-verify and confirm.

darshankawar avatar Aug 02 '22 07:08 darshankawar

@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.

darshankawar avatar Aug 02 '22 10:08 darshankawar

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

IMG_DDA2101B5E12-1

tudor07 avatar Aug 02 '22 13:08 tudor07

Screen recording:

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

tudor07 avatar Aug 02 '22 13:08 tudor07

@tudor07 - did you try this link?

reactnativefirebase.page.link/bFkn

russellwheatley avatar Aug 02 '22 13:08 russellwheatley

Yes, same with that one

tudor07 avatar Aug 02 '22 14:08 tudor07

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

russellwheatley avatar Aug 02 '22 14:08 russellwheatley

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

russellwheatley avatar Aug 02 '22 14:08 russellwheatley

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.

tudor07 avatar Aug 03 '22 07:08 tudor07

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

russellwheatley avatar Aug 03 '22 07:08 russellwheatley

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?

maciejbrzezinski avatar Aug 03 '22 09:08 maciejbrzezinski

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

tudor07 avatar Aug 03 '22 09:08 tudor07

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

russellwheatley avatar Aug 03 '22 11:08 russellwheatley