EthicsComputerCourses
Join training +91 90532 10052

iOS vs Android & Platform APIs

React Native · Lesson 21 of 30 Intermediate 7 min read Updated

By the end of this lesson you will be able to write one codebase that still feels at home on both iPhone and Android. You will detect the platform, pick different values or whole files per platform, handle the Android back button, and open the dialler or maps from your app.

React Native shares most code between platforms, often well over nine tenths of it. But iPhone and Android users have different habits, like the difference between driving in India and driving in the USA. The road rules are similar, but the steering wheel is on the other side.

The differences that matter most

AreaiOSAndroid
Going backSwipe from the left edge, back button in the headerSystem back gesture or button
Default fontSan FranciscoRoboto
Touch feedbackFading or dimmingRipple effect
Design guideApple Human Interface GuidelinesGoogle Material Design
Permission dialogsUsually asked once; then only SettingsCan often ask again; "Don't ask again" after denials
Toast messagesNo built-in toastToastAndroid
Testing on WindowsExpo Go on an iPhone, or cloud buildsEmulator and real phones

Rule of thumb: keep your app's logic and structure the same, and adapt small details (feedback, icons, gestures) so each platform feels natural.

Step 1: Platform.OS and Platform.select

Platform.OS is "ios", "android" or "web". Platform.select picks a value from an object based on the current platform, with default as a fallback.

React JSX
import { Platform, StyleSheet, Text } from "react-native";

export function Title({ children }) {
  return <Text style={styles.title}>{children}</Text>;
}

const styles = StyleSheet.create({
  title: {
    fontSize: 22,
    fontWeight: Platform.OS === "ios" ? "600" : "700",
    ...Platform.select({
      ios: { letterSpacing: 0.3 },
      android: { fontFamily: "sans-serif-medium" },
      default: {},
    }),
  },
});

What you should see: on iPhone the title is semi-bold with slightly wider letters. On Android it uses the system's medium sans-serif font. Same component, different finishing.

Platform.Version gives the OS version: a number on Android (the API level, such as 35) and a string on iOS (such as "18.2"). Use it only when a feature exists on newer versions.

Practise the logic: how select works

Platform.select is a small function you could write yourself. This browser version takes the platform as an argument, so you can switch between them and see what each phone would get:

React JSXTry it yourself
import { useState } from "react";
import { createRoot } from "react-dom/client";

function select(os, options) {
  if (os in options) return options[os];
  if ((os === "ios" || os === "android") && "native" in options) return options.native;
  return options.default;
}

const settings = {
  backLabel: { ios: "< Back", android: "←", default: "Back" },
  shareTitle: { android: "Share via", ios: "Share", default: "Copy link" },
  haptics: { native: true, default: false },
};

function Preview() {
  const [os, setOs] = useState("android");
  return (
    <div style={{ fontFamily: "sans-serif" }}>
      {["ios", "android", "web"].map((p) => (
        <button key={p} onClick={() => setOs(p)} style={{ fontWeight: p === os ? "bold" : "normal" }}>{p}</button>
      ))}
      <ul>
        {Object.entries(settings).map(([key, options]) => (
          <li key={key}>{key}: {String(select(os, options))}</li>
        ))}
      </ul>
    </div>
  );
}

console.log(select("ios", settings.shareTitle), "|", select("web", settings.haptics));
createRoot(document.getElementById("root")).render(<Preview />);
Output
Share | false

Click "ios", "android" and "web" and watch every value change. React Native's real Platform.select also supports the native key, which matches both phones but not the web.

Step 2: platform-specific files

When a component is very different on each platform, split it into files instead of filling it with if statements. Metro, the bundler, picks the right file by its extension:

Text
src/components/
  date-field.ios.jsx       used on iPhone
  date-field.android.jsx   used on Android
  date-field.web.jsx       used on the web (optional)
React JSX
// anywhere in the app: no extension in the import
import DateField from "@/components/date-field";

What happens: the iPhone build only contains the .ios.jsx code, and the Android build only the .android.jsx code. You can also use .native.jsx for "both phones" with a plain .jsx file for the web.

Step 3: the Android back button

Android users press back constantly. Expo Router and React Navigation already handle it for screens: back pops the stack. Sometimes you want to intercept it, for example to confirm before leaving an unsaved form. Use BackHandler:

React JSX
import { useEffect } from "react";
import { Alert, BackHandler } from "react-native";
import { router } from "expo-router";

export function useConfirmLeave(hasUnsavedChanges) {
  useEffect(() => {
    const sub = BackHandler.addEventListener("hardwareBackPress", () => {
      if (!hasUnsavedChanges) return false; // let the normal back action happen
      Alert.alert("Discard changes?", "Your expense is not saved yet.", [
        { text: "Stay", style: "cancel" },
        { text: "Discard", style: "destructive", onPress: () => router.back() },
      ]);
      return true; // we handled it; do not go back yet
    });
    return () => sub.remove();
  }, [hasUnsavedChanges]);
}

What you should see on Android: with unsaved changes, pressing back shows a dialog with "Stay" and "Discard". "Discard" leaves the screen. Without changes, back works normally. BackHandler does nothing on iOS, where the swipe-back gesture is handled by the stack (you can turn it off with the screen option gestureEnabled: false if needed).

Alert.alert itself works on both platforms and shows a native dialog. Android shows at most three buttons in it.

Step 4: open other apps with Linking

Linking.openURL hands a URL to the system, which opens the right app. This is how "Call shop", "Email us" and "Get directions" buttons work.

React JSX
import { Linking, Platform, Pressable, Text, View } from "react-native";

const shop = { phone: "+919876543210", lat: 26.9196, lng: 75.8235, label: "Johari Bazaar Sweets" };

export function ShopActions() {
  const mapsUrl = Platform.select({
    ios: `maps:0,0?q=${encodeURIComponent(shop.label)}@${shop.lat},${shop.lng}`,
    android: `geo:0,0?q=${shop.lat},${shop.lng}(${encodeURIComponent(shop.label)})`,
  });

  return (
    <View style={{ flexDirection: "row", gap: 12, padding: 16 }}>
      <Pressable onPress={() => Linking.openURL(`tel:${shop.phone}`)}><Text>Call</Text></Pressable>
      <Pressable onPress={() => Linking.openURL("mailto:orders@example.com?subject=Order")}><Text>Email</Text></Pressable>
      <Pressable onPress={() => Linking.openURL(mapsUrl)}><Text>Directions</Text></Pressable>
    </View>
  );
}

What you should see: "Call" opens the dialler with the number filled in (it does not call by itself). "Email" opens the mail app. "Directions" opens Apple Maps on iPhone or Google Maps on Android with a pin on the shop. On a simulator without these apps, openURL may fail, so wrap it in try/catch in production code.

Common mistakes

  • Copying iOS design onto Android (or the reverse). Back arrows, tabs and feedback should feel native to each platform.
  • Big if (Platform.OS === ...) blocks everywhere. Use Platform.select for values and platform files for whole components.
  • Fighting the back button. Only intercept it for a real reason, such as unsaved data, and always let the user leave.
  • Assuming Platform.Version is a number. It is a string on iOS.
  • Testing only on your own phone. Cheap Android phones and older iPhones reveal performance and layout problems.

Key points

  • Platform.OS tells you the platform; Platform.select picks values with ios, android, native and default keys.
  • .ios.jsx, .android.jsx, .native.jsx and .web.jsx files let Metro choose whole components per platform.
  • BackHandler intercepts the Android back button; return true to stop the default action.
  • Alert.alert shows native dialogs on both platforms.
  • Linking.openURL opens the dialler, email, maps and other apps.
  • Follow each platform's design conventions and test on both.

Exercise

Write a ShareRow component with two buttons. "Share on WhatsApp" opens whatsapp://send?text=MESSAGE (URL-encode the message "Join our cricket match in Kochi at 6 PM"). "Copy link" is shown only on Android and logs "Copied". On iOS, add extra horizontal padding to the row using Platform.select. If WhatsApp cannot be opened, show an Alert saying "WhatsApp is not installed".

Show solution
React JSX
import { Alert, Linking, Platform, Pressable, Text, View } from "react-native";

const message = "Join our cricket match in Kochi at 6 PM";

export function ShareRow() {
  async function shareWhatsApp() {
    try {
      await Linking.openURL(`whatsapp://send?text=${encodeURIComponent(message)}`);
    } catch {
      Alert.alert("WhatsApp is not installed");
    }
  }

  return (
    <View style={{ flexDirection: "row", gap: 12, paddingVertical: 12, paddingHorizontal: Platform.select({ ios: 24, default: 12 }) }}>
      <Pressable onPress={shareWhatsApp}><Text>Share on WhatsApp</Text></Pressable>
      {Platform.OS === "android" ? (
        <Pressable onPress={() => console.log("Copied")}><Text>Copy link</Text></Pressable>
      ) : null}
    </View>
  );
}

openURL rejects when no app can handle the URL, so the catch shows the alert. encodeURIComponent turns spaces into %20 so the message survives inside the URL.

Quick Quiz

1. What does Platform.select({ ios: 1, android: 2, default: 3 }) return on Android?

2. You have card.ios.jsx and card.android.jsx. How do you import the component?

3. Inside a hardwareBackPress listener, what does returning true mean?

4. Which call opens the phone dialler with a number filled in?

Learn React Native with a mentor

Free lessons get you started. Our Mobile App Development training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call