Daniel
Daniel

Reputation: 1610

Firebase Dynamic Link is not caught by getInitialLink if app is closed and opened by that link

Programmatically generated dynamic links are not properly catched by

FirebaseDynamicLinks.instance.getInitialLink().

if the app is closed. However, if the app is open it is properly detected by the listener for new incoming dynamic links. It is not clear to me if it is a setup problem, how I generate the dynamic link.

To Reproduce

First set up Firebase for Flutter project as documented. Then to set up a dynamic link:

/// See also
/// https://firebase.google.com/docs/dynamic-links/use-cases/rewarded-referral
/// how to implement referral schemes using Firebase.
Future<ShortDynamicLink> buildDynamicLink(String userId) async {
  final PackageInfo packageInfo = await PackageInfo.fromPlatform();
  final String packageName = packageInfo.packageName;

  var androidParams = AndroidParameters(
    packageName: packageInfo.packageName,
    minimumVersion: Constants.androidVersion, // app version and not the Android OS version
  );

  var iosParams = IosParameters(
    bundleId: packageInfo.packageName,
    minimumVersion: Constants.iosVersion, // app version and not the iOS version
    appStoreId: Constants.iosAppStoreId,
  );

  var socialMetaTagParams = SocialMetaTagParameters(
    title: 'Referral Link',
    description: 'Referred app signup',
  );

  var dynamicLinkParams = DynamicLinkParameters(
    uriPrefix: 'https://xxxxxx.page.link',
    link: Uri.parse('https://www.xxxxxxxxx${Constants.referralLinkPath}?${Constants.referralLinkParam}=$userId'),
    androidParameters: androidParams,
    iosParameters: iosParams,
    socialMetaTagParameters: socialMetaTagParams,
  );

  return dynamicLinkParams.buildShortLink();
}

This dynamic link then can be shared with other new users.

I listen for initial links at app startup and then for new incoming links.

1) The link properly opens the app if the app is not running but the getInitialLink does not get it.

2) If the app is open the link is properly caught by the listener and all works.

Here is the very simple main.dart that I used to verify 1) that the initial link is not found with FirebaseDynamicLinks.instance.getInitialLink().

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  PendingDynamicLinkData linkData = await FirebaseDynamicLinks.instance.getInitialLink();
  String link = linkData?.link.toString();
  runApp(MyTestApp(link: link));
}

class MyTestApp extends StatelessWidget {
  final String link;

  MyTestApp({this.link});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        builder: (BuildContext context, Widget child) {
          return Scaffold(
            body: Container(
              child: Center(
                  child: Text('Initial dynamic Firebase link: $link')
              ),
            ),
          );
        }
    );
  }
}

Expected behavior

The link should open the app and trigger FirebaseDynamicLinks.instance.getInitialLink()..

Additional context

I hope properly configured Firebase project with Firebase console. To verify this I created a dynamic link to be used with Firebase Auth 'signup by email link' and these dynamic links are working as expected, also when the app is not open.

The point here is that the referral dynamic link that I generate programmatically is opening the app when it is closed but is then not caught by FirebaseDynamicLinks.instance.getInitialLink(), and to make things more confusing, works as expected if the app is open. In that case it is caught by the listener FirebaseDynamicLinks.instance.onLink.

I also set up the WidgetsBindingObserver in Flutter to handle that callback as required, when the app gets its focus back.

Any help is greatly appreciated. Debugging is very tricky, as you need to do it on a real device and not in the simulator. To make things worse, I did not figure out how to attach a debugger while the dynamic link opens the app. This means I am also stuck in investigating this issue further.

Upvotes: 16

Views: 10564

Answers (3)

Draeko
Draeko

Reputation: 111

Initialize Link Listener. This works for me.

class _MainAppState extends State<MainApp> {  

    Future<void> initDynamicLinks() async {
        print("Initial DynamicLinks");
        FirebaseDynamicLinks dynamicLinks = FirebaseDynamicLinks.instance;

    // Incoming Links Listener
    dynamicLinks.onLink.listen((dynamicLinkData) {
      final Uri uri = dynamicLinkData.link;
      final queryParams = uri.queryParameters;
      if (queryParams.isNotEmpty) {
        print("Incoming Link :" + uri.toString());
        //  your code here
      } else {
        print("No Current Links");
        // your code here
      }
    });

    // Search for Firebase Dynamic Links
    PendingDynamicLinkData? data = await dynamicLinks
        .getDynamicLink(Uri.parse("https://yousite.page.link/refcode"));
    final Uri uri = data!.link;
    if (uri != null) {
      print("Found The Searched Link: " + uri.toString());
      // your code here
    } else {
      print("Search Link Not Found");
      // your code here
    }    
  }         

Future<void> initFirebase() async {
    print("Initial Firebase");
    WidgetsFlutterBinding.ensureInitialized();
    await Firebase.initializeApp();
    // await Future.delayed(Duration(seconds: 3));
    initDynamicLinks();
  }         

@override
  initState() {
    print("INITSTATE to INITIALIZE FIREBASE");
    super.initState();
    initFirebase();
  }

Upvotes: 1

Daniel
Daniel

Reputation: 1610

I tried Rohit's answer and because several people face the same issue I add here some more details. I created a stateful widget that I place pretty much at the top of the widget tree just under material app:

class DynamicLinkWidget extends StatefulWidget {
  final Widget child;

  DynamicLinkWidget({this.child});

  @override
  State<StatefulWidget> createState() => DynamicLinkWidgetState();
}

class DynamicLinkWidgetState extends State<DynamicLinkWidget> with WidgetsBindingObserver {

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
    locator.get<DynamicLinkService>().initDynamicLinks();
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Container(child: widget.child);
  }
}

I use the getit package to inject services. The dynamic link service is roughly like this:

class DynamicLinkService {
  final UserDataService userDataService;

  final ValueNotifier<bool> isLoading = ValueNotifier<bool>(false);

  final BehaviorSubject<DynamicLinkError> _errorController = BehaviorSubject<DynamicLinkError>();

  Stream<DynamicLinkError> get errorStream => _errorController.stream;

  DynamicLinkService({@required this.userDataService});

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

    if (deepLink != null) {
      processDynamicLink(deepLink);
    }

    FirebaseDynamicLinks.instance.onLink(
        onSuccess: (PendingDynamicLinkData dynamicLink) async {
          final Uri deepLink = dynamicLink?.link;
          if (deepLink != null) {
            print('=====> incoming deep link: <${deepLink.toString()}>');
            processDynamicLink(deepLink);
          }
        },
        onError: (OnLinkErrorException error) async {
          throw PlatformException(
            code: error.code,
            message: error.message,
            details: error.details,
          );
        }
    );
  }

  Future<void> processDynamicLink(Uri deepLink) async {
    if (deepLink.path == Constants.referralLinkPath && deepLink.queryParameters.containsKey(Constants.referrerLinkParam)) {
      var referrer = referrerFromDynamicLink(deepLink);
      userDataService.processReferrer(referrer);
    } else {
      await FirebaseEmailSignIn.processDynamicLink(
          deepLink: deepLink,
          isLoading: isLoading,
          onError: this.onError
      );
    }
  }

  void onError(DynamicLinkError error) {
    _errorController.add(error);
  }
}

You see that my app has to process two types of dynamic link, one is for email link signup, the other link is our referral link that is used to link users together and allow us to understand who introduced a new user to us. This setup works now for us. Hope it helps others too.

Upvotes: -1

Rohit Soni
Rohit Soni

Reputation: 1447

  • In The FirebaseDynamicLinks Two Methods 1) getInitialLink() 2) onLink().

  • If When Your App Is Open And You Click On Dynamic Link Then Will Be Call FirebaseDynamicLinks.instance.onLink(), If Your App Is Killed Or Open From PlayStore Then You Get From FirebaseDynamicLinks.instance.getInitialLink();.

  • First Of You Need To Initialise Instance Of FirebaseDynamicLinks.instance.

      static void initDynamicLinks() async {
        final PendingDynamicLinkData data =
            await FirebaseDynamicLinks.instance.getInitialLink();
        final Uri deepLink = data?.link;
    
        if (deepLink != null && deepLink.queryParameters != null) {
          SharedPrefs.setValue("param", deepLink.queryParameters["param"]);
        }
    
        FirebaseDynamicLinks.instance.onLink(
            onSuccess: (PendingDynamicLinkData dynamicLink) async {
          final Uri deepLink = dynamicLink?.link;
    
          if (deepLink != null && deepLink.queryParameters != null) {
            SharedPrefs.setValue("param", deepLink.queryParameters["param]);
          }
        }, onError: (OnLinkErrorException e) async {
          print(e.message);
        });
      }
    

Upvotes: 12

Related Questions