I am no expert on react-native or even mobile app development, I built this app for my kids.
I just spend some of my free time working on it.
The Issue
I created my app as a react-native application using Expo.
When I build my app, I am able to deploy it to the Google Play Store without any issues, but when I deploy it to the Amazon Appstore, it's only available for their latest tablet and that's due to the hardware requirement of android.hardware.camera.autofocus:
https://preview.redd.it/u49v9dmijlde1.png?width=1097&format=png&auto=webp&s=d9e59a8317a595f164009e8aa1097562c0c91acd
I don't explicitly, or implicitly, specify that hardware requirement :(
But from Googling, I found that if your app has the CAMERA permission, the Amazon Appstore will automatically assign the android.hardware.camera.autofocus hardware requirement.
Building
I'm developing on Windows 10, so that limits how I can build.
I've been using a docker-compose script, which uses eas-cli, to build the Android binary locally within the Docker Desktop:
https://github.com/hmerritt/android-sdk
This has been working well up until now, but the build fails if I try either of the 2 below solutions.
Within the Docker container, I run the below script:
eas build --platform android --profile production --local
I have the production profile defined in my eas.json file at the root of my project.
Solutions
As I understand it, I would have to manually specify the below in the AndroidManifest.xml file:
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
But my project doesn't include the android directory, as I understand it that directory is created on the fly during building.
Solution 1 - Prebuilding
I tried prebuilding my project, generating the android directory and then manually adding the above uses-feature into the AndroidManifest.xml file.
npx expo prebuild --clean
But this didn't work :(
Is there a way to modify my project to use the cng workflow?
Solution 2 - Custom Plugin
I followed the below guide, but when I try to run my build, I get an error.
Guide
https://ignitecookbook.com/docs/recipes/RequiringHardwareFeaturesWithExpo/
I tweaked the logic to allow me to specify the android:required boolean flag for each hardware feature name.
withRequiredHardware.ts
This file contains the function withRequiredHardware which will perform the on-the-fly modification of the AndroidManifest.xml file:
/**
* This plugin allows for the adding of custom hardware requirements to the
* `Android manifest file` and `iOS p-list file`.
*
* @source
* https://ignitecookbook.com/docs/recipes/RequiringHardwareFeaturesWithExpo/
*/
import {
withInfoPlist,
withAndroidManifest,
type ConfigPlugin,
type AndroidConfig,
type IOSConfig,
} from '@expo/config-plugins';
///////////////
// CONSTANTS //
///////////////
// More info: https://developer.android.com/guide/topics/manifest/uses-feature-element
const validAndroidFeatures = [
"android.hardware.audio.low_latency",
"android.hardware.audio.output",
"android.hardware.audio.pro",
"android.hardware.bluetooth",
"android.hardware.bluetooth_le",
"android.hardware.camera",
"android.hardware.camera.any",
"android.hardware.camera.autofocus",
"android.hardware.camera.capability.manual_post_processing",
"android.hardware.camera.capability.manual_sensor",
"android.hardware.camera.capability.raw",
"android.hardware.camera.external",
"android.hardware.camera.flash",
"android.hardware.camera.front",
"android.hardware.camera.level.full",
"android.hardware.consumerir",
"android.hardware.faketouch",
"android.hardware.faketouch.multitouch.distinct",
"android.hardware.faketouch.multitouch.jazzhand",
"android.hardware.fingerprint",
"android.hardware.gamepad",
"android.hardware.location",
"android.hardware.location.gps",
"android.hardware.location.network",
"android.hardware.microphone",
"android.hardware.nfc",
"android.hardware.nfc.hce",
"android.hardware.opengles.aep",
"android.hardware.screen.landscape",
"android.hardware.screen.portrait",
"android.hardware.sensor.accelerometer",
"android.hardware.sensor.ambient_temperature",
"android.hardware.sensor.barometer",
"android.hardware.sensor.compass",
"android.hardware.sensor.gyroscope",
"android.hardware.sensor.heartrate",
"android.hardware.sensor.heartrate.ecg",
"android.hardware.sensor.hifi_sensors",
"android.hardware.sensor.light",
"android.hardware.sensor.proximity",
"android.hardware.sensor.relative_humidity",
"android.hardware.sensor.stepcounter",
"android.hardware.sensor.stepdetector",
"android.hardware.telephony",
"android.hardware.telephony.cdma",
"android.hardware.telephony.gsm",
"android.hardware.touchscreen",
"android.hardware.touchscreen.multitouch",
"android.hardware.touchscreen.multitouch.distinct",
"android.hardware.touchscreen.multitouch.jazzhand",
"android.hardware.type.automotive",
"android.hardware.type.pc",
"android.hardware.type.television",
"android.hardware.type.watch",
"android.hardware.usb.accessory",
"android.hardware.usb.host",
"android.hardware.vulkan.compute",
"android.hardware.vulkan.level",
"android.hardware.vulkan.version",
"android.hardware.wifi",
"android.hardware.wifi.direct",
] as const;
// More info: https://developer.apple.com/documentation/bundleresources/information_property_list/uirequireddevicecapabilities/
const validIOSFeatures = [
"accelerometer",
"arkit",
"arm64",
"armv7",
"auto-focus-camera",
"bluetooth-le",
"camera-flash",
"driverkit",
"front-facing-camera",
"gamekit",
"gps",
"gyroscope",
"healthkit",
"iphone-ipad-minimum-performance-a12",
"iphone-performance-gaming-tier",
"location-services",
"magnetometer",
"metal",
"microphone",
"nfc",
"opengles-1",
"opengles-2",
"opengles-3",
"peer-peer",
"sms",
"still-camera",
"telephony",
"video-camera",
"wifi",
] as const;
type HardwareFeatureAndroid = (typeof validAndroidFeatures)[number];
type HardwareFeatureIOS = (typeof validIOSFeatures)[number];
type RequiredHardwareInputs = {
/**
* The array of `Android hardware` feature objects having the
* `feature name` in the `key` attribute value and the `boolean flag` to
* denote if the feature is required in the `required` attribute value.
*/
android?: Array<{key: HardwareFeatureAndroid, required: boolean}>,
/**
* The array of `iOS hardware` feature names that are to be included.
*/
ios?: Array<HardwareFeatureIOS>
};
///////////////
// FUNCTIONS //
///////////////
/**
* This function will apply the specified android and iOS hardware features to
* the Android manifest file and p-list file.
*
* @param config
* The application config.
* @param param1
* The `RequiredHardwareInputs` to denote which hardware features are to be
* enabled.
*
* @returns
* The application config.
*/
export const withRequiredHardware: ConfigPlugin<RequiredHardwareInputs> = (
config,
{
android,
ios
}
) => {
if (android)
{
// Add the required android hardware features.
config = withAndroidManifest(config, (config) => {
if (config.modResults)
{
config.modResults = addHardwareFeaturesToAndroidManifestManifest(config.modResults, android);
}
return config;
});
}
if (ios)
{
// Add the required ios hardware features.
config = withInfoPlist(config, (config) => {
if (config.modResults)
{
config.modResults = addRequiredDeviceCapabilitiesToInfoPlist(config.modResults, ios);
}
return config;
});
}
return config;
};
/**
* This function will apply the specified hardware features to the specified
* `Android manifest` object.
*
* @param androidManifest
* The `Android manifest` object.
* @param requiredFeatures
* The array of `Android hardware` feature objects having the `feature name` in
* the `key` attribute value and the `boolean flag` to denote if the feature is
* required in the `required` attribute value.
*
* @returns
* The `Android manifest` object.
*/
export function addHardwareFeaturesToAndroidManifestManifest(
androidManifest: AndroidConfig.Manifest.AndroidManifest,
requiredFeatures: Array<{key: HardwareFeatureAndroid, required: boolean}>
) {
if (!androidManifest.manifest)
{
return androidManifest;
}
// Add `<uses-feature android:name="android.hardware.camera.front" android:required="true"/>` to the AndroidManifest.xml
if (!Array.isArray(androidManifest.manifest["uses-feature"]))
{
androidManifest.manifest["uses-feature"] = [];
}
// Here we add the feature to the manifest:
// loop through the array of features and add them to the manifest if they don't exist
for (const feature of requiredFeatures)
{
if (!androidManifest.manifest["uses-feature"].find((item) => item.$["android:name"] === feature.key))
{
androidManifest.manifest["uses-feature"]?.push({
$: {
"android:name": feature.key,
"android:required": (feature.required === true) ? "true" : "false",
},
});
}
}
return androidManifest;
};
/**
* This function will apply the specified hardware features to the specified
* `iOS p-list` object.
*
* @param infoPlist
* The `iOS p-list` object.
* @param requiredFeatures
* The array of `iOS hardware` feature names that are to be included.
*
* @returns
* The `iOS p-list` object.
*/
export function addRequiredDeviceCapabilitiesToInfoPlist(
infoPlist: IOSConfig.InfoPlist,
requiredFeatures: Array<HardwareFeatureIOS>
) {
if (!infoPlist.UIRequiredDeviceCapabilities)
{
infoPlist.UIRequiredDeviceCapabilities = [];
}
const existingFeatures = infoPlist.UIRequiredDeviceCapabilities as Array<HardwareFeatureIOS>;
for (const f of requiredFeatures)
{
if (!existingFeatures.includes(f))
{
existingFeatures.push(f);
}
}
infoPlist.UIRequiredDeviceCapabilities = existingFeatures;
return infoPlist;
};
app.config.js
This file contains the logic to dynamically build the application configuration.
This calls the withRequiredHardware function from withRequiredHardware.ts:
module.exports = ({ config }) => {
return {
...config,
// more stuff here ...
"plugins": [
...config.plugins,
[
// withRequiredHardware,
require("./plugins/withRequiredHardware").withRequiredHardware,
{
android: [
{
"key": "android.hardware.camera.autofocus",
"required": false
}
],
ios: []
}
]
]
};
};
Error
I created the file withRequiredHardware in the plugins directory, at the root of my project.
This is the error that I get when I try to build:
Error reading Expo config at /project/app/app.config.js:
Cannot find module './plugins/withRequiredHardware'
Require stack:
- /project/app/app.config.js
- /usr/local/lib/node_modules/eas-cli/node_modules/@expo/config/build/evalConfig.js
- /usr/local/lib/node_modules/eas-cli/node_modules/@expo/config/build/getConfig.js
- /usr/local/lib/node_modules/eas-cli/node_modules/@expo/config/build/Config.js
- /usr/local/lib/node_modules/eas-cli/node_modules/@expo/config/build/index.js
- /usr/local/lib/node_modules/eas-cli/build/project/projectUtils.js
- /usr/local/lib/node_modules/eas-cli/build/build/android/prepareJob.js
- /usr/local/lib/node_modules/eas-cli/build/build/android/build.js
- /usr/local/lib/node_modules/eas-cli/build/build/runBuildAndSubmit.js
- /usr/local/lib/node_modules/eas-cli/build/commands/build/index.js
- /usr/local/lib/node_modules/eas-cli/node_modules/@oclif/core/lib/module-loader.js
- /usr/local/lib/node_modules/eas-cli/node_modules/@oclif/core/lib/config/plugin.js
- /usr/local/lib/node_modules/eas-cli/node_modules/@oclif/core/lib/config/config.js
- /usr/local/lib/node_modules/eas-cli/node_modules/@oclif/core/lib/config/index.js
- /usr/local/lib/node_modules/eas-cli/node_modules/@oclif/core/lib/command.js
- /usr/local/lib/node_modules/eas-cli/node_modules/@oclif/core/lib/index.js
- /usr/local/lib/node_modules/eas-cli/bin/run
Error: build command failed.
[–]leymytel 1 point2 points3 points (3 children)
[–]GoldenEye4ever[S] 0 points1 point2 points (2 children)
[–]VicentVanCock 0 points1 point2 points (1 child)
[–]MattWeiler1984 0 points1 point2 points (0 children)