A guide to developing audio focused mobile applications for multiple platforms using React Native and Expo.
Expo
Install basic codebase
npx create-expo-app@latest --template tabs@49
Local development
npx expo run:ios
Install expo-dev-client
npx expo install expo-dev-client
Now trying to follow Setup Code Signing Certificates in Xcode for Development
Note: this requires a paid Developer account
--- more to add here once working
Updating to latest version of Expo
Version 50
Expo SDK 50 beta is now available
npm install expo@next
npx expo install --fix
This caused an error
⚠️ Something went wrong running `pod install` in the `ios` directory.
Command `pod install --repo-update` failed.
└─ Cause: This is often due to native package versions mismatching. Try deleting the 'ios/Pods' folder or the 'ios/Podfile.lock' file and running 'npx pod-install' to resolve.
So I nuked the iOS
directory and ran again
npx expo run ios
A new error
iOS Bundling failed 2392ms (index.js)
error: index.js: [BABEL]: expo-router/babel is deprecated in favor of babel-preset-expo in SDK 50. (While processing: /Users/chrismasters/git/expo-audio/node_modules/expo-router/babel.js)
This article was useful Expo Router v3 beta is now available
Remove the expo-router/babel preset in favor of babel-preset-expo and be sure to clear the Metro cache before restarting your dev server—this means running npx expo start --clear or npx expo export --clear
module.exports = function (api) {
api.cache(true);
return {
presets: ['babel-preset-expo']
};
};
Then there was an issue with the certificate which required following these instructions
- Open Keychain Access on your Mac.
- Locate your Apple Development certificate under the "login" keychain and the "Certificates" category.
- Double-click the certificate to open its details. Look for any warnings or status messages indicating why it's not trusted.
Final issue was connectin to the dev server.
Would not connect whilst on work VPN, it also required connecting to the dev server URL manually via the IP address ie
http://192.168.1.16:8081
After that the build was successful.
React Native Track Player
You can now use React Native Track Player with Expo.
Please be aware that while many people are using React Native Track Player with Expo successfully, the current maintainers of this project do not use Expo and their ability to resolve issues involving Expo is limited.
To get started, create a custom development client for your Expo app and then install React Native Track Player.
npx expo install react-native-track-player
Custom index.js file
Remove from package.json
"main": "expo-router/entry"
Then create an index.js
file in the root
(Taken from Expo Router Troubleshooting).
Alternatively, you can circumvent this issue by creating an index.js file in the root of your project with the following contents:
import { registerRootComponent } from 'expo';
import { ExpoRoot } from 'expo-router';
import TrackPlayer from 'react-native-track-player';
// Must be exported or Fast Refresh won't update the context
export function App() {
const ctx = require.context('./app');
return <ExpoRoot context={ctx} />;
}
TrackPlayer.registerPlaybackService(() => require('./service'));
registerRootComponent(App);
import { registerRootComponent } from 'expo';
import { ExpoRoot } from 'expo-router';
import TrackPlayer from 'react-native-track-player';
// Must be exported or Fast Refresh won't update the context
export function App() {
const ctx = require.context('./app');
return <ExpoRoot context={ctx} />;
}
TrackPlayer.registerPlaybackService(() => require('./service'));
registerRootComponent(App);
And service.js
file
import TrackPlayer, { Event } from 'react-native-track-player';
module.exports = async function () {
TrackPlayer.addEventListener(Event.RemotePlay, () => TrackPlayer.play());
TrackPlayer.addEventListener(Event.RemotePause, () => TrackPlayer.pause());
TrackPlayer.addEventListener(Event.RemoteStop, () => TrackPlayer.stop());
};
import TrackPlayer, { Event } from 'react-native-track-player';
module.exports = async function () {
TrackPlayer.addEventListener(Event.RemotePlay, () => TrackPlayer.play());
TrackPlayer.addEventListener(Event.RemotePause, () => TrackPlayer.pause());
TrackPlayer.addEventListener(Event.RemoteStop, () => TrackPlayer.stop());
};
So this doesn't seem to work, better to look at the demo application where the service is directly imported ?
PlayPauseButton.tsx SetupService.ts
import { QueueInitialTracksService, SetupService } from './services';
function useSetupPlayer() {
const [playerReady, setPlayerReady] = useState<boolean>(false);
useEffect(() => {
let unmounted = false;
(async () => {
await SetupService();
if (unmounted) return;
setPlayerReady(true);
const queue = await TrackPlayer.getQueue();
if (unmounted) return;
if (queue.length <= 0) {
await QueueInitialTracksService();
}
})();
return () => {
unmounted = true;
};
}, []);
return playerReady;
}
Following the guides