Dev / Staging / Prod environments, selectable per build and switchable at
runtime (a current selection plus a next pending one, restart to apply). Two
cooperating halves.
Which environment a build defaults to is chosen by the APP_ENV build variable,
baked into app.config.ts's extra and read at runtime
through expo-constants — set at build time:
APP_ENV=staging npx expo prebuild # use cross-env on Windows, or a CI variablegetBuildDefaultEnvironment() is the
single place this value is read; it falls back to development if unset.
EnvironmentService exposes
the active environment and its config, and persists a runtime override via
KeyValueStore (MMKV). Features consume this service, never app.config.ts
directly (convention).
const { environment } = useServices();
const { apiBaseUrl } = environment.getConfig(); // per-env valuesPer-environment values live in a typed map,
environments.ts — type-safe
rather than .env.* files:
| Value | development | staging | production |
|---|---|---|---|
logging.console |
on | off | off |
logging.file |
on | on | on |
logging.minimumLevel |
debug | info | warn |
diagnosticsEnabled |
on | on | off |
crashReportingEnabled |
on | on | off |
remoteConfig.fetchIntervalMinutes |
1 | 1 | 720 |
crashReportingEnabled gates Bugsee to internal builds — off in production, which
is billed per user (CrashReporting.md).
remoteConfig.fetchIntervalMinutes tunes the Firebase fetch cadence
(FirebaseRemoteConfig.md). (apiBaseUrl and
appStoreUrl are also per-env; the sample points apiBaseUrl at the public Dad
Jokes API in every environment, and appStoreUrl at placeholder store URLs.)
setEnvironment(env)persists the override and pushes it ontopending$. The active environment and the wired services graph do not change mid-session.- The composition root reads the persisted override once at startup and wires the whole graph (HTTP base URL, logging levels, …) from that config.
- Applying a switch therefore requires an app restart. The diagnostics
environment picker shows a "restart to apply" banner whenever
pending$differs from the current environment. reset()deletes the override; the next launch reverts to the build default.
Live re-wiring on switch is deliberately avoided — applying a switch defers to a restart because the environment feeds construction-time dependencies.
DefaultEnvironmentService takes a KeyValueStore and the build default by
constructor, so Tier-1 tests drive it with an InMemoryKeyValueStore and an
explicit default — no expo-constants, no native modules
(test).
MockEnvironmentService is a no-persistence fake for consumers that just need a
fixed environment.