Change Recipes
Default approaches for everyday work. Follow each chain end-to-end; stopping at UI or host-only code is how preview and export diverge.
1. Add or change a host UI string
- Add the property in the correct
Strings*split with all 10 languages. - Reference it from Compose/UI the same way neighbors do.
See Internationalization.
2. Add an editor setting that must affect the generated APK
Trace and update all of:
- Model (
WebApp/ nested config) and editor UI binding. - Export mapping (
ApkBuilder/ApkConfig/ApkConfigJsonFactory). - Shell config types (
ShellModeManager/ shell config data classes) if the runtime reads them. - Runtime use site in shell-synced code.
- Unit tests for export wiring when flags change.
See Config Field Drift.
3. Change shell runtime behavior used by every generated app
- Edit the source under
app/(shared runtime). - Confirm the file is included by
syncShellRuntimeSources. - Rebuild the shell template if you need to validate packaging.
- Keep changes surgical; shell has a low
targetSdkand a thin dependency set. - If you touch FGS / notification channel creation, fail soft via
SafeNotificationChannels.
4. Add a host-only feature (editor, market, tooling)
- Keep implementation under host-only packages (
core/apkbuilder, host screens, sample/market,core/host, …). - Do not pull host-only deps into
shell/build.gradle.kts.
5. Touch APK export or incremental rebuild
- Prefer
ApkBuildCache/ content hashes over timestamps. - Encrypted builds stay full rebuild.
- Do not use signed outputs as templates.
- If template bytes change, ensure cache keys invalidate correctly.
6. Change notifications / engines / network hardening
- Prefer existing channel abstractions (polyfill / polling / WebSocket / FCM).
- Do not add OEM push SDKs by default.
- FGS channel-create paths must tolerate OEM/channel failures.
7. Module Market / modules/
- Follow
modules/README.mdcatalog layout (registry.json+ module folders). - Runtime consumption still goes through the extension/shell paths if it ships inside generated APKs.
8. Local server runtime / download path
- Allocate ports through
PortManagerwith the configured conflict policy; implement real stop handlers. - Wire fork+exec processes into
LocalDnsBridgeProxywhen they need host DNS/proxy env. - Use
NetworkModule.downloadClientfor large dependency / engine / runtime downloads.
9. Node.js / Go export
- Node.js: ensure
injectNodeJsNativeLibsembedslibnode_bridge.so+libnode.so(16KB-aligned viaElfAligner16k) +libc++_shared.so. Node binary resolution prefersnativeLibraryDir, falls back to download cache. - Go: ensure
injectGoExecLoaderNativeLibembedslibgo_exec_loader.so. NodeServiceruns in a dedicated:nodejsOS process so V8 lifecycle is isolated from the host.
Verify commands
bash
./gradlew :shell:assembleRelease :app:syncShellTemplateApk --no-configuration-cache
./gradlew :app:compileDebugKotlin -x syncCloneHostDex --no-configuration-cache
./gradlew :app:checkConfigFieldDrift --no-configuration-cache
python3 scripts/check_config_field_drift.pyUse these when you change shell membership, export packaging, or config fields. For host-only UI/string work, targeted compile on :app is usually enough.
Focused tests often worth running after nearby edits: ApkBuildCacheTest, AdBlockerHostRuntimeTest, AdBlockExportWiringTest, PortManagerTest, BuildInputPreflightTest, GoBuildEnvironmentTest, RuntimePermissionSyncTest.
