Search code examples
firebasecordovaios-universal-linksdeeplink

Cordova universal links on a different activity


I am implementing Firebase Authentication in my Cordova application. The android app integrates Cordova in an activity that is not the main/launcher activity. Due to this, a required dependency plugin "cordova-universal-links-plugin" does not target the correct activity (it targets the main/launcher activity).

Is there a way to specify the target activity for universal-links? If not, how can I make a workaround/hack to fix this issue (as the plugin is not being maintained anymore)?


Solution

  • There is no known method of specifying the target activity for cordova-universal-links-plugin. You can create a workaround by running scripts before/after cordova prepare.

    The fix will work like this:

    1. You have to rearrange your activities in AndroidManifest.xml so that the target activity is ordered before the main/launcher activity. I am assuming the target activity initially looks like this:
    <activity android:name=".CordovaActivity">
      <intent-filter android:label="@string/launcher_name">
        <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
    </activity>
    
    1. Now, you'll write two NodeJS scripts: before_prepare.js and after_prepare.js.

      a) before_prepare.js: This will add an <action android:name="android.intent.action.VIEW" /> after the <category /> tag for your target activity. Since "CordovaActivity" is temporarily also a main/launcher activity & is before the actual main activity, the universal-links plugin should target it instead.

      b) after_prepare.js: We want this to run after the plugin applies the universal-links intent-filters to your target activity. It will remove the <action /> tag you've added. This is necessary since Android can't allow two main activities.

    2. Add before_prepare.js to your project-level config.xml as a before_prepare Cordova hook.

    3. Because Cordova runs your hooks before the plugin hooks, you can't add after_prepare.js as an after_prepare Cordova hook. That's because the <action /> tag would be removed before the universal-links plugin gets a chance to run. Instead, you'll have to run it after the cordova prepare command. I recommend doing this by using an npm script (npm run prepare) that runs after_prepare.js after cordova prepare.

    My implementation depends on elementtree for XML editing (it is also used by Cordova internally):

    npm install elementtree
    

    before_prepare.js:

    const et = require('elementtree')
    const fs = require('fs')
    
    const MANIFEST_FILE = '/.../CordovaProject/platforms/android/app/src/main/AndroidManifest.xml'
    
    /**
     * Add main action intent from `SurkartaActivity`.
     *
     * @param {ElementTree} manifestTree
     */
    function addMainAction (manifestTree) {
        const intentFilterElement = manifestTree.find("./application/activity[@android:name='.SurakartaActivity']")
            .getchildren()[0]
    
        const mainActionElement = et.SubElement(intentFilterElement, 'action')
        mainActionElement.set('android:name', 'android.intent.action.MAIN')
    }
    
    // Cordova hook executes module.exports
    module.exports = function () {
        const manifestTree = et.parse(fs.readFileSync(MANIFEST_FILE, 'utf8'))
        addMainAction(manifestTree)
    
        console.log(manifestTree.write())
        fs.writeFileSync(MANIFEST_FILE, manifestTree.write())
    }
    

    after_prepare.js:

    const et = require('elementtree')
    const fs = require('fs')
    
    const MANIFEST_FILE = '/.../CordovaProject/platforms/android/app/src/main/AndroidManifest.xml'
    
    /**
     * Remove main action intent from `SurkartaActivity`.
     *
     * @param {ElementTree} manifestTree
     */
    function stripMainAction (manifestTree) {
        const intentFilterElement = manifestTree.find("./application/activity[@android:name='.SurakartaActivity']")
            .getchildren()[0]
        intentFilterElement.remove(intentFilterElement.getchildren()[1])
    }
    
    function hey () {
        const manifestTree = et.parse(fs.readFileSync(MANIFEST_FILE, 'utf8'))
        stripMainAction(manifestTree)
        fs.writeFileSync(MANIFEST_FILE, manifestTree.write())
    }
    
    module.exports = hey
    
    // This isn't an Cordova hook, so run it manually
    hey()
    

    Changes to existing files:

    config.xml:

    <hook type="before_prepare" src="./path/to/before_prepare.js" />
    

    package.json:

    {
      "scripts": {
        "prepare": "cordova prepare; node ./path/to/after_prepare.js"
      },
      "dependencies": {
        "elementtree": "@latest" /* npm install elementtree should do this for you */
      }
    }