sendSMSAsync results in "No messaging application available" for EAS builds only

The background is that I am converting an existing expo workflow application to be built via EAS in order to build an APK that will pass Google’s Families Policy Requirements.

npmPackages:
“expo”: “^41.0.0”,
“expo-sms”: “~9.1.2”,

eas version
eas-cli/0.17.0 win32-x64 node-v12.18.3

Expo Workflow: managed


The application uses sendSMSAsync from expo-sms and this works with the APK when built using the expo build:android command.

However the new APK’s that we get from building it via eas build results in a runtime error where the sendSMSAsync promise fails with the message “No messaging application available”.

I think it might an issue with newer versions of android, as I get this error on my Pixel 5, but when I try the same APK on older devices it does work and the SMS application does opens up.

I have tried giving my application various permission via the android.permissions setting in app,json with no success. When I compare the android manifests from the two versions I did notice the old working one had a section

    <intent>
      <action
          name='android.intent.action.SENDTO'>
      </action>

which is missing in the new one.

Is this a bug with EAS? Or is there some config I am missing for requesting that permission?

Thanks,
Ivan

hi there! thanks for the report. it looks like we need to add this to androidmanifest for the library, similar to what we did for other libraries in this pr: [expo] Add queries to `AndroidManifest` by lukmccall · Pull Request #11829 · expo/expo · GitHub. we’ll fix this for sdk 42. you can use a config plugin to add the intent query to your androidmanifest for now to work around this.

1 Like

If anyone else has has the same problem. The quick and dirty plugin I wrote has solved it for us.

app.config.js


import { withAndroidManifest } from '@expo/config-plugins';

// Using helpers keeps error messages unified and helps cut down on XML format changes.
//const { addMetaDataItemToMainApplication, getMainApplicationOrThrow, ge } = AndroidConfig.Manifest;
export const withMyCustomConfig = config => {
  return withAndroidManifest(config, async (config) => {
    // Modifiers can be async, but try to keep them fast.
    config.modResults = await setCustomConfigAsync(config, config.modResults);
    return config;
  });
};
// Splitting this function out of the mod makes it easier to test.
async function setCustomConfigAsync(config, androidManifest) {
  AddActionToManifest(androidManifest,
    // value for `android:name`
    'android.intent.action.SENDTO',
    'smsto');
  return androidManifest;
}

function AddActionToManifest(androidManifest, itemName, ItemData) {
  let existingAction, queries, intent;
  let manifest = androidManifest["manifest"]
  const newAction = {
    "action": { $: prefixAndroidKeys({ name: itemName }) },
    "data": { $: prefixAndroidKeys({ scheme: ItemData }) }
  };

  if (manifest['queries']) {
    queries = manifest['queries'];
  }
  else {
    queries = [];
    manifest['queries'] = queries;
  }

  if (queries.filter(e => e["intent"]).length > 0) {
    intent = queries.filter(e => e["intent"])[0].intent;
  }
  else {
    intent = [];
    queries.push( {intent} ) ;
  }

  if (intent.filter(e => e["action"] && e["action"][0].$['android:name'] === itemName).length == 0) {
    intent.push(newAction);
  }

  //console.log("androidManifestAFter = " + JSON.stringify(androidManifest)); 

  return androidManifest;
}

function prefixAndroidKeys(head) {
  // prefix all keys with `android:`
  return Object.entries(head).reduce((prev, [key, curr]) => ({ ...prev, [`android:${key}`]: curr }), {});
}

export default ({ config }) => {
  withMyCustomConfig(config);
  return {
    ...config,
  };
};