React-native-firebase: Failed to create Short Dynamic Link on iOS

Created on 22 Nov 2018  ·  21Comments  ·  Source: invertase/react-native-firebase


Issue

When trying to create a short dynamic link on iOS I get an exception from ExceptionsManager. Creating a regular dynamic link works as expected. Android works as expected.

const dm = new firebase.links.DynamicLink(
  'https://google.no',
  'example.page.link'
);

const dynamicLinks = firebase.links();

dynamicLinks
  .createShortDynamicLink(dm)
  .then(link => console.log('Short dynamic link: ', link))
  .catch(err => console.error(err));

dynamicLinks
  .createDynamicLink(dm)
  .then(link => console.log('Dynamic link: ', link))
  .catch(err => console.error(err));

Which produce the following output in my console:

ExceptionsManager.js:84 Short dynamic link error:  Error: Failed to create Short Dynamic Link
    at createErrorFromErrorData (NativeModules.js:146)
    at NativeModules.js:95
    at MessageQueue.__invokeCallback (MessageQueue.js:397)
    at MessageQueue.js:127
    at MessageQueue.__guard (MessageQueue.js:297)
    at MessageQueue.invokeCallbackAndReturnFlushedQueue (MessageQueue.js:126)
    at t (RNDebuggerWorker.js:1)
reactConsoleErrorHandler @ ExceptionsManager.js:84
console.error @ YellowBox.js:59
(anonymous) @ VM344:21
tryCallOne @ core.js:37
(anonymous) @ core.js:123
(anonymous) @ JSTimers.js:295
_callTimer @ JSTimers.js:152
_callImmediatesPass @ JSTimers.js:200
callImmediates @ JSTimers.js:464
__callImmediates @ MessageQueue.js:320
(anonymous) @ MessageQueue.js:135
__guard @ MessageQueue.js:297
flushedQueue @ MessageQueue.js:134
invokeCallbackAndReturnFlushedQueue @ MessageQueue.js:130
t @ RNDebuggerWorker.js:1
VM344:24 Dynamic link:  https://example.page.link/?link=https%3A%2F%2Fgoogle%2Eno

For some reason, createShortDynamicLink produces the exception in the first part of the log above, while createDynamicLink produces the expected output in the last line of the log above.

note: anything identifying our project is anonymised (replaced actual domains and identifiers with example.com). We are using the correct page.link, not only does it work to create a long dynamic link, but the rest of deep linking functionality works as expected.




Project Files






iOS

ios/Podfile:

  • [ ] I'm not using Pods
  • [x] I'm using Pods and my Podfile looks like:
# Uncomment the next line to define a global platform for your project
platform :ios, '9.3'

def pods
  pod 'Firebase/Core', '~> 5.11.0'
  pod 'Firebase/DynamicLinks', '~> 5.11.0'
  pod 'Fabric', '~> 1.8.0'
  pod 'Crashlytics', '~> 3.11'
  pod 'Intercom', '~> 5.1.8'
  pod 'AdobeMobileSDK', '~> 4.17.0'
end

target 'prod' do
  pods
end

target 'test' do
  pods
end

target 'dev' do
  pods
end

AppDelegate.m:

#import "AppDelegate.h"

#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>
#import <CodePush/CodePush.h>
#import "MHEnvironment.h"
#import <Intercom/Intercom.h>
#import <Firebase.h>
#import "RNFirebaseLinks.h"
#import <ADBMobile.h>
#import "AdobeMobile.h"

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{

  // Firebase
  // Leave at top, to let all incidents during initialisation be reported to firebase
  NSString *googleServicePlist = [NSString stringWithFormat:@"GoogleService-Info-%@",
                                  [MHEnvironment sharedInstance].environment];
  NSString *filePath = [[NSBundle mainBundle] pathForResource:googleServicePlist ofType:@"plist"];
  [FIROptions defaultOptions].deepLinkURLScheme = [MHEnvironment sharedInstance].dynamicLinksScheme; // bundle identifier (multiple targets)
  FIROptions *options = [[FIROptions alloc] initWithContentsOfFile:filePath];
  [FIRApp configureWithOptions:options];

{...}
}

{...}

- (BOOL)application:(UIApplication *)application
            openURL:(NSURL *)url
            options:(NSDictionary<NSString *, id> *)options {
  return [[RNFirebaseLinks instance] application:application openURL:url options:options];
}

- (BOOL)application:(UIApplication *)application
continueUserActivity:(NSUserActivity *)userActivity
 restorationHandler:(void (^)(NSArray *))restorationHandler {
  return [[RNFirebaseLinks instance] application:application continueUserActivity:userActivity restorationHandler:restorationHandler];
}

@end

Android

android/build.gradle:

import groovy.json.JsonSlurper

// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    ext.kotlin_version = '1.3.0'
    repositories {
        google()
        jcenter()
        maven {
            url 'https://maven.fabric.io/public'
        }
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.1.4'
        classpath 'com.google.gms:google-services:4.0.1'
        classpath 'io.fabric.tools:gradle:1.25.4'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        google()
        mavenLocal()
        jcenter()
        maven {
            // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
            url "$rootDir/../node_modules/react-native/android"
        }
    }
}

ext {
    compileSdkVersion = 27
    targetSdkVersion = 26
    buildToolsVersion = "27.0.3"
    supportLibVersion = "27.1.1"
}

subprojects {
    ext {
        def npmVersion = getNpmVersion()
        versionMajor = npmVersion['major']
        versionMinor = npmVersion['minor']
        versionPatch = npmVersion['patch']
    }
}

def getNpmVersion() {
    def packageJsonFile = file("$projectDir.path/../package.json")
    def packageJson = new JsonSlurper().parseText(packageJsonFile.text)
    def versionString = packageJson['version']
    def (major, minor, patch) = versionString.tokenize('.')

    return [major: major, minor: minor, patch: patch]
}

android/app/build.gradle:

apply plugin: "com.android.application"
apply plugin: 'kotlin-android'
apply plugin: "io.fabric"

import com.android.build.OutputFile

project.ext.react = [
        bundleInStaging      : true,
        devDisabledInStaging : true,
        nodeExecutableAndArgs: ["/usr/local/bin/node"]
]
project.ext.vectoricons = [
        iconFontNames: [ 'FontAwesome.ttf', 'MaterialIcons.ttf' ]
]

apply from: "../../node_modules/react-native/react.gradle"
apply from: "../../node_modules/react-native-code-push/android/codepush.gradle"
apply from: "../../node_modules/react-native-vector-icons/fonts.gradle"

def enableSeparateBuildPerCPUArchitecture = false

def enableProguardInReleaseBuilds = false

android {
    compileSdkVersion rootProject.compileSdkVersion
    buildToolsVersion rootProject.buildToolsVersion

    defaultConfig {
        applicationId "com.example.app"
        minSdkVersion 16
        targetSdkVersion rootProject.ext.targetSdkVersion
        versionCode readBuildVersion()
        versionName "${versionMajor}.${versionMinor}.${versionPatch}"
        multiDexEnabled true
        ndk {
            abiFilters "armeabi-v7a", "x86"
        }
    }
    signingConfigs {
        release {
            if (project.hasProperty('RELEASE_STORE_FILE')) {
                storeFile file(RELEASE_STORE_FILE)
                storePassword RELEASE_STORE_PASSWORD
                keyAlias RELEASE_KEY_ALIAS
                keyPassword RELEASE_KEY_PASSWORD
            }
        }
    }
    buildTypes {
        dev {
            initWith debug
            applicationIdSuffix ".dev"
            matchingFallbacks = ['debug']
        }
        staging {
            initWith release
            signingConfig signingConfigs.release
            applicationIdSuffix ".test"
            matchingFallbacks = ['release']
        }
        release {
            signingConfig signingConfigs.release
            minifyEnabled enableProguardInReleaseBuilds
            proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
        }
    }
    splits {
        abi {
            reset()
            enable enableSeparateBuildPerCPUArchitecture
            universalApk false  // If true, also generate a universal APK
            include "armeabi-v7a", "x86"
        }
    }
    // applicationVariants are e.g. debug, release
    applicationVariants.all { variant ->
        variant.outputs.each { output ->
            // For each separate APK per architecture, set a unique version code as described here:
            // http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits
            def versionCodes = ["armeabi-v7a": 1, "x86": 2]
            def abi = output.getFilter(OutputFile.ABI)
            if (abi != null) {  // null for the universal-debug, universal-release variants
                output.versionCodeOverride =
                        versionCodes.get(abi) * 1048576 + defaultConfig.versionCode
            }
        }
    }
    dexOptions {
        jumboMode = true
    }
}

dependencies {
    implementation project(':react-native-webview')
    implementation project(':react-native-svg')
    implementation project(':react-native-contacts')
    implementation project(':react-native-intercom')
    implementation project(':react-native-image-picker')
    implementation project(':react-native-code-push')
    implementation project(':react-native-firebase')
    implementation "com.google.android.gms:play-services-base:16.0.1"
    implementation "com.google.firebase:firebase-core:16.0.4"
    implementation "com.google.firebase:firebase-analytics:16.0.4"
    implementation "com.google.firebase:firebase-invites:16.0.4"
    implementation('com.crashlytics.sdk.android:crashlytics:2.9.5@aar') {
        transitive = true
    }
    implementation "com.adobe.mobile:adobeMobileLibrary:4.17.0"
    implementation fileTree(dir: "libs", include: ["*.jar"])
    implementation "com.android.support:multidex:1.0.3"
    implementation "com.android.support:appcompat-v7:${rootProject.supportLibVersion}"
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation "com.facebook.react:react-native:+" // From node_modules
}

apply plugin: 'com.google.gms.google-services'

// Run this once to be able to run the application with BUCK
// puts all compile dependencies into folder libs for BUCK to use
task copyDownloadableDepsToLibs(type: Copy) {
    from configurations.compile
    into 'libs'
}

task doIncrementBuildVersion << {
    println 'Incrementing build version...'
    incrementBuildVersion()
}

def readVersion() {
    new File(project.rootDir, 'version.properties').withInputStream { stream ->
        def version = new Properties()
        version.load(stream)
        return version
    }
}

def readBuildVersion() {
    def version = readVersion()
    return version['build'] as int
}

def incrementBuildVersion() {
    def versionFile = new File(project.rootDir, 'version.properties')

    def version = readVersion() as Properties

    def build = version['build'] as int
    build++

    version['build'] = build.toString()

    versionFile.withOutputStream { stream ->
        version.store(stream, null)
    }

    println 'Build version is now ' + build

    return build
}

android/settings.gradle:

rootProject.name = 'example'
include ':react-native-webview'
project(':react-native-webview').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-webview/android')
include ':react-native-svg'
project(':react-native-svg').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-svg/android')
include ':react-native-contacts'
project(':react-native-contacts').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-contacts/android')
include ':react-native-intercom'
project(':react-native-intercom').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-intercom/android')
include ':react-native-image-picker'
project(':react-native-image-picker').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-image-picker/android')
include ':react-native-firebase'
project(':react-native-firebase').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-firebase/android')
include ':react-native-code-push'
project(':react-native-code-push').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-code-push/android/app')

include ':app'

MainApplication.kt:

package com.example.app

import android.support.multidex.MultiDexApplication
import com.facebook.react.ReactApplication
import com.facebook.react.ReactNativeHost
import com.facebook.react.ReactPackage
import com.facebook.react.shell.MainReactPackage
import com.facebook.soloader.SoLoader
import com.horcrux.svg.SvgPackage
import com.imagepicker.ImagePickerPackage
import com.microsoft.codepush.react.CodePush
import com.reactnativecommunity.webview.RNCWebViewPackage
import com.robinpowered.react.Intercom.IntercomPackage
import com.rt2zz.reactnativecontacts.ReactNativeContacts
import io.intercom.android.sdk.Intercom
import io.invertase.firebase.RNFirebasePackage
import io.invertase.firebase.links.RNFirebaseLinksPackage
import io.invertase.firebase.analytics.RNFirebaseAnalyticsPackage
import io.invertase.firebase.fabric.crashlytics.RNFirebaseCrashlyticsPackage
{...}

class MainApplication : MultiDexApplication(), ReactApplication {

    private val mReactNativeHost: ReactNativeHost = object : ReactNativeHost(this) {

        override fun getJSBundleFile(): String? {
            return CodePush.getJSBundleFile()
        }

        override fun getUseDeveloperSupport(): Boolean {
            return BuildConfig.DEBUG
        }

        override fun getPackages(): List<ReactPackage> {
            return listOf(
                    MainReactPackage(),
                    RNCWebViewPackage(),
                    SvgPackage(),
                    ReactNativeContacts(),
                    IntercomPackage(),
                    ImagePickerPackage(),
                    RNFirebasePackage(),
                    RNFirebaseLinksPackage(),
                    RNFirebaseAnalyticsPackage(),
                    RNFirebaseCrashlyticsPackage(),
                    CodePush(resources.getString(R.string.codePush_deploymentKey), applicationContext, BuildConfig.DEBUG)
        }
    }

    override fun getReactNativeHost(): ReactNativeHost {
        return mReactNativeHost
    }

    override fun onCreate() {
        super.onCreate()
        SoLoader.init(this, false)
        Intercom.initialize(this, resources.getString(R.string.intercom_apiKey), resources.getString(R.string.intercom_appId))
    }
}

AndroidManifest.xml:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:tools="http://schemas.android.com/tools"
          package="com.example.app">

    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
    <uses-permission android:name="android.permission.READ_PROFILE"/>
    <uses-permission android:name="android.permission.READ_CONTACTS"/>
    <uses-permission android:name="android.permission.CAMERA"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.USE_FINGERPRINT" />

    <application
        android:name=".MainApplication"
        android:allowBackup="true"
        android:label="@string/app_name"
        android:icon="@mipmap/ic_launcher"
        android:theme="@style/AppTheme">

        <activity
            android:name=".SplashActivity"
            android:label="@string/app_name"
            android:theme="@style/SplashTheme"
            android:screenOrientation="portrait">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>

        <activity
            android:name=".MainActivity"
            android:label="@string/app_name"
            android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
            android:screenOrientation="portrait"
            android:windowSoftInputMode="adjustResize"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.VIEW"/>
                <category android:name="android.intent.category.DEFAULT"/>
                <category android:name="android.intent.category.BROWSABLE"/>
                <data android:host="example.page.link" android:scheme="http"/>
                <data android:host="example.page.link" android:scheme="https"/>
            </intent-filter>
        </activity>

        <activity android:name="com.facebook.react.devsupport.DevSettingsActivity"/>

        <service
            android:name="com.robinpowered.react.Intercom.IntercomIntentService"
            android:exported="false">
            <intent-filter
                android:priority="999">
                <action android:name="com.google.android.c2dm.intent.RECEIVE"/>
            </intent-filter>
        </service>

        <receiver
            android:name="io.intercom.android.sdk.push.IntercomPushBroadcastReceiver"
            tools:replace="android:exported"
            android:exported="true"/>
    </application>
</manifest>

Environment

  • Platform that you're experiencing the issue on:

    • [x] iOS

    • [ ] Android

    • [ ] iOS but have not tested behavior on Android

    • [ ] Android but have not tested behavior on iOS

    • [ ] Both

  • If known, the version of the platform are you experiencing the issue on:

    • ios 12

  • Operating System:

    • [x] MacOS, version: 10.14.1

    • [ ] Windows, version: N/A

    • [ ] Other, please specify: N/A

  • Build Tools:

    • Xcode 10.1

  • React Native version:

    • 0.57.4

  • React Native Firebase library version:

    • 5.1.0

  • Firebase module(s) you're using that has the issue:

    • [ ] Authentication

    • [ ] Analytics

    • [ ] Cloud Firestore

    • [ ] Cloud Messaging (FCM)

    • [ ] Crashlytics

    • [x] Dynamic Links

    • [ ] Functions Callable

    • [ ] Invites

    • [ ] Instance ID

    • [ ] Notifications

    • [ ] Performance Monitoring

    • [ ] Realtime Database

    • [ ] Remote Config

    • [ ] Storage

  • Are you using TypeScript?

    • [x] No

    • [ ] Yes, version: N/A

  • Are you using Expo, e.g. ExpoKit?

    • [x] No

    • [ ] Yes, I've _not_ ejected

    • [ ] Yes, but I have ejected to ExpoKit

    • [ ] Yes, but I have ejected to vanilla React Native

    • Expo version: N/A




Think react-native-firebase is great? Please consider supporting the project with any of the below:

Most helpful comment

I've just spent a good half an hour to figure out why it cannot create the short link, until I figured out the page link needs to contain https://.

new firebase.links.DynamicLink('https://google.no', 'https://example.page.link');

domainURIPrefix: The Firebase project’s Dynamic Links domain. You can find this value in the Dynamic Links section of the Firebase console. It must begin with https://

All 21 comments

me too!
but in my case it's just not work in ios 12.1

I think maybe this issues can help you
https://github.com/invertase/react-native-firebase/issues/1677#issuecomment-440175789

@mf-fengsheng Thanks for the tip, but unfortunately it didn't solve my issue.

I did however find the reason why it fails.

The URL shortener service of Firebase require a API key to be set. This key is defined in GoogleService-Info.plist. Since we have multiple targets, that require different GoogleService-Info.plist we initialise Firebase with [FIRApp configureWithOptions:options];. It seems though that this configuration is not respected when trying to use the URL shortener service through react-native-firebase' createShortDynamicLink function. Renaming one of our GoogleService-Info-<some target>.plist file to GoogleService-Info.plist makes createShortDynamicLink work again.

I'm not sure if the problem lies in react-native-firebase or firebase-ios-sdk, but createShortDynamicLink is looking for the API key in GoogleService-Info.plist, even though Firebase Core is initialised with another location for the Google Service plist file.

The issue is not with react-native-firebase. Issue is tracked in firebase/firebase-ios-sdk#2112

@forsen how can I fix it? Please show the solution.

@dantn93 the problem I had was fixed by https://github.com/firebase/firebase-ios-sdk/pull/2124, and a suitable workaround was described in https://github.com/invertase/react-native-firebase/issues/1702#issuecomment-441031458. Make sure your code finds your API key (as it is required by the url shortener service).

If you're on a enterprise network:
Some enterprise networks have a SSL gateway to spy on the traffic coming in and out of the network to detect malware etc. This kind of "man in the middle" setup is something that is not compatible with the url shortener service. I ran into this problem as well, but I guess it is most likely not your problem.

@forsen i am using react native firebase 5.5.5, but still i am getting this error in ios, android its working

I decided to use google api, the rn firebase deeplink is bad

On Sun, Jul 21, 2019 at 2:00 PM affanhashone notifications@github.com
wrote:

@forsen https://github.com/forsen i am using react native firebase
5.5.5, but still i am getting this error in ios, android its working


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/invertase/react-native-firebase/issues/1702?email_source=notifications&email_token=AE3VM2Z2G7QA2NNGHERHO4LQAQCRNA5CNFSM4GF36DHKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD2N5KAI#issuecomment-513529089,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AE3VM24DRMKAMZARQFNLY33QAQCRNANCNFSM4GF36DHA
.

@dantn93 Thank You Somuch for hinting out that.

I've just spent a good half an hour to figure out why it cannot create the short link, until I figured out the page link needs to contain https://.

new firebase.links.DynamicLink('https://google.no', 'https://example.page.link');

domainURIPrefix: The Firebase project’s Dynamic Links domain. You can find this value in the Dynamic Links section of the Firebase console. It must begin with https://

This is indicated here https://rnfirebase.io/docs/v5.x.x/releases/v5.5.x#5.5.0 and here https://rnfirebase.io/docs/v5.x.x/releases/v5.5.x#Upgrade-instructions and https://rnfirebase.io/docs/v5.x.x/links/reference/DynamicLink#Constructor - I'm not sure what more we can do? That said, @markhomoki can you confirm that this works if you use https?

@markhomoki I was having same thing in code, but still i couldn't create shortLink In IOS. SO i move to REST Full Method

Yes, in my case it's working fine using https://. I copied the example code from https://rnfirebase.io/docs/v5.x.x/links/reference/links which says abc123.page.link. I'm using 5.5.4 btw.

That code didn't change after v5.5.0 that I'm aware of. It may be that there are further configurations required if you use more custom links vs page.link - I haven't implemented this in my app myself but I recall there were all sorts of things that needed to be done in google and apple-specific ways to enable really custom links. Really vague comment but main point is that maybe page.link works and others don't @affanhashone were you trying xyz.page.link or something different?

@mikehardy abc123.page.link this was the example, but i created my own page link, i used with https, and it was working only in android, not on ios

Hmm @affanhashone I would suspect some sort of config issue then, if I recall the config on the iOS side went as far as entitlements or some such, but as I mentioned I haven't integrated it myself so this is necessarily vague, sorry

@mikehardy maybe some configuration issue, but couldnt figure out. so now i am using firebase rest api.

In our case the issue was that there was a / at the end of the domainURIPrefix supplied when creating the DynamicLink.
Then when we use it to create the short dynamic link, the longDynamicLink used has two / before the query params and that screws everything up..

We can probably put a PR up that gets rid of extra slashes at the end... It took me quite a while and Xcode debugging to find the cause.

On v6 we should probably handle these better. Does anyone know the exact rules for link & domainUriPrefix? Currently we just check they start with http or https: https://github.com/invertase/react-native-firebase/blob/master/packages/dynamic-links/lib/builder.js#L52

I'm using https:// and still fail with createShortDynamicLink
=> update: I figured out that I have to create a dynamic link in the same project I used with database

I've just spent a good half an hour to figure out why it cannot create the short link, until I figured out the page link needs to contain https://.

new firebase.links.DynamicLink('https://google.no', 'https://example.page.link');

domainURIPrefix: The Firebase project’s Dynamic Links domain. You can find this value in the Dynamic Links section of the Firebase console. It must begin with https://

Thanks alot bro. made my day
@mikehardy please add it to the docs

Docs are open sources with edit button on each page, please make PR to docs

Was this page helpful?
0 / 5 - 0 ratings