Skip to content

Instantly share code, notes, and snippets.

@letam
Created August 5, 2025 19:39
Show Gist options
  • Save letam/536ba9bd591afc6938d7bd6e1ff27aec to your computer and use it in GitHub Desktop.
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
/*
* 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,
},
}
}
{
"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
}
}
}
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