Guide 4 of 4 · Mobile app

Mobile app build guide: Android and iPhone

Build the app once with Expo (React Native) and get both an Android and an iPhone app. The app talks only to your own backend. A user signs in to your backend, and connects BookMyShow by logging in once inside the app. No token, any phone, any account.

  • 11 October 2026
  • 16 min read
  • For whoever builds the app after version 2 works on the web
B4–5
On the phone, the alert screen shows these seats and opens BookMyShow in one tap.

01Choosing how to build it

Use Expo with TypeScript, and build in the cloud with EAS Build. One code base gives you an Android APK and an iPhone build, Expo's push service handles both Google's and Apple's push systems for you, and the code that talks to the backend is shared with the version 2 website.

Option Language One code base for both? Push notifications Fit for this project Verdict
Expo (React Native) TypeScript Yes Expo's service wraps both Google and Apple; one kind of token Shares TypeScript, the API client and the React way of thinking with the website; cloud builds mean iPhone builds work before Xcode is even set up Recommended
Flutter Dart Yes Firebase plugin; you wire Apple push yourself Great screens, but a second language and nothing shared with the website Good second choice
Native (Kotlin + Swift) Two No, two apps Platform tools directly Best polish; double the work for a small app Not for the first version
Progressive Web App TypeScript Yes, it is the website Browser push works on Android, and on iPhone (16.4 or newer) once added to the home screen Zero extra code; iPhone push is less reliable in the background Ship this first as a stepping stone

What "works on any mobile and any account" means here. The app never holds a BookMyShow token in its code. A user signs in to your backend (email link or Google), and the backend polls BookMyShow with no login. Seat checking, which needs a BookMyShow login, is done once per user inside the app through a web view of the normal BookMyShow login page. The resulting cookies go to the backend, exactly as the version 2 plan, section 6 describes.

Minimum versions. Expo SDK 52 or newer, Android 8.0 and iOS 15.1 as the oldest supported. Together they cover more than 95 % of phones in use in India in 2026.

02What to install on your computer

You can build the app from macOS, Linux or Windows. Android works fully on all three. For iPhone, the simulator and building on your own computer need a Mac, but Expo's cloud builds make iPhone apps from any computer, so a Windows or Linux user can still ship to iPhones and test on a real one.

Tool Why Needed on Check
Node.js 20 or newer Runs Expo and the build tools All node --version
Expo CLI Creates and runs the project All (no install; use npx expo inside the project) npx expo --version
EAS CLI Cloud builds, signing, store upload All eas whoami
Expo Go on your phone Run the app while developing, without building Your phone Scan the QR code from npx expo start
Android Studio Android tools and the virtual phone (emulator) All adb --version, emulator -list-avds
Java 17 Needed by Android builds All java -version
Watchman Notices file changes quickly while developing macOS (optional on Linux, not needed on Windows) watchman --version
Xcode (about 12 GB) iPhone simulator, local iPhone builds macOS only xcodebuild -version
CocoaPods iPhone packages for local builds macOS only pod --version

Install them:

brew install watchman cocoapods
brew install --cask android-studio zulu@17
npm install -g eas-cli && eas login

# Xcode: install it from the Mac App Store, open it once and accept the licence, then:
sudo xcode-select -s /Applications/Xcode.app

Then open Android Studio once. In its SDK Manager, tick Android 14 (API 34), Android SDK Build-Tools, Platform-Tools and Emulator. In Device Manager, create one Pixel virtual phone.

Finally, tell your terminal where the Android tools are:

echo 'export ANDROID_HOME=$HOME/Library/Android/sdk' >> ~/.zshrc
echo 'export PATH=$PATH:$ANDROID_HOME/platform-tools:$ANDROID_HOME/emulator' >> ~/.zshrc
source ~/.zshrc
adb --version

Accounts.

Account Cost Needed for
Expo Free (30 cloud builds a month) Cloud builds, push service
Google Play Console 25 USD once Publishing in the Play Store; not needed for an APK you install yourself
Apple Developer Program 99 USD a year TestFlight and the App Store; also needed to run on a real iPhone for more than 7 days
Firebase project Free Google's push keys for Android (uploaded to Expo once)

Follow Expo's own environment setup page for the exact current versions of Android Studio and Xcode. It has a separate tab for each system and is updated with every release.

03Project setup, step by step

About an hour from an empty folder to the app running on your own phone. The backend from the version 2 plan must be reachable over HTTPS before the sign-in screen works.

  1. Open a terminal in the folder where you keep projects, then create the project (TypeScript, with tabs). This is the same on every system:

    bash
    npx create-expo-app@latest bms-alerts --template tabs
    cd bms-alerts
  2. Install the packages the app needs. Always use npx expo install so the versions match your Expo release:

    bash
    npx expo install expo-notifications expo-device expo-constants expo-secure-store expo-linking expo-web-browser react-native-webview @react-native-cookies/cookies
    npm install @tanstack/react-query zod
  3. Set the app's names in app.json:

    json
    {
      "expo": {
        "name": "BMS Alerts", "slug": "bms-alerts", "scheme": "bmsalerts",
        "ios": { "bundleIdentifier": "com.sriram.bmsalerts", "supportsTablet": false },
        "android": { "package": "com.sriram.bmsalerts", "googleServicesFile": "./google-services.json" },
        "plugins": ["expo-router", "expo-secure-store", ["expo-notifications", { "icon": "./assets/notification-icon.png" }]]
      }
    }
  4. Point the app at the backend with a line in .env: EXPO_PUBLIC_API_URL=https://bms.yourdomain.in. Generate the typed API client from the backend's description: npx openapi-typescript https://bms.yourdomain.in/openapi.json -o lib/api.d.ts.

  5. Create the screens. Expo Router turns file names into screens:

    text
    app/
      sign-in.tsx                 email link or Google
      connect-bms.tsx             BookMyShow login in a web view (section 5)
      (tabs)/index.tsx            dashboard of watches
      (tabs)/theatres.tsx         theatre list with search
      (tabs)/settings.tsx         push on/off, email, disconnect
      theatre/[code].tsx          movies and shows at one theatre
      watch/new.tsx               the watch form
      alert/[id].tsx              one alert, with an Open in BookMyShow button
    lib/api.ts  lib/auth.ts  lib/push.ts
  6. Run it in Expo Go first: npx expo start, scan the QR code with your phone. The theatre and movie screens work here. Push notifications and reading web view cookies do not, because Expo Go cannot include those native pieces.

  7. Make a "development build" for your phone and use that from now on. These commands are the same on every system:

    bash
    eas login
    eas init                      # links the project to your Expo account
    eas build:configure           # writes eas.json with development, preview, production
    eas build --profile development --platform android   # about 10 min in the cloud, gives an APK link
    npx expo start --dev-client

    Install the APK on your Android phone from the link, open it, and it connects to your computer over the same Wi-Fi. The iPhone version of this step needs the Apple Developer account (section 8).

Guides: Create a project, Expo Router, Development builds.

04Screens and flow

or skipSign inemail link or Googleonce per phoneConnect BookMyShowweb view, phone + OTPoptional, for seat checksDashboard tabyour watches and statuspause, delete, historyadd a watchTheatres tabcity, search,favouritesTheatre screendates, movies, shows,price and seat statusNew watchshows, 2 to 6 seats,price range, channelssavedPush notificationarrives with theapp closedAlert screenseats, show, time foundOpen in BookMyShowBookMyShow seat pageyou pick and payfor the seatstapopen
Two one-time screens, three tabs, and a push notification as the second way into the app.

Two one-time screens (sign in, connect BookMyShow) lead to a three-tab app: Dashboard, Theatres, Settings. The everyday path is Theatres, then a theatre, then a new watch, then back to the dashboard. A push notification is the second way in: tapping it opens the alert screen, whose main button opens the BookMyShow seat page. Each box is one file under app/ from section 3.

05Talking to the backend and signing in

The app talks only to your FastAPI backend over HTTPS. It signs in to your backend, not to BookMyShow. The BookMyShow connection is a one-time login in a web view whose cookies are handed to the backend.

Signing in to your backend. Two ways, both handled by fastapi-users on the server:

  • Email link: the user types an email, the backend sends a link like bmsalerts://auth?token=.... Opening it launches the app (expo-linking), which swaps the link token for a sign-in token pair.
  • Google: expo-web-browser opens the backend's /auth/google/authorize page; when done it comes back to the same bmsalerts://auth link.

Keep the long-lived refresh token in expo-secure-store (the phone's locked storage), keep the short-lived access token in memory, and add the Authorization: Bearer header in one fetch wrapper in lib/api.ts. On a 401, refresh once, then sign out.

Connecting BookMyShow (any account). The screen connect-bms.tsx:

tsx
import { WebView } from 'react-native-webview';
import CookieManager from '@react-native-cookies/cookies';

<WebView
  source={{ uri: 'https://in.bookmyshow.com/explore/home/hyderabad' }}
  sharedCookiesEnabled
  onNavigationStateChange={async () => {
    const jar = await CookieManager.get('https://in.bookmyshow.com', true);
    if (jar.ud?.value) {               // this cookie exists only after login
      await api.post('/api/bms/session', { cookies: { ud: jar.ud.value, bmsId: jar.bmsId?.value } });
      router.replace('/(tabs)');
    }
  }}
/>

The user sees the normal BookMyShow login (phone and OTP) inside the app, so it works for whoever is holding the phone, and nothing on that page is automated. The backend stores the cookies encrypted and uses them only to read seat maps for that user's watches. Add a Disconnect button on the settings tab that calls DELETE /api/bms/session and clears the web view's cookies with CookieManager.clearAll().

Keeping the app and the backend in step. The app and the website use the same generated client from openapi.json; regenerate it whenever the backend changes (npm run gen-api). With TanStack Query, keep theatre showtimes for 5 minutes and the theatre list for 24 hours, and refresh the dashboard whenever the app comes to the front.

Guides: expo-secure-store, Linking into your app, react-native-webview guide, @react-native-cookies/cookies.

06Push notifications

Use Expo's push service. The app registers one Expo push token with your backend, the backend sends to Expo, and Expo delivers through Google (FCM) on Android and Apple (APNs) on iPhone. The keys are set up once.

One-time key setup.

  1. Android: create a Firebase project, add an Android app with package com.sriram.bmsalerts, download google-services.json into the project root (add it to .gitignore). Then in Firebase, Project settings, Service accounts, generate a private key and upload it with eas credentials (Android, "Google Service Account Key for FCM V1"). Guide: Expo: using FCM.
  2. iPhone: with the Apple Developer account linked, eas build --platform ios offers to create the Apple push key for you; say yes. Nothing to download.
  3. Backend: pip install exponent_server_sdk. No keys needed for Expo's push service at this app's volume.

In the app, lib/push.ts.

ts
import * as Notifications from 'expo-notifications';
import * as Device from 'expo-device';
import Constants from 'expo-constants';
import { Platform } from 'react-native';

Notifications.setNotificationHandler({
  handleNotification: async () => ({ shouldShowBanner: true, shouldShowList: true, shouldPlaySound: true, shouldSetBadge: false }),
});

export async function registerForPush(api) {
  if (!Device.isDevice) return;                       // simulators cannot receive push
  if (Platform.OS === 'android') {
    await Notifications.setNotificationChannelAsync('alerts', {
      name: 'Seat alerts', importance: Notifications.AndroidImportance.MAX, sound: 'default',
    });
  }
  const { status } = await Notifications.requestPermissionsAsync();
  if (status !== 'granted') return;
  const projectId = Constants.expoConfig?.extra?.eas?.projectId;
  const token = (await Notifications.getExpoPushTokenAsync({ projectId })).data; // ExponentPushToken[...]
  await api.post('/api/push/register', { kind: 'expo', token });
}

// tapping a notification opens the alert screen
Notifications.addNotificationResponseReceivedListener(({ notification }) => {
  const id = notification.request.content.data?.alertId;
  if (id) router.push(`/alert/${id}`);
});

On the backend (the worker's notify.py): build one message per push_device of the user with a title, a body, data={"alertId": id, "url": seat_layout_url}, channelId="alerts" and priority="high". Send with PushClient().publish_multiple. A minute later read the receipts and delete tokens that come back as DeviceNotRegistered. Guide: Sending notifications with Expo's push API.

Testing. Paste the token printed by the app into Expo's push tool and send a test. On Android, test with the app closed. On iPhone, a real phone with a development build is needed. The phone's operating system delivers the message, so the app does not have to be running.

Setup guide: Expo push notifications setup.

07Building and testing on Android

Android is the easier of the two: no paid account, it works the same from macOS, Linux and Windows, cloud builds produce an APK you can install on any phone, and a virtual Pixel on your computer covers daily work.

Daily development.

  1. Start a virtual phone from Android Studio (Device Manager, play button) or emulator -avd Pixel_8_API_34, or plug in a phone with USB debugging on (adb devices lists it).
  2. npx expo start --dev-client, press a to open the development build on the virtual or real phone. Changes show up instantly.
  3. Push notifications only work on a real phone with Google Play services, or a virtual phone whose image says "Google Play".

Build profiles in eas.json.

json
{
  "build": {
    "development": { "developmentClient": true, "distribution": "internal", "android": { "buildType": "apk" } },
    "preview":     { "distribution": "internal", "android": { "buildType": "apk" } },
    "production":  { "android": { "buildType": "app-bundle" }, "autoIncrement": true }
  }
}

Builds.

bash
eas build --profile preview --platform android      # APK for friends and your own phone
eas build --profile production --platform android   # AAB for the Play Store
eas build --profile preview --platform android --local   # on your own computer; macOS or Linux only, needs Java 17 and the Android tools

The first cloud build creates and keeps a signing key for you. Keep using EAS for signing so every build is signed the same way. A preview APK installs on any Android 8 or newer phone from the build link (allow "install unknown apps" for the browser once).

Checklist on a real phone.

  • Sign in, connect BookMyShow in the web view, confirm the backend shows the session as ok.
  • Create a watch for a show today; force a test alert from the backend (POST /api/dev/alerts/test) and confirm the push arrives with the app closed and the screen locked.
  • Tap the notification: the alert screen opens, and "Open in BookMyShow" opens the BookMyShow app or site on the seat page.
  • Battery: Android may kill background apps, but push delivery does not depend on the app running. On Xiaomi, Oppo and Samsung phones, still set the app to "Unrestricted" under Settings, Battery.

Guides: Build APKs for emulators and devices, Android emulator setup, Local builds.

08Building and testing on iPhone

iPhone needs the Apple Developer Program (99 USD a year) for anything beyond the simulator. With it, Expo builds and signs in the cloud, and TestFlight puts the app on your iPhone and on up to 10,000 testers' phones.

Windows

On Windows there is no iPhone simulator. Skip to "With the account" below: Expo builds the iPhone app in the cloud, and you install it on a real iPhone from a QR code. Everything in this section after the simulator works the same from Windows.

Linux

On Linux there is no iPhone simulator. Skip to "With the account" below: Expo builds the iPhone app in the cloud, and you install it on a real iPhone from a QR code. Everything in this section after the simulator works the same from Linux.

Without a paid account (Mac only). Install the full Xcode, then npx expo run:ios builds and opens the app in the iPhone simulator. Everything except push notifications and the camera works there. The web view BookMyShow login works in the simulator too, so the connect flow can be tested without a phone.

With the account.

  1. Enroll at developer.apple.com with your Apple ID (approval takes a day or two).
  2. eas build --profile development --platform ios: Expo asks to log in with your Apple ID, registers the app, creates the certificates and the push key, and keeps them. Register your iPhone when asked (eas device:create sends a link to open on the phone).
  3. Install the development build on the iPhone from the build page (a QR code), then npx expo start --dev-client as on Android.
  4. For a build to share: eas build --profile preview --platform ios (up to 100 registered phones) or go straight to TestFlight with the production profile (section 9).

Local iPhone builds (eas build --platform ios --local) need a Mac with Xcode, CocoaPods and the same certificates. Use them only when the cloud queue is slow.

iPhone-specific things to test.

  • Notification permission is asked once. If refused, send the user to Settings with Linking.openSettings().
  • Push must be tested on a real iPhone; the simulator cannot receive it.
  • A normal web link that opens the app (https://bms.yourdomain.in/alert/123) needs an apple-app-site-association file on your backend's domain and associatedDomains in app.json. The custom link bmsalerts:// works without any of that.
  • Keep supportsTablet: false to avoid iPad screenshots at review time.

Guides: Xcode and the simulator, iPhone signing with EAS, Internal distribution.

09Getting it onto phones and into the stores

Start with an APK and TestFlight for yourself and friends. Publish in the stores only if strangers should use it, because both store reviews will ask what the app does with BookMyShow data.

Way Who can install What it takes Command
APK link (Android) Anyone with the link, any Android 8 or newer phone Nothing beyond Expo eas build --profile preview --platform android
Internal distribution (iPhone) Up to 100 registered iPhones Apple Developer account, each phone registered eas device:create, then eas build --profile preview --platform ios
TestFlight Up to 10,000 testers by email or a public link Apple Developer account, an App Store Connect record, a short review of the first build eas build --profile production --platform ios then eas submit --platform ios
Google Play internal testing Up to 100 testers by email Play Console account, an app record, an AAB upload eas build --profile production --platform android then eas submit --platform android
App Store and Play Store Everyone Store listings, screenshots, a privacy policy page, data-safety form, review Promote the tested build in each console

Uploading with one command. Add a submit.production block to eas.json with ios.appleId, ios.ascAppId and android.serviceAccountKeyPath (a Play Console service-account JSON file). Then eas submit uploads the latest build without opening Xcode or the Play Console. Guide: EAS Submit.

Updates without a new store release. eas update sends JavaScript-only changes (screens, text, fixes) straight to installed apps at next launch. Changes to native pieces (a new package, a new permission) still need a new build. Guide: EAS Update.

What store review will ask for.

  • A privacy policy page on your domain saying that BookMyShow login cookies are stored encrypted, used only to read seat availability for the user's own watches, and can be deleted in the app.
  • Apple's data questionnaire and Google's data-safety form: email, push token and the BookMyShow session count as account data linked to the user.
  • Apple requires "Sign in with Apple" if you offer Google sign-in (guideline 4.8). The email link flow alone avoids that.
  • Trademark: do not use BookMyShow's name or logo in the app name or icon. "Seat Alerts" with a plain icon is safe.

Version numbers. Set autoIncrement: true in the production profile so each build gets a new build number, and raise version in app.json for releases users will notice.

Expo's own documentation covers every step above. The rows are in the order a first build meets them.

Topic Link
Set up the environment (Xcode, Android Studio, exact versions) docs.expo.dev/get-started/set-up-your-environment
Expo tutorial: build a first app (two hours, worth doing once) docs.expo.dev/tutorial/introduction
Expo Router: screens from file names docs.expo.dev/router/introduction
Development builds docs.expo.dev/develop/development-builds/introduction
EAS Build and the eas.json file docs.expo.dev/build/introduction, eas.json reference
Signing keys and certificates docs.expo.dev/app-signing/managed-credentials
Push notifications: setup, Google keys, sending from a server setup, FCM, sending
Links that open the app docs.expo.dev/linking/into-your-app
Locked storage on the phone docs.expo.dev/versions/latest/sdk/securestore
Web view and cookies react-native-webview guide, @react-native-cookies/cookies
Loading data TanStack Query with React Native
Uploading to the stores EAS Submit, App Store review guidelines, Play Console help
Updates without a store release EAS Update
React Native basics (if React is new) reactnative.dev/docs/getting-started
Flutter, in case you prefer it docs.flutter.dev/get-started, Firebase Messaging for Flutter
Progressive Web App stepping stone web.dev/learn/pwa, Web Push on iPhone

Two community write-ups worth one read each: Capgo: Expo push notifications on the common delivery failures on real phones, and BrowserStack: Playwright storage state on the backend side of the login the app hands over.