---
title: 'The Bridge-Based Approach'
source: 'https://academia.sh/en/courses/cross-platform-approaches/bridge-based-approach'
course: 'Cross-Platform Approaches'
language: en
updated: '2026-10-21T05:02:49+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Explains how the bridge-based approach relates shared code, the bridge, and the native view tree'
    - 'Measures why the single function that maps a node to a platform component comes out branched'
    - 'Shows that branched sharing arises from the platform changing, not from repetition or order'
    - 'Measures that dividing the same mapping into two separate functions turns the sharing from branched to split'
---

# The Bridge-Based Approach

The bridge-based approach defines the interface in shared code and maps each node to a platform's native component through a bridge. The lesson measures the sharing of the mapping: the same mapping function gives a different component name per platform, and it turns split when the mapping is divided into two functions.

The previous lesson measured the mechanism that draws the interface from a single page and showed that the sharing follows not the page code but the input the shell provides. The second mechanism does not draw the interface in a page. The definition of the interface lives in shared code; the code produces a tree of nodes, and each node of this tree corresponds to a native component of the platform. The intermediary between the node and the component is called the bridge.

The question of the course does not change: **how is this feature's code shared between the platforms?** In this lesson the question is asked separately of the two ends of the bridge. The shared end is a single code. The native end is the components that actually appear on screen on each platform.

The mechanism works as follows. The shared code builds a tree of nodes: a button, a label, an input field, a spacer. The bridge translates this tree into the platform's tree; a mapping decides which native component to create for each node. The controls that appear on screen are the platform's own components. For this reason the look and the behavior match what the platform expects. What the shared code writes is only the definition of the tree.

**AP1.** The model builds this sharing's rule; the model does not run the bridge, the shared code, or the native components. In the model, the bridge's mapping is a table: a node type goes in, the platform's component name comes out. The component names are real API names, such as `UIButton` for iOS and `Button` for Android, but the model does not create these components; it only returns their names.

```javascript
// sharing.mjs — classifies how a feature's code is shared between the platforms
export const PLATFORMS = ["ios", "android"];

export class Sharing {
  constructor(label, impl, samples) {
    this.label = label;
    this.total = samples.length;
    this.missing = PLATFORMS.filter((p) => typeof impl[p] !== "function");
    const present = PLATFORMS.filter((p) => !this.missing.includes(p));
    this.code = new Set(present.map((p) => impl[p])).size;
    if (this.missing.length > 0) {
      this.differ = null;
      this.verdict = "single";
      return;
    }
    this.differ = samples.filter((s) => {
      const outs = PLATFORMS.map((p) => JSON.stringify(impl[p](s, p)));
      return new Set(outs).size > 1;
    }).length;
    if (this.code > 1) this.verdict = "split";
    else this.verdict = this.differ === 0 ? "shared" : "branched";
  }

  line() {
    const detail = this.differ === null
      ? `missing=${this.missing.join(",")}`
      : `diff=${this.differ}/${this.total}`;
    console.log(`${this.label}: code=${this.code} | ${detail} -> ${this.verdict}`);
  }
}

export function countSharing(items) {
  for (const name of ["shared", "branched", "split", "single"]) {
    console.log(`${name}: ${items.filter((s) => s.verdict === name).length}`);
  }
}
```

We put the mapping table and the mapping function in a shared file. The next two measurements import it.

```javascript
// bridge.mjs — bridge: maps a node type to the platform's component name
export const NATIVE = {
  button: { ios: "UIButton", android: "Button" },
  label: { ios: "UILabel", android: "TextView" },
  input: { ios: "UITextField", android: "EditText" },
  spacer: { ios: "spacing", android: "spacing" },
};

export function mapToNative(node, platform) {
  return NATIVE[node][platform];
}
```

The `spacer` node gets the same name on both platforms: a gap is drawn not as a component but as a share of the surrounding layout. The other three nodes have a separate component on each platform.

## Shared Code, Branched Mapping

The same shared code goes to both platforms. The function of the bridge that translates a node type into a component name is a single function; it takes the platform as an argument. Also, the event name that returns to the shared code when the button is tapped is the same on both platforms: the bridge reduces the different events of the two platforms to a single name.

```javascript
// main.mjs
import { Sharing, countSharing } from "./sharing.mjs";
import { mapToNative } from "./bridge.mjs";

function eventName(node) {
  return node === "button" ? "press" : "none";
}

const nodes = ["button", "label", "input", "spacer"];

const features = [
  new Sharing("component-mapping", { ios: mapToNative, android: mapToNative }, nodes),
  new Sharing("event-name", { ios: eventName, android: eventName }, nodes),
];
features.forEach((f) => f.line());
countSharing(features);
```

```text
component-mapping: code=1 | diff=3/4 -> branched
event-name: code=1 | diff=0/4 -> shared
shared: 1
branched: 1
split: 0
single: 0
```

`component-mapping` is branched. The mapping function is single, but in three of the four nodes the result differs by platform: the button, the label, and the input field. The fourth node, the spacer, does not differ. Looked at from its shared end, one portion of the same code produces two separate components on two platforms. `event-name`, on the other hand, is shared, because the event name is an interface that the bridge leaves uniform on the way up.

These two lines show what the bridge shares. The definition of the tree is shared, and the identity of the component is branched. The shared code asks for a button; the bridge and the platform decide which button comes out. This is the gain of the approach: the platform's own control appears on screen. Its cost comes from the same place: a node type that the mapping does not know, or a feature specific to the platform, cannot be expressed in the shared code.

The tree produced by the shared code looks like this. The piece is not measured; the model does not run the shared code.

```jsx
// Platform code: Cart
function Cart({ count, onPay }) {
  return (
    <View>
      <Text>Cart: {count} items</Text>
      <Button title="Pay" onPress={onPay} />
    </View>
  );
}
```

There are three nodes in this tree, and each crosses the bridge once. A bridge crossing is a communication between the shared code and the native side; when it repeats often on a hot path, it gains importance as a unit of work. The performance expectations topic takes this up separately.

## Two Trees, One Bridge

In the bridge-based approach two trees live. One is the node tree that the shared code builds; the other is the platform's own native view tree. When a state changes, the shared code produces a new node tree, the bridge compares the old one with the new one, and updates the native tree only for the nodes that changed. Each update is a bridge crossing: a message goes from the shared side to the native side.

This mechanism has two consequences. The first is that user interaction starts in the native component, the event comes through the bridge to the shared code, and the response of the shared code returns again through the bridge to the native side. The two-way crossing determines how a screen responds; for this reason the team examines frequently updated places, such as scrolling and typing, separately. The second is that, because the native tree is the platform's own tree, behaviors such as accessibility, focus, and text input are left to the platform's own component. The shared code neither writes nor repeats these behaviors.

The model builds these two trees as a single mapping. In a real bridge a node has many properties: color, size, text, margin. Each property can be mapped separately, and each is a separate sharing question. Whether a property gives the same result on two platforms or not tells whether the mapping of that property is shared or branched. The approach therefore produces not a single sharing but a distribution of sharing, feature by feature.

## Branched Is Not Repetition

In an earlier course, in M30/K05, we asked whether repeating an operation or changing its order changes the result. The branched here is not that question. Branched is the result changing not with the repetition or the order of the input but with the change of the platform. To test this, the same mapping is measured with three separate sample lists: the plain order, the reversed order, and a list in which each node is repeated twice.

```javascript
// main.mjs
import { Sharing, countSharing } from "./sharing.mjs";
import { mapToNative } from "./bridge.mjs";

const nodes = ["button", "label", "input", "spacer"];
const reversed = [...nodes].reverse();
const doubled = [...nodes, ...nodes];

const features = [
  new Sharing("mapping-ordered", { ios: mapToNative, android: mapToNative }, nodes),
  new Sharing("mapping-reversed", { ios: mapToNative, android: mapToNative }, reversed),
  new Sharing("mapping-repeated", { ios: mapToNative, android: mapToNative }, doubled),
];
features.forEach((f) => f.line());
countSharing(features);
```

```text
mapping-ordered: code=1 | diff=3/4 -> branched
mapping-reversed: code=1 | diff=3/4 -> branched
mapping-repeated: code=1 | diff=6/8 -> branched
shared: 0
branched: 3
split: 0
single: 0
```

The order changed and `diff` stayed the same: the results of three nodes kept differing by platform. The list doubled and `diff` went from 3/4 to 6/8: the ratio is the same, because when each node was repeated, the platform difference was repeated too. All three measurements are branched. This shows that the cause of branched sharing is not the repetition or the order of the input; the cause is the platform itself.

**AP2.** The node types and the component names are inputs of the model. A real bridge can map one node type to more than one component and can change the mapping by version; the model reduces these to a single table.

## Common Misconceptions

Two misconceptions are often seen about the bridge-based approach. The first is the idea that "if there is shared code, the screens look the same on both platforms". The component that appears on screen is the platform's own component; for this reason they differ. The bridge does not hide this difference; it hides it from the shared code. The shared code does not know which component is drawn, and because it does not know, it cannot control the behavior of that component either.

The second is the idea that "branched is bad, shared is good". Branched is the purpose of this approach: the user wants to see the component of their own platform on both platforms. Increasing sharing as much as possible is not the right goal for every feature. The rule classifies branching not as an error but as a structure; the team decides which feature should stay branched.

## The Bounding Measurement

This time the same mapping is divided into two separate functions. One returns the component names of iOS, the other those of Android; each function reads its own table. The results are the same as in the previous measurement: only the number of functions becomes two.

```javascript
// main.mjs
import { Sharing, countSharing } from "./sharing.mjs";
import { NATIVE, mapToNative } from "./bridge.mjs";

function mapIos(node) {
  return NATIVE[node].ios;
}

function mapAndroid(node) {
  return NATIVE[node].android;
}

const nodes = ["button", "label", "input", "spacer"];

const features = [
  new Sharing("mapping-one-function", { ios: mapToNative, android: mapToNative }, nodes),
  new Sharing("mapping-two-functions", { ios: mapIos, android: mapAndroid }, nodes),
];
features.forEach((f) => f.line());
countSharing(features);
```

```text
mapping-one-function: code=1 | diff=3/4 -> branched
mapping-two-functions: code=2 | diff=3/4 -> split
shared: 0
branched: 1
split: 1
single: 0
```

The `diff` value of the two measurements is the same: the results of three nodes differ on the two platforms. Only `code` changed, from 1 to 2. The rule does not see the result; it sees the number of codes. Although the results stayed the same, the sharing turned from branched to split.

This limit is a warning. When the mapping is divided into two separate functions or two separate files, the same screen keeps producing the same components, but now two codes need maintenance. When a new node type is to be added, the two functions are updated separately, and when one is forgotten the platforms drift apart. In a branched mapping this risk does not exist, because the single table is the place that updates both platforms together. The rule does not see this maintenance difference; the lesson sees it and says it in the text.

## Summary

- This lesson's sharing: branched. The single function that maps a node type to the platform's component name gave different names on the two platforms in three of four nodes.
- In the bridge-based approach the definition of the tree is shared and the identity of the component changes by platform; the platform's own component appears on screen.
- When the mapping was ordered or repeated, the `diff` ratio did not change: branched arises from the platform changing, not from repetition or order.
- When the same mapping was divided into two separate functions, the results stayed the same but the sharing turned split: the rule sees the number of codes.
- The rule does not see the maintenance difference; the risk of one of the two functions being forgotten is stated in the lesson text.

## Next Step

In this lesson the platform's own components drew the interface. The third mechanism uses no native component: it draws everything onto a canvas with its own rendering engine. The next lesson, `The Own-Rendering-Engine Approach`, measures the sharing of the layout function in this mechanism.
