---
title: 'Platform-Specific Code'
source: 'https://academia.sh/en/courses/react-native/platform-specific-code'
course: 'React Native'
language: en
updated: '2026-10-21T05:02:54+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Explains that a description that diverges by platform produces two separate trees'
    - 'Measures that a node present on only one platform comes out mounted in the comparison'
    - 'Distinguishes that this comparison is not an update that happens at runtime'
    - 'Counts the share of shared code and the platform-specific share by the common nodes of the two trees'
---

# Platform-Specific Code

The same screen can be described differently on two platforms. The lesson compares two platform trees, shows that a toggle switch present on only one platform comes out mounted, and states plainly that this is not a runtime update.

In the previous lessons we measured how a single tree changes over time. In this lesson there are two trees, and the two never exist at the same time: one runs on iOS, the other on Android. While describing a screen, shared code can look at the platform and produce different nodes. The question then is this: how similar are the trees of the two platforms, and how does the difference appear in the tree?

One thing must be stated clearly from the start. An app is at any moment on either iOS or Android; at runtime it does not move from one platform to the other. Comparing the two trees, that is, deriving a fate from one platform's tree to the other's, is not a runtime update. Here the comparison is a measuring tool: it shows where the two trees part ways.

Code that asks about the platform looks like this in the framework. The snippet is not measured.

```jsx
// Platform code: Row
function Row({ label }) {
  return (
    <View>
      <Text>{label}</Text>
      {Platform.OS === "ios" ? <Switch /> : <Checkbox />}
    </View>
  );
}
```

`Platform.OS` gives the name of the platform it runs on. The expression is a condition: if it is `ios`, a toggle switch is returned, otherwise a checkbox. The same container and the same label, but the type of the last child differs by platform.

## Shared Code, Diverging Description

The framework's core types are shared by the two platforms; what is platform-specific is which of those types is selected. Shared code makes this selection inside the describing function, and two separate trees arise. The core is needed first for the measurement.

```javascript
// fate.mjs — classifies what an update does to each node of the view tree
export const FATES = ["kept", "updated", "mounted", "unmounted"];

export function el(type, props = {}, children = [], key = null) {
  return { type, props, children, key };
}

function segment(node) {
  return node.key === null ? node.type : `${node.type}[${node.key}]`;
}

function identity(node, index) {
  return node.key === null ? `#${index}` : `key:${node.key}`;
}

function sameProps(a, b) {
  const names = [...new Set([...Object.keys(a), ...Object.keys(b)])].sort();
  return names.every((n) => JSON.stringify(a[n]) === JSON.stringify(b[n]));
}

function whole(node, path, fate, ops) {
  const here = `${path}/${segment(node)}`;
  ops.push({ fate, path: here });
  node.children.forEach((child) => whole(child, here, fate, ops));
}

export function reconcile(prev, next, path = "", ops = []) {
  if (prev === null) {
    whole(next, path, "mounted", ops);
    return ops;
  }
  if (next === null) {
    whole(prev, path, "unmounted", ops);
    return ops;
  }
  if (prev.type !== next.type || prev.key !== next.key) {
    whole(prev, path, "unmounted", ops);
    whole(next, path, "mounted", ops);
    return ops;
  }
  const here = `${path}/${segment(next)}`;
  ops.push({ fate: sameProps(prev.props, next.props) ? "kept" : "updated", path: here });
  const before = new Map(prev.children.map((child, i) => [identity(child, i), child]));
  const matched = new Set();
  next.children.forEach((child, i) => {
    const id = identity(child, i);
    matched.add(id);
    reconcile(before.has(id) ? before.get(id) : null, child, here, ops);
  });
  prev.children.forEach((child, i) => {
    if (!matched.has(identity(child, i))) reconcile(child, null, here, ops);
  });
  return ops;
}

export function printOps(ops) {
  ops.forEach((op) => console.log(`${op.fate}: ${op.path}`));
}

export function countFates(ops) {
  for (const name of FATES) {
    console.log(`${name}: ${ops.filter((op) => op.fate === name).length}`);
  }
  console.log(`commands: ${ops.filter((op) => op.fate !== "kept").length}`);
}
```

This lesson's helper file produces a settings screen tree from the platform name. On both platforms the screen carries a title and a label. On Android there is additionally a toggle switch; on iOS this node does not exist. The second function produces the same row with a different type on the two platforms; it is compared below.

```javascript
// settings.mjs — produces a settings screen tree from the platform name
import { el } from "./fate.mjs";

export function renderSettings(platform) {
  const children = [
    el("Text", { value: "Settings" }),
    el("Text", { value: "Notifications" }),
  ];
  if (platform === "android") {
    children.push(el("Toggle", { on: true }));
  }
  return el("Settings", {}, children);
}

export function renderChoice(platform) {
  return el("Row", {}, [
    el("Text", { value: "Notifications" }),
    platform === "ios" ? el("Switch", { on: true }) : el("Checkbox", { on: true }),
  ]);
}
```

## How Much Is Shared

The first measurement compares the same platform's tree with itself. This is a reference that shows shared code is consistent within itself: the platform does not change, so the fate does not change either.

```javascript
// main.mjs
import { reconcile, printOps, countFates } from "./fate.mjs";
import { renderSettings } from "./settings.mjs";

const ops = reconcile(renderSettings("ios"), renderSettings("ios"));
printOps(ops);
countFates(ops);
```

```text
kept: /Settings
kept: /Settings/Text
kept: /Settings/Text
kept: 3
updated: 0
mounted: 0
unmounted: 0
commands: 0
```

Three nodes are kept, and the commands are zero. This is the reference: if the iOS description is compared with the iOS description, there is no difference. Now we compare the two platforms.

## The Difference Between the Two Platforms

The second measurement compares the iOS tree with the Android tree. The container and the two texts exist on both; the toggle switch is only on Android.

```javascript
// main.mjs
import { reconcile, printOps, countFates } from "./fate.mjs";
import { renderSettings } from "./settings.mjs";

const ops = reconcile(renderSettings("ios"), renderSettings("android"));
printOps(ops);
countFates(ops);
```

```text
kept: /Settings
kept: /Settings/Text
kept: /Settings/Text
mounted: /Settings/Toggle
kept: 3
updated: 0
mounted: 1
unmounted: 0
commands: 1
```

The three common nodes are kept, and `/Settings/Toggle` is mounted. The share of shared code is three nodes, and the platform-specific share is one node. We read this as follows: the screens of the two platforms are equal in three of four nodes. This is a count, not a ratio; the number is valid only for these two trees.

The word mounted must be read carefully here. An app running on iOS never mounts that toggle switch, because it never sees Android's tree. The comparison is not a transition but a distinction: the answer to the question "which extra node is there on Android".

**CS10.** The model produces the trees of the two platforms in the same process and at the same time. In reality only one is produced, and the platform it runs on stays fixed throughout the app. This model is a convenience needed to make a comparison, not runtime behavior.

## The Platform Module

Alongside file selection, the framework offers a small module that asks which platform it is running on. The module gives the platform's name and contains a helper for selecting values from a table: one value is written for iOS and another for Android, and the running platform's value is returned. These two ways do the same job; in one the selection is made inside an expression, in the other in the file name. For small differences, for example for a margin to be different on the two platforms, the module is enough; if a whole screen fragment needs to be written separately for the two platforms, the file path stays cleaner.

A single question helps when deciding: is the divergence a detail of the look, or the behavior itself? If it is a detail, it appears in the tree as a prop difference and comes out updated in the comparison. If it is behavior, different types or different nodes arise in the tree and the comparison gives mounted and unmounted. A prop difference keeps the shared share high; a type difference lowers it. For this reason, making the divergence in the smallest possible node is the way to protect the shared share.

## Selection by File Extension

Writing a condition is not the only way to diverge by platform. The framework can choose between files that carry the same name but state the platform in the extension: one file written for iOS and another written for Android stand side by side, and the import writes only the shared name. The running platform decides which file to choose; there is no `if` in the code.

```jsx
// Platform code: Toggle
// Toggle.ios.js    -> file written for iOS
// Toggle.android.js -> file written for Android
import Toggle from "./Toggle";

function Row() {
  return <Toggle on={true} />;
}
```

The first two lines in the snippet are comments showing the file names; the code imports only the shared name. Which file will be used is chosen when the app is bundled: a separate bundle is built for each platform, and the file with the `.ios` extension goes into the iOS bundle and the file with the `.android` extension into the Android bundle. The benefit of this method is that platform-specific code stays in separate files and shared files do not fill up with conditions. Its cost is that the two files can drift apart: if one is updated and the other is forgotten, the two platforms quietly differ. The tree shows this drift when we compare the trees of the two platforms.

## The Same Place, a Different Type

Platform-specific divergence is not only an extra node. The node in the same position can be of a different type on the two platforms: a toggle switch on iOS, a checkbox on Android. In this case the comparison sees the previous lesson's type change again, this time across platforms.

```javascript
// main.mjs
import { reconcile, printOps, countFates } from "./fate.mjs";
import { renderChoice } from "./settings.mjs";

const ops = reconcile(renderChoice("ios"), renderChoice("android"));
printOps(ops);
countFates(ops);
```

```text
kept: /Row
kept: /Row/Text
unmounted: /Row/Switch
mounted: /Row/Checkbox
kept: 2
updated: 0
mounted: 1
unmounted: 1
commands: 2
```

The container and the label came out kept, the toggle switch unmounted, the checkbox mounted; the commands are two. Of four nodes, two are common. In the previous measurement the platform-specific share was a single node; here it is two nodes: the same position, but one type in place of another. The same screen being "the same" on two platforms does not mean the nodes are the same in the tree. A single codebase does not guarantee the same screen; what it guarantees is that the screen is produced from the same source on the two platforms.

Here is a misconception to pick out: "I wrote shared code, so the two platforms show the same screen." Shared code can produce two trees, and if the trees differ, the native side builds different views. The person who wants to raise the shared share can use this comparison as a checking tool: every node that diverges is either a platform difference left in deliberately or a drift that came in without being noticed. The tree does not say which is which; it only shows where they part.

## The Bounding Measurement

The focus node is `/Settings/Toggle`. The last measurement also tests the reverse of the divergence: when one looks from the Android tree to the iOS tree, the toggle switch disappears.

```javascript
// main.mjs
import { reconcile, printOps, countFates } from "./fate.mjs";
import { renderSettings } from "./settings.mjs";

const ops = reconcile(renderSettings("android"), renderSettings("ios"));
printOps(ops);
countFates(ops);
```

```text
kept: /Settings
kept: /Settings/Text
kept: /Settings/Text
unmounted: /Settings/Toggle
kept: 3
updated: 0
mounted: 0
unmounted: 1
commands: 1
```

When the direction changed, the fate changed too: looking from Android to iOS, the toggle switch came out unmounted; looking from iOS to Android, it came out mounted. The three common nodes are kept in both directions. A comparison has a direction, and the fate depends on that direction; but whichever direction one looks from, the size of the common and diverging share is the same.

This lesson's fate is mounted, but its limit must be written from the start: this mounted did not come from an update that happens at runtime. It came from comparing one tree with the other. This is the measure we use when looking at platform-specific code: in how many nodes the two trees are common, and in how many they diverge.

This differs from the question of M32/K01. In that lesson we measured sharing: how much of the code is common to two platforms. Here sharing was not measured; what was measured is what fate a node in the trees of the two platforms receives in the comparison. We are not saying that a node that is common also corresponds to the same native view on the platform.

**CS11.** The model counts the shared types as the same on the two platforms. In reality the same type produces a different native view depending on the platform; the type name is the same in the tree and the difference is not visible.

The model builds this fate's rule; the model is not the framework itself.

As a last measure, we put the two measurements side by side. On the settings screen, three of four nodes were common and one diverged. On the choice row, two of four nodes were common and two diverged. In the first case the divergence adds something to the screen, in the second it changes something. A person who wants to raise a screen's shared share should first look at which kind of divergence it is; because the addition can be solved with a conditional node, and the change by choosing only the type of that node, and neither touches the container or the siblings.

The limit of these measurements must be stated too. The trees we compared carry only types and props; how the native views of the two platforms look on the screen, which typeface they use, or how they compute margins is not in the tree. For this reason the result "the shared share is high" does not mean the screens look alike; it only means the descriptions are alike. For the looks to be alike, one has to look with one's own eyes as well, and this course's measure does not cover that.

Finally, we stress once more why this lesson is not a runtime update, because the word `mounted` in the measurement output can mislead. The app produces the same tree from start to finish on the platform it is installed on. Because the platform does not change, `renderSettings("android")` and `renderSettings("ios")` are never called one after the other in an app's life. The updates that happen at runtime happen within a platform, as the state changes, and the measurements of the earlier lessons showed them. In this lesson, putting the two trees side by side is a tool used to make the divergence visible.

There is a small trap when counting the share of shared code: the number depends on the size of the tree. On a screen of four nodes, one diverging node looks like a large share; on a screen of a hundred nodes, the same single node hardly stands out. For this reason, before comparing numbers across screens, one has to look at the node counts. In this lesson every number is valid only for the two trees of that measurement and is not generalized. A person who wants to generalize sets up the same measurement again on their own screens.

A checklist also helps. Before adding a divergence, these are asked: does the difference really come from the platform, or from indecision in the design? Is the diverging node the smallest node? Was the counterpart of the divergence in the two trees measured and the shared share recorded? If the answers to the three questions are clear, the divergence counts as made deliberately.

## Summary

- This lesson's fate: mounted. In the second measurement, `/Settings/Toggle`, found only on Android, came out mounted when one looked from the iOS tree to the Android tree; this is not a runtime update but a comparison of two trees.
- A description that diverges by platform produces two separate trees; the app sees only one of them.
- The share of shared code is counted by the common nodes and the platform-specific share by the diverging nodes: in four nodes, three common and one diverging.
- The direction of the comparison determines the fate: the same node comes out mounted in one direction and unmounted in the other.
- The same type name does not guarantee the same native view on the two platforms; the tree does not show this difference.

## Next Step

The core structure is completed here: tree, components, style, touch, and platform difference. The next topic, `Navigation`, covers what moving between screens does to the tree: when one screen goes and another comes, which nodes are unmounted and mounted.
