Flutter / Inclusive interfaces and target evidence / Lesson 18
Release configuration and observability
Keep environment selection explicit, preserve diagnostic symbol context, and publish only evidence-backed compatibility claims.
On this page
Lesson goals
- A release is a configuration and provenance decision
- Validate public configuration rather than hiding it
- Capture errors at the relevant asynchronous boundary
A release is a configuration and provenance decision
The same source can behave differently under build modes, entry points, compile-time values, native variants, assets, and signing configuration. Record these inputs with dependency locks and reviewed SDK constraints so a release can be traced to its actual artifact. A successful local debug run does not prove a signed release installs or that every target variant uses the intended endpoint. Keep development and production identities distinguishable in the authorized project workflow. The examples here describe configuration contracts only; they do not build, sign, upload, or contact a deployment service.
Validate public configuration rather than hiding it
Compile-time environment values can select a public environment label or endpoint policy, but they are embedded client configuration rather than a secret store. The first fragment accepts only two synthetic labels and fails early for an unsupported value. Failing clearly is safer than quietly sending traffic to a default production system. A production application should validate endpoint schemes and permitted hosts at the appropriate boundary and ensure the native variant matches the expected application identity. Keep privileged credentials outside distributed artifacts regardless of whether their variable names sound internal.
const environment = String.fromEnvironment('APP_ENV', defaultValue: 'lab');
String checkedEnvironment() {
if (environment != 'lab' && environment != 'staging') {
throw StateError('Unsupported APP_ENV');
}
return environment;
}
void main() { print(checkedEnvironment()); }
With no compile-time APP_ENV supplied, the program prints lab.
No endpoint is contacted. Environment values supplied through the actual build host require separate variant verification.
Capture errors at the relevant asynchronous boundary
Flutter framework errors and asynchronous errors outside framework callbacks can follow different reporting paths. The second fragment shows the conceptual setup for FlutterError.onError and PlatformDispatcher.instance.onError. Preserve the framework’s normal presentation or reviewed diagnostics and route sanitized records through the application’s reporting adapter. Do not throw recursively from the handler or send entire private payloads as context. Returning true from the dispatcher handler indicates handling according to that API; decide that policy deliberately so failures are not swallowed with no useful record. This fragment does not configure an external telemetry account.
import 'dart:ui';
import 'package:flutter/foundation.dart';
void installErrorHandlers(void Function(Object, StackTrace?) record) {
FlutterError.onError = (details) {
FlutterError.presentError(details);
record(details.exception, details.stack);
};
PlatformDispatcher.instance.onError = (error, stack) {
record(error, stack);
return true;
};
}
Configured callbacks forward framework and dispatcher errors to the supplied local adapter.
The adapter must implement reviewed redaction and failure handling. No telemetry event was sent and no runtime handler was installed here.
Keep symbols and version identity usable
A crash record without matching artifact identity can be difficult to interpret. Retain the release version, build identifier, target, and symbol information according to the platform’s documented workflow. When obfuscation or split debug information is used, keep the matching symbol artifacts under controlled retention; an unrelated symbol file can produce misleading diagnosis. Record safe operation categories and coarse context needed to reproduce the issue. Avoid collecting more user content simply because a logging SDK permits it. Observability should support a concrete debugging question with a reviewed data lifecycle.
Define release checks by target and feature
A useful release matrix names the supported target variants and the critical flows that cross their boundaries: launch, navigation, localized forms, offline recovery, permissions, and native capabilities. Keep pure tests and widget tests fast, then add target integration checks where the feature depends on native or browser behavior. Review accessibility and large-text paths as ordinary release requirements. Do not infer all-target support from one emulator or all-browser support from a single host. A documented limitation is more useful than a vague claim that a project is production ready.
Publish a validation statement people can trust
A validation statement should name what changed, what was executed, the relevant versions and target, and material untested boundaries. For this course, JSON structure and bilingual alignment are checked independently, while Dart, Flutter widget, native channel, device, and release-build examples remain unexecuted. In your own project, attach actual test results and artifact provenance instead of copying these expectations as evidence. When a release fails, preserve the first relevant error and exact variant before trying broad cleanup or dependency upgrades that can erase the original reproduction.
Common errors and fixes
A build uses the wrong environment silently
- Why it happens
- Configuration fallback hid an unsupported or missing variant value.
- How to fix it
- Validate permitted values and align native and Dart variant identity.
- Prevent it next time
- Check each authorized artifact’s public configuration.
A crash cannot be symbolicated reliably
- Why it happens
- Matching symbol and artifact identity were not retained.
- How to fix it
- Use the exact release’s symbol information and documented platform workflow.
- Prevent it next time
- Retain versioned provenance with controlled symbol artifacts.
An error handler hides failures without a record
- Why it happens
- The handler claims success but its reporting path is ineffective or recursive.
- How to fix it
- Keep handling local and robust, with reviewed redaction and fallback diagnostics.
- Prevent it next time
- Test the adapter with synthetic errors before integration.
Check your understanding
1. Can APP_ENV contain a privileged service key safely because it is compiled in?
Show worked solution
No. Compiled client configuration remains part of a distributed artifact and is not a secret boundary. Keep privileged operations behind an authorized server service and treat build-time configuration as public selection data with explicit validation.
2. What should a truthful report say after only JSON and translation validation?
Show worked solution
It should report content/schema and bilingual alignment checks as completed, and explicitly leave SDK compilation, widget execution, native integration, and device release behavior unverified. Predicted example outputs can teach a contract but cannot replace recorded execution evidence.
Key takeaways
- A release is a configuration and provenance decision
- Publish a validation statement people can trust