---
title: 'Approach Selection Criteria'
source: 'https://academia.sh/en/courses/cross-platform-approaches/approach-selection-criteria'
course: 'Cross-Platform Approaches'
language: en
updated: '2026-10-21T05:02:49+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Explains that approach selection starts from how dependent the features are on the platform'
    - 'Measures that a feature present on only one platform comes out single in whichever approach it is placed'
    - 'Compares how the same six features are distributed differently in two mechanisms'
    - 'Shows that the sharing counts alone do not choose an approach and that the cost has to be weighed in the prose'
---

# Approach Selection Criteria

Approach selection starts from how dependent each feature is on the platform. The lesson measures the same six features with a single codebase and with shared logic and native UI, compares the two distributions of the four sharings, and shows that the count alone does not choose the approach.

Five lessons measured five mechanisms one by one. Each mechanism had a sharing: the page shared, the bridge branched, the rendering engine shared, and the mechanism with a separately written interface split. In a real app, however, there is no single feature. There is a list, and each row of the list carries its own sharing. Approach selection looks at how much of this list falls on which sharing.

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 of the same six features in two mechanisms, and the two distributions are put side by side. The lesson does not recommend an approach. It shows what a recommendation must rest on.

The first input of the selection is how dependent the feature is on the platform. The price format never reads the platform. Title shortening is affected by the platform according to the width of the text area. The navigation bar follows the platform's own habit. Biometric login calls the platform's own programming interface. A home screen tile, in this app, is written for only one platform. The six features are six different degrees of dependence on the platform.

**AP1.** The model builds this sharing's rule; the model does not build an app, does not draw a screen, and does not call the programming interface of a platform. In the model the six features are a fixed list. In the app the "home screen tile" exists only for iOS; an Android counterpart is outside the scope of this app. This is an assumption and not a real product decision.

```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 functions of the six features in a shared file. The next two measurements combine them with different mechanisms.

```javascript
// catalog.mjs — functions of the six features
export function priceText(cents) {
  const pad = String(cents % 100).padStart(2, "0");
  return `${Math.floor(cents / 100)}.${pad}`;
}

export function cartSum(lines) {
  return lines.reduce((sum, [price, qty]) => sum + price * qty, 0);
}

export function titleCut(title, platform) {
  const limit = platform === "ios" ? 10 : 8;
  return title.length > limit ? `${title.slice(0, limit - 1)}~` : title;
}

export function backBar(screen, platform) {
  return `${screen}:${platform === "ios" ? "left" : "bottom"}-back`;
}

export const iosBiometric = (user) => `face:${user}`;
export const androidBiometric = (user) => `finger:${user}`;
export const iosTile = (name) => `tile:${name}`;
```

`titleCut` and `backBar` are written independent of the platform but read the platform name. In the measurement these two functions stand as a single function. The two functions of biometric login and the single function of the home screen tile are named separately.

## The Single Feature

In the first measurement we put the features at the two ends: the price format, which is not tied to the platform at all, and the home screen tile, which in this app is written for only one platform. The first will come out shared. For the second there is no Android app, and only the `ios` key exists in `impl`.

```javascript
// main.mjs
import { Sharing, countSharing } from "./sharing.mjs";
import { priceText, iosTile } from "./catalog.mjs";

const prices = [1250, 300, 99999, 5];
const tiles = ["daily", "weekly"];

const features = [
  new Sharing("price-format", { ios: priceText, android: priceText }, prices),
  new Sharing("home-screen-tile", { ios: iosTile }, tiles),
];
features.forEach((f) => f.line());
countSharing(features);
```

```text
price-format: code=1 | diff=0/4 -> shared
home-screen-tile: code=1 | missing=android -> single
shared: 1
branched: 0
split: 0
single: 1
```

For `price-format` the output was `diff=0/4` and `code=1`: this feature is written once and gives the same result on the two platforms. For `home-screen-tile` `missing=android` is read instead of `diff`, and the sharing is **single**. In a single feature there is no second app to compare with, so `diff` is not computed. Whichever approach is chosen, this feature stays single, because what is missing is not the mechanism of the code but the app on the other platform itself.

The source of a feature that exists only on iOS looks like this. The piece is not measured.

```swift
// Platform code: HomeTile
import WidgetKit
import SwiftUI

struct HomeTile: Widget {
    var body: some WidgetConfiguration {
        StaticConfiguration(kind: "home-tile", provider: TileProvider()) { entry in
            Text(entry.title)
        }
    }
}
```

This is a feature that stays on one platform by a scope decision. No shared layer brings an app that was not written on the other platform into existence by itself. The second input of the selection comes from here: **which features are written for only one platform?** These features are kept out of the comparison of approaches, because they have no difference between approaches.

## Six Features in a Single Codebase

In the second measurement we measure the six features with the single-codebase mechanism. In this mechanism each feature is written as a single function as far as possible; the parts that depend on the platform are separated inside the function with the `platform` key. The exceptions are that biometric login calls the platform's programming interface and that the home screen tile exists on one platform.

**AP2.** In the model "single codebase" means this: the features that are sensitive to the platform stay as a single function and read the `platform` argument. Biometric login asks for two separate functions even in this mechanism, because the programming interfaces of the two platforms are not shared. This model does not imitate a shell, a bridge, or an engine; it only counts the number of functions.

```javascript
// main.mjs
import { Sharing, countSharing } from "./sharing.mjs";
import {
  priceText, cartSum, titleCut, backBar, iosBiometric, androidBiometric, iosTile,
} from "./catalog.mjs";

const prices = [1250, 300, 99999, 5];
const carts = [[[100, 2], [250, 1]], [[999, 3]], [[50, 4], [75, 2]]];
const titles = ["Coffee", "Black tea", "Weekly plan", "Tea"];
const screens = ["list", "detail", "cart"];
const users = ["ada", "can", "eda"];
const tiles = ["daily", "weekly"];

const features = [
  new Sharing("price-format", { ios: priceText, android: priceText }, prices),
  new Sharing("cart-sum", { ios: cartSum, android: cartSum }, carts),
  new Sharing("title-shortening", { ios: titleCut, android: titleCut }, titles),
  new Sharing("navigation-bar", { ios: backBar, android: backBar }, screens),
  new Sharing("biometric-login", { ios: iosBiometric, android: androidBiometric }, users),
  new Sharing("home-screen-tile", { ios: iosTile }, tiles),
];
features.forEach((f) => f.line());
countSharing(features);
```

```text
price-format: code=1 | diff=0/4 -> shared
cart-sum: code=1 | diff=0/3 -> shared
title-shortening: code=1 | diff=2/4 -> branched
navigation-bar: code=1 | diff=3/3 -> branched
biometric-login: code=2 | diff=3/3 -> split
home-screen-tile: code=1 | missing=android -> single
shared: 2
branched: 2
split: 1
single: 1
```

The distribution is two shared, two branched, one split, and one single. The first two features are shared because they never read the platform. `title-shortening` and `navigation-bar` are a single function but read the platform key; this is why they came out branched. For `title-shortening` `diff=2/4` is read: in two of the four titles the two platforms produced different shortenings, because the room is ten characters on iOS and eight characters on Android. These two numbers are an input of the model and are not the measure of a real text area. `biometric-login` came out split, because the two platforms call two separate programming interfaces and there are two codes that write this call.

This distribution shows both the strength and the limit of the promise of a single codebase. Four of the six features were written with a single function, but two of the four are still branched. Being a single function does not mean being a single behavior.

## Six Features with Shared Logic and Native UI

In the third measurement we measure the same list with the other mechanism. In this mechanism the business rules stay in the shared module: the price format and the cart sum. The title, the navigation bar, and biometric login belong to the interface layer and are written as a separate function on each platform.

**AP3.** In the model the interface functions wrap the shared function with the platform name: `iosTitle` and `androidTitle` call the same shortening rule with their own platform name, and `iosBack` and `androidBack` call the same navigation rule. The two wrappers are separate function objects; this represents the two being written separately. In real teams two interface functions do not have to wrap a shared rule, and most of the time they do not.

```javascript
// main.mjs
import { Sharing, countSharing } from "./sharing.mjs";
import {
  priceText, cartSum, titleCut, backBar, iosBiometric, androidBiometric, iosTile,
} from "./catalog.mjs";

const iosTitle = (title) => titleCut(title, "ios");
const androidTitle = (title) => titleCut(title, "android");
const iosBack = (screen) => backBar(screen, "ios");
const androidBack = (screen) => backBar(screen, "android");

const prices = [1250, 300, 99999, 5];
const carts = [[[100, 2], [250, 1]], [[999, 3]], [[50, 4], [75, 2]]];
const titles = ["Coffee", "Black tea", "Weekly plan", "Tea"];
const screens = ["list", "detail", "cart"];
const users = ["ada", "can", "eda"];
const tiles = ["daily", "weekly"];

const features = [
  new Sharing("price-format", { ios: priceText, android: priceText }, prices),
  new Sharing("cart-sum", { ios: cartSum, android: cartSum }, carts),
  new Sharing("title-shortening", { ios: iosTitle, android: androidTitle }, titles),
  new Sharing("navigation-bar", { ios: iosBack, android: androidBack }, screens),
  new Sharing("biometric-login", { ios: iosBiometric, android: androidBiometric }, users),
  new Sharing("home-screen-tile", { ios: iosTile }, tiles),
];
features.forEach((f) => f.line());
countSharing(features);
```

```text
price-format: code=1 | diff=0/4 -> shared
cart-sum: code=1 | diff=0/3 -> shared
title-shortening: code=2 | diff=2/4 -> split
navigation-bar: code=2 | diff=3/3 -> split
biometric-login: code=2 | diff=3/3 -> split
home-screen-tile: code=1 | missing=android -> single
shared: 2
branched: 0
split: 3
single: 1
```

The distribution became two shared, zero branched, three split, and one single. The two features that were branched in the first measurement turned split here: their results did not change, `diff=2/4` and `diff=3/3` stayed the same, but because each platform has its own function the code count rose to two. The shared count and the single count did not change. Half of the six features are written twice in this mechanism.

## The Bounding Measurement

When the two distributions are put side by side, it is tempting to draw a conclusion: the first has two branched and one split, the second has zero branched and three split, so one is as good or as bad as the other. The rule cannot say this. The counts give the sharing of the two mechanisms over six features. They do not give which will ask for less maintenance, which will lead to fewer errors, or which will look better to the user.

A branched feature is a single function but has a branch inside; both arms of the branch ask for maintenance, and the drifting of the arms must be watched. A split feature is two functions and both ask for maintenance. The answer to the question of which is more expensive depends on how often the feature changes and on whether a change goes to the two platforms together. The rule does not see this information; it sees only the structure of the code.

For this reason the selection looks at at least four inputs, and the measurement helps with only the first:

- Which sharing the features fall into. This is what the lesson measured.
- The share of features that exist on only one platform. These stay single in every mechanism.
- Which codebases the team can keep maintaining. This is not a measurement but a question of capacity.
- How much the app has to conform to the look and habits of the platform. This is not a question of sharing but a question of product.

The first input is measured, the others are discussed. No approach is chosen from the first input alone. The measurement gives the selection a ground, and the prose says where the counts may mislead.

## Summary

- This lesson's sharing: single, for one feature; the carrying measurement of the lesson is the distribution of the same six features in two mechanisms. Because the home screen tile is written for only one platform, it stays single whichever mechanism is chosen.
- In the single-codebase mechanism the six features were distributed as two shared, two branched, one split, and one single.
- In the shared-logic and native UI mechanism the same six features were distributed as two shared, zero branched, three split, and one single; the two branched features turned split.
- The rule counts the structure of the code but cannot count its cost: which distribution will ask for less maintenance depends on how often the feature changes and on the capacity of the team.
- Approach selection looks at at least four inputs: the distribution of sharing, the features present on only one platform, the maintenance capacity of the team, and the need of the product to conform to the look of the platform.

## Next Step

This course measured how the approaches share code. The next course, `Platform Design Language Differences`, draws the limit of sharing from another place: even when the same code runs on two platforms, the look and behavior that the user expects may diverge by platform.
