Deprecations and Platform Requirements

Minimum iOS and Android versions moving under you, store deadlines for new platform SDKs, and reading a release's breaking changes.

Three clocks move under a mobile app. The framework deprecates and then removes APIs. Apple and Google raise the minimum OS and SDK versions they support. The stores set dates after which they will not accept a build made with an older platform SDK. None of them care that your code currently works.

Platform floors

Expo SDK 57 supports iOS 16.4 and later and Android 7 and later, compiles against Android SDK 36, targets 36, and requires Xcode 26.4. A raised floor removes the oldest devices from your installable base, so check your analytics before you take the upgrade rather than after.

Store deadlines

From 31 August 2026 Google Play requires new apps and updates to target Android 16, API level 36, with an extension to 1 November 2026 available on request. From 28 April 2026 App Store Connect only accepts uploads built with the iOS 26 SDK, which means Xcode 26. These are submission gates. A published app keeps running; you simply cannot ship a fix to it.

Reading a release

Read removals before features. React Native 0.87 removed InteractionManager, replaced by requestIdleCallback, the Modal animated prop, StatusBar's backgroundColor and translucent props, and the NativeMethods types, replaced by HostInstance. ImageBackground is deprecated. The Strict TypeScript API became the default, so imports from react-native/Libraries/* are now type errors.

A deprecation is a dated warning. Fix it in the release that announces it, while the old path still works and you can compare behaviour.

Example

Example · tsx
import { Image, StyleSheet, Text, View } from "react-native";

type HeroProps = {
  title: string;
  source: { uri: string };
};

// ImageBackground is deprecated as of React Native 0.87. The documented
// replacement is a View with an absolutely positioned Image behind its
// children. This is not CSS: there is no background-image property on a
// style, and a View will not paint an image for you.
function Hero({ title, source }: HeroProps) {
  return (
    <View style={styles.hero}>
      <Image source={source} style={styles.background} resizeMode="cover" />
      <Text style={styles.title}>{title}</Text>
    </View>
  );
}

export default function HomeScreen() {
  return (
    <View style={styles.screen}>
      <Hero title="Release notes" source={{ uri: "https://example.com/hero.jpg" }} />
    </View>
  );
}

const styles = StyleSheet.create({
  screen: { flex: 1, justifyContent: "center", padding: 16 },
  hero: {
    height: 180,
    justifyContent: "flex-end",
    padding: 16,
    borderRadius: 12,
    overflow: "hidden",
  },
  // absoluteFillObject is shorthand for the four edges plus position:
  //   { position: "absolute", top: 0, right: 0, bottom: 0, left: 0 }
  background: StyleSheet.absoluteFillObject,
  title: { color: "#ffffff", fontSize: 20, fontWeight: "600" },
});

When to use it

  • Deciding whether to take an SDK upgrade that drops support for the oldest OS version a chunk of your users are on.
  • Planning the Android target SDK bump before Google Play stops accepting updates.
  • Working out, from a release's notes, how many of your own files an upgrade will touch.
  • Replacing a framework API that the release notes marked deprecated, while the old one still works for comparison.

More examples

Buying one version of time on the Strict TypeScript API

The tsconfig.json opt-out React Native 0.87 documents for projects whose code, or whose dependencies, still import from react-native/Libraries/*. Adding it turns those type errors back into working imports so an upgrade can land today and the imports can be rewritten next week. It is deliberately temporary: the condition is honoured through 0.88 and the legacy types are deleted after that. Record the removal date in the same commit, because a workaround with no deadline attached is how a project arrives at the next upgrade still broken.

Example · json
{
  "extends": "@react-native/typescript-config",
  "compilerOptions": {
    "customConditions": ["react-native", "react-native-legacy-deep-imports"]
  }
}

A header after StatusBar lost its Android props

What a removed prop costs in practice. On screen: a dark bar running from the top edge of the display, the clock and battery icons drawn in light-content style over it, and the word Inbox below them. The pattern is the general shape of a removal fix. The old prop asked the platform to colour an area for you; the replacement is to draw that area yourself and size it from the safe-area inset, which also works on an iPhone with a notch.

Example · tsx
import { StatusBar, StyleSheet, Text, View } from "react-native";
import { SafeAreaProvider, useSafeAreaInsets } from "react-native-safe-area-context";

function Header() {
  const insets = useSafeAreaInsets();
  // 0.87 removed StatusBar's backgroundColor and translucent props, so the
  // colour behind the status bar is now your own View, padded by the inset.
  return (
    <View style={[styles.header, { paddingTop: insets.top }]}>
      <Text style={styles.title}>Inbox</Text>
    </View>
  );
}

export default function App() {
  return (
    <SafeAreaProvider>
      {/* barStyle is still supported: it sets the colour of the clock and icons. */}
      <StatusBar barStyle="light-content" />
      <Header />
    </SafeAreaProvider>
  );
}

const styles = StyleSheet.create({
  header: { backgroundColor: "#1f2933", paddingHorizontal: 16, paddingBottom: 12 },
  title: { color: "#ffffff", fontSize: 18, fontWeight: "600" },
});

Finding what the release broke, before the build does

Run this list after the version bump and before the first build, because a type error found in two seconds is cheaper than the same mistake found at the end of a twelve-minute native build. The grep over node_modules matters more than the one over src: your own calls are easy to fix, while a removed API inside an unmaintained library is the finding that decides whether this upgrade happens this week or next quarter.

Example · bash
# Type errors first. With the Strict TypeScript API as the default, deep
# imports and renamed ref types fail here rather than at runtime.
npx tsc --noEmit

# The imports that need rewriting, with file and line.
grep -rn "react-native/Libraries" src

# Removed APIs are not type errors if a library still calls them.
grep -rn "InteractionManager\|ImageBackground" src node_modules/*/lib

# Dependencies that do not support this SDK, and app config drift.
npx expo-doctor

Discussion

  • Be the first to comment on this lesson.