Lesson 10 / 10
Store and Release Constraints
App store rules, store review, and the in-app update also shape how code is shared. The lesson classifies an update flow written on only one platform as single, and measures that code switched off by a flag still counts as branched.
Published Updated
In this lesson
- Explain how app store rules and store review affect the sharing of the code
- Measure an in-app update flow written on only one platform as single
- Assign separate sharing to the shared function that makes the update decision and to the platform code that runs the flow
- Show that code switched off by a flag counts as branched even though it enters the package
Contents
The previous lesson showed that the performance justification can turn the code split. All of that concerned the application at runtime. Getting the code to the user passes through a separate boundary: the app store. The course’s question stays the same here too: how is this feature’s code shared between the platforms? The definitions of the four answers are as in the opening lesson and are not rewritten here.
The core supplies the rule and arrives in every lesson as the same file.
// 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}`); } }
Store Rules and Store Review
Each platform has its own app store, its own publishing console, and its own release rules. Before an application is published, it goes through store review: the package is evaluated against the rules the store sets for that platform. Even if the cross-platform codebase is one, this line is not one. The same fix passes through two separate store lines as two packages and may reach users at two separate times.
This affects the sharing question from two directions. The first is that the rules themselves are platform-specific: a behavior one store allows may not be accepted by the other, and shared code cannot know that. The second is that the features the store provides are platform-specific: one platform may offer users an update flow outside the store, and the other may not. The first direction limits which behavior the code can carry; the second limits on which platform the code can be written. Both directions show up in the sharing class, and this lesson measures the second.
Each platform’s package also carries its own version identity. Independent of the shared code, the platform side holds pieces that look something like this.
// Platform code: AndroidVersionIdentity
android {
defaultConfig {
versionCode = 14
versionName = "2.1"
}
}
// Platform code: IosVersionIdentity
import Foundation
func installedVersion() -> String? {
Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String
}
The two pieces keep the version in two separate places, in two separate forms. The pieces produce no output; they only show that the version information lives outside the shared code, on the platform side.
PR1. The model builds this sharing’s rule; the model is not the framework itself. In the model, the update decision and the update flow are two separate functions. The version numbers and the state names are examples; they do not show a real application’s version scheme.
The Update Decision Is Shared
An in-app update is a flow that offers to install the new version without taking the user to the store. The flow has two parts. The first is the decision: is the installed version below the minimum version the server asks for? The second is the flow itself: showing the offer to the user, starting the download, completing the installation. The decision is a pure comparison. The flow rests on the platform’s store infrastructure.
The decision comes first. There are three samples: the installed version below the minimum, equal to it, above it.
// main.mjs — the function that makes the update decision is the same on both platforms import { Sharing } from "./sharing.mjs"; const versions = [ { installed: 12, minimum: 14 }, { installed: 14, minimum: 14 }, { installed: 15, minimum: 14 }, ]; function isBelowMinimum(version) { return version.installed < version.minimum; } new Sharing( "update-decision", { ios: isBelowMinimum, android: isBelowMinimum }, versions, ).line();
update-decision: code=1 | diff=0/3 -> shared
The decision is shared: the same function, the same result on both platforms in all three samples. The version identity sits on the platform side, but the comparison is between one number and another, and that can move into shared code.
The Flow on Only One Platform
Now the flow. In this model we assume the in-app update flow is written on the Android side only. There is no counterpart of the flow on the iOS side: the user goes to the store on their own.
// main.mjs — the in-app update flow is written on only one platform import { Sharing } from "./sharing.mjs"; const states = ["outdated", "current", "required"]; function androidUpdate(state) { if (state === "current") return "no-action"; return state === "required" ? "install-started" : "update-offered"; } const inAppUpdate = { android: androidUpdate }; new Sharing("in-app-update", inAppUpdate, states).line();
in-app-update: code=1 | missing=ios -> single
The missing=ios output means the iOS key is not in the table at all. Because the rule finds no function, it computes no difference; it prints missing instead of diff and the class is single. The code is one, because there is a single function on a single platform.
PR2. Which platform the update flow can be written on is an assumption in this lesson. In the model, the Android side exists and the iOS side does not. What a platform’s store infrastructure really offers is not measured in the lesson and is not written as a claim on behalf of a platform; only the form of the table is shown.
A distinction has to be made here, because two questions get mixed up. Does this code go to production? The answer to this question, which we asked in M12/K05, is yes: it enters the Android package and reaches users. But single does not mean the code stays out of the package; it means it has not been written on one platform at all. Entering the package and being written are separate questions: the first asks about the contents of the package, the second about the existence of code on the two platforms.
The decision is shared, the flow is single. When the same feature is split into two pieces, two separate classes came out. The rule does not classify a feature as a whole; it looks at which piece is measured.
Code Downloaded Later
There is one more release constraint, and this lesson does not measure it. Some frameworks can change behavior with code downloaded later, outside the package that passed through the app store. This promises to ship fixes without waiting for store review.
PR3. How far downloaded-later code is permitted within the store rules is an assumption in this lesson and is not measured. The limit depends on the store’s rules; the rules can change and can differ from platform to platform. Before using this path, a team should check the current rules for its own release; the lesson writes no limit.
From the point of view of the sharing question, this path changes nothing: code downloaded later also sits in a codebase, and the rule classifies it with the same three questions.
The Bounding Measurement
A flag draws the last boundary. Put the update code into both packages, but switch it off with a fixed flag in the iOS package. The code is the same function; the flag is on or off depending on the platform.
// main.mjs — the same update code enters both packages, switched off by a flag on one platform import { Sharing, countSharing } from "./sharing.mjs"; const states = ["outdated", "current", "required"]; const updateEnabled = { ios: false, android: true }; function androidUpdate(state) { if (state === "current") return "no-action"; return state === "required" ? "install-started" : "update-offered"; } function guardedUpdate(state, platform) { if (!updateEnabled[platform]) return "off"; return androidUpdate(state); } const single = new Sharing("in-app-update", { android: androidUpdate }, states); const flagged = new Sharing( "flagged-update", { ios: guardedUpdate, android: guardedUpdate }, states, ); single.line(); flagged.line(); countSharing([single, flagged]);
in-app-update: code=1 | missing=ios -> single flagged-update: code=1 | diff=3/3 -> branched shared: 0 branched: 1 split: 0 single: 1
The same feature received two different classes. When written on one platform, it came out single; when it entered both packages and was switched off by a flag on one platform, it came out branched. The code is one, because both platforms carry the same guardedUpdate function. The difference is three of three, because on the switched-off platform every sample returns off, while on the enabled platform the result varies with the sample.
This makes the distinction concrete. Code switched off by a flag enters the package, but its behavior runs on only one platform. The rule does not count it as single, because the function exists on both platforms; it counts it as branched, because the result differs by platform.
The rule sees the structure, not the cost. Code that is carried in both packages and never runs in one of them puts weight on the package, has to be tested, and reaches users the day the flag is forgotten. None of this shows up in the class. Prose says the maintenance cost.
PR4. In the model the flag is a fixed table. In a real application the flag may come from the server, may change by version, or may remove the code from the package entirely at build time; in the last case the code does not enter the package and the model being measured changes. This lesson measures only code that stays inside the package and is switched off at runtime.
The Closing Table
Over ten lessons the course asked the same question. The table puts side by side the feature each lesson measured, the sharing it received, and the measurement that bounded the lesson.
| Lesson | Feature measured | Sharing | Bounding measurement |
|---|---|---|---|
| The Case for Cross-Platform | Cart total, the same function on both platforms | shared | The badge function is shared with three samples; branched once a fourth sample is added |
| The WebView-Based Approach | The function that draws a list row | shared | Branched once the safe area inset is read from the shell |
| The Bridge-Based Approach | A single function that maps a node to a platform widget | branched | Split once the mapping is divided into two separate functions |
| The Own-Rendering-Engine Approach | The layout function that computes box positions | shared | Branched once the text width comes from the platform font |
| Shared Logic, Native UI | The UI functions; validation shared | split | Once a UI field is forgotten, the diff is above zero and it is still split |
| Approach Selection Criteria | A home screen piece written on only one platform | single | Counting the same six features under two setups changes the distribution; this stays single |
| Platform Design Language Differences | A single function that picks the navigation pattern from the screen type | branched | Shared in the adapted design |
| The Need for Native Modules | The biometric authentication module | split | A capability present on only one platform is single |
| Performance Expectations | Thumbnail decoding | split | The same computation is shared in shared code with a single function; bridge crossings from 1 to 6 |
| Store and Release Constraints | The in-app update flow | single | Code switched off by a flag is branched |
The distribution over the ten lessons: shared 3, branched 2, split 3, single 2. This is not the same thing as the 1 shared, 1 branched, 1 split, 1 single distribution in the opening lesson. The core tested four features and gave one example from each class; the table counts ten lessons, and each lesson’s main feature is a single row. The two numbers are the results of separate measurements and are not derived from each other.
What stands out in the table is that no class stays empty and none comes out dominant. But this is a distribution, not a recommendation. The lessons made the choice of features themselves; had other features been chosen, the distribution would be different. The table does not say whether an approach is good or bad; it only shows how the code of the feature each lesson measured is shared.
Summary
- This lesson’s sharing: single. Because the in-app update flow was written on only one platform, the rule printed
missing=iosand counted the feature as single. - The function that makes the update decision is the same on both platforms and is shared; two pieces of the same feature can receive two separate classes.
- Single does not mean the code stays out of the package; it means it has not been written on one platform at all. Entering the package and being written are separate questions.
- When the same code entered two packages and was switched off by a flag on one platform, the feature became branched; the rule sees the structure and does not see the maintenance cost of the switched-off code.
- How far downloaded-later code is permitted within the store rules is an assumption in this lesson and is not measured.
Course Wrap-Up
The course asked a single question: how is this feature’s code shared between the platforms? The closing table shows that all four answers were given: shared, branched, split, and single. Each answer was met with a real measurement in at least two lessons. The course’s gain is being able to make this distinction on a feature’s code, and knowing that the distinction is one where the rule sees the structure and does not see the cost. The next course sets its own question in its own opening lesson.
To keep your progress and take notes, Log in
My notes
Log in to take notes.