Created
August 5, 2025 19:39
-
-
Save letam/536ba9bd591afc6938d7bd6e1ff27aec to your computer and use it in GitHub Desktop.
Transform Expo app's config file from app.json to app.config.ts for bluetooth connectivity
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Reference: https://docs.expo.dev/config-plugins/plugins/ | |
*/ | |
import 'tsx/cjs' | |
import type { ExpoConfig } from 'expo/config' | |
module.exports = ({ config }: { config: ExpoConfig }) => { | |
return { | |
name: 'mobile-app', | |
slug: 'mobile-app', | |
version: '1.0.0', | |
orientation: 'portrait', | |
icon: './assets/images/icon.png', | |
scheme: 'mobileapp', | |
userInterfaceStyle: 'automatic', | |
newArchEnabled: true, | |
ios: { | |
supportsTablet: true, | |
bundleIdentifier: 'com.anonymous.mobile-app', | |
infoPlist: {}, | |
}, | |
android: { | |
adaptiveIcon: { | |
foregroundImage: './assets/images/adaptive-icon.png', | |
backgroundColor: '#ffffff', | |
}, | |
edgeToEdgeEnabled: true, | |
package: 'com.anonymous.mobileapp', | |
permissions: [], | |
}, | |
web: { | |
bundler: 'metro', | |
output: 'static', | |
favicon: './assets/images/favicon.png', | |
}, | |
plugins: [ | |
'expo-router', | |
[ | |
'expo-splash-screen', | |
{ | |
image: './assets/images/splash-icon.png', | |
imageWidth: 200, | |
resizeMode: 'contain', | |
backgroundColor: '#ffffff', | |
}, | |
], | |
'expo-sqlite', | |
'./withBluetoothPlugin.ts', | |
], | |
experiments: { | |
typedRoutes: true, | |
}, | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"expo": { | |
"name": "mobile-app", | |
"slug": "mobile-app", | |
"version": "1.0.0", | |
"orientation": "portrait", | |
"icon": "./assets/images/icon.png", | |
"scheme": "mobileapp", | |
"userInterfaceStyle": "automatic", | |
"newArchEnabled": true, | |
"ios": { | |
"supportsTablet": true, | |
"bundleIdentifier": "com.anonymous.mobile-app", | |
"infoPlist": { | |
"NSBluetoothAlwaysUsageDescription": "This app needs access to Bluetooth to connect to devices." | |
} | |
}, | |
"android": { | |
"adaptiveIcon": { | |
"foregroundImage": "./assets/images/adaptive-icon.png", | |
"backgroundColor": "#ffffff" | |
}, | |
"edgeToEdgeEnabled": true, | |
"package": "com.anonymous.mobile-app", | |
"permissions": [ | |
"android.permission.BLUETOOTH_CONNECT", | |
"android.permission.BLUETOOTH_SCAN" | |
] | |
}, | |
"web": { | |
"bundler": "metro", | |
"output": "static", | |
"favicon": "./assets/images/favicon.png" | |
}, | |
"plugins": [ | |
"expo-router", | |
[ | |
"expo-splash-screen", | |
{ | |
"image": "./assets/images/splash-icon.png", | |
"imageWidth": 200, | |
"resizeMode": "contain", | |
"backgroundColor": "#ffffff" | |
} | |
], | |
"expo-sqlite" | |
], | |
"experiments": { | |
"typedRoutes": true | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { | |
type ConfigPlugin, | |
withAndroidManifest, | |
withInfoPlist, | |
} from 'expo/config-plugins' | |
const withAndroidPlugin: ConfigPlugin = (config) => { | |
return withAndroidManifest(config, (config) => { | |
const mainApplication = config?.modResults?.manifest | |
/* | |
* Bluetooth permissions and features | |
* Reference: https://developer.android.com/develop/connectivity/bluetooth/bt-permissions | |
* Reference: https://innoveit.github.io/react-native-ble-manager/install/ | |
*/ | |
if (mainApplication) { | |
// Permissions | |
// Ensure uses-permission array exists | |
if (!mainApplication['uses-permission']) { | |
mainApplication['uses-permission'] = [] | |
} | |
// Request legacy Bluetooth permissions on older devices. | |
mainApplication['uses-permission'].push({ | |
$: { | |
'android:name': 'android.permission.BLUETOOTH', | |
// @ts-ignore | |
'android:maxSdkVersion': '30', | |
}, | |
}) | |
mainApplication['uses-permission'].push({ | |
$: { | |
'android:name': 'android.permission.BLUETOOTH_ADMIN', | |
// @ts-ignore | |
'android:maxSdkVersion': '30', | |
}, | |
}) | |
// Needed only if your app looks for Bluetooth device. | |
mainApplication['uses-permission'].push({ | |
$: { | |
'android:name': 'android.permission.BLUETOOTH_SCAN', | |
// strongly assert that the app never derives physical location from Bluetooth scan results | |
// @ts-ignore | |
'android:usesPermissionFlags': 'neverForLocation', | |
}, | |
}) | |
// Needed only if your app communicates with already-paired Bluetooth devices. | |
mainApplication['uses-permission'].push({ | |
$: { | |
'android:name': 'android.permission.BLUETOOTH_CONNECT', | |
}, | |
}) | |
// Needed only if your app makes the device discoverable to Bluetooth devices. | |
// mainApplication['uses-permission'].push({ | |
// $: { | |
// 'android:name': 'android.permission.BLUETOOTH_ADVERTISE', | |
// }, | |
// }) | |
// Needed only if your app uses Bluetooth scan results to derive physical location. | |
// mainApplication['uses-permission'].push({ | |
// $: { | |
// 'android:name': 'android.permission.ACCESS_FINE_LOCATION', | |
// }, | |
// }) | |
// Features | |
// Ensure uses-feature array exists | |
if (!mainApplication['uses-feature']) { | |
mainApplication['uses-feature'] = [] | |
} | |
// Companion device pairing | |
// Reference: https://developer.android.com/develop/connectivity/bluetooth/companion-device-pairing | |
// mainApplication['uses-feature'].push({ | |
// $: { | |
// 'android:name': 'android.software.companion_device_setup', | |
// 'android:required': 'true', | |
// }, | |
// }) | |
} | |
return config | |
}) | |
} | |
const withIosPlugin: ConfigPlugin = (config) => { | |
return withInfoPlist(config, (config) => { | |
// For access to Bluetooth. | |
config.modResults.NSBluetoothAlwaysUsageDescription = | |
'This app needs access to Bluetooth to connect to a device.' | |
return config | |
}) | |
} | |
const withPlugin: ConfigPlugin = (config) => { | |
// Apply Android modifications first | |
config = withAndroidPlugin(config) | |
// Then apply iOS modifications and return | |
return withIosPlugin(config) | |
} | |
export default withPlugin |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment