A Flutter app works in debug but fails only in a release build on Android; which build-mode differences do you check first?
answer
- INTERNET only in debug and profile manifests
- asserts and kDebugMode code are gone
- no service extensions in release
- AOT versus JIT timing
- does it fail in profile too?
basics
~20 sCheck what differs in release: the Android template declares INTERNET only in the debug and profile manifests, asserts and kDebugMode code are compiled out, service extensions are gone, and code is AOT-compiled. Reproducing in profile narrows which difference it is.
solid answer
~40 sFirst, variant configuration: Flutter's Android template adds `android.permission.INTERNET` only to the `debug` and `profile` manifests, so network calls fail in release until it is in `src/main/AndroidManifest.xml`. Second, code that exists only in debug: side effects inside `assert` and logic behind `kDebugMode` disappear, and invalid states that asserts caught early now fail later. Third, release has no service extensions or debugger. Fourth, AOT compilation and different timing can expose races that slow debug builds hid. Reproducing in profile mode splits the problem: if profile also fails, suspect compilation or removed debug code, with DevTools available; if only release fails, diff variant configuration and `kReleaseMode` gates.
code
xml · 8 lines<!-- android/app/src/main/AndroidManifest.xml -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Required in release: the template declares it only in src/debug and src/profile. -->
<uses-permission android:name="android.permission.INTERNET"/>
<application android:label="call_app">
<!-- activities omitted -->
</application>
</manifest>go deeper
Remember to test release builds on a device, and that Android network access needs INTERNET in the main manifest.
Explain which things disappear in release, asserts, kDebugMode code, service extensions, and why that changes behaviour.
Walk through a diagnosis order that uses profile mode to split causes, diffs variant configuration and hunts side effects in asserts.
Make release-mode verification part of the pipeline and set rules that keep build mode from changing app behaviour.
## Why "works in debug" proves less than it seems A release build is not the debug build with a faster engine. It is **compiled differently** (ahead of time), **runs different code** (assertions and debug-gated blocks removed), exposes **no service extensions**, and on Android may even be packaged from **different manifest sources**. A bug that appears only in release usually comes from one of those differences, so check them in a fixed order before blaming the framework. ## 1. Platform configuration that differs by build variant The Flutter app template on Android ships **three manifests**: `src/main`, `src/debug` and `src/profile`. The debug and profile manifests contain: ```xml <uses-permission android:name="android.permission.INTERNET"/> ``` with a comment explaining the Flutter tool needs it to talk to the running app for breakpoints and hot reload. The **main** manifest does not. So an app that calls a network API works in debug and profile and fails in release until `INTERNET` is added to `src/main/AndroidManifest.xml`. This is the most common release-only failure in new Flutter apps. The same idea applies to anything configured per variant: signing, a flavor's settings, or a plugin configured only for debug. ## 2. Code that only runs in debug - **Side effects inside `assert`.** Assertions are not evaluated in profile or release, so `assert(initialize())` or `assert(_list.remove(x))` silently stops doing its work. - **Logic behind `kDebugMode`.** A fallback, a seeded user, or a relaxed validation behind `if (kDebugMode)` is gone in release. - **Behaviour that relied on an assertion failing.** In debug an invalid state throws early with a clear message; in release the same state runs on and fails later, somewhere else. ## 3. Tooling that is absent Release builds register **no service extensions** and allow no debugger. Anything that talked to the VM service, or code that assumed DevTools-driven toggles, will not work. Diagnosis has to rely on logs and crash reports instead. ## 4. Compilation differences - Debug runs JIT-compiled code; release is AOT-compiled and much faster. Code that depends on timing that debug's slowness masked, such as a race between two futures or a listener registered "just in time", can behave differently. - Web release builds use `dart2js` with minification and tree shaking, while debug uses the development compiler, so JavaScript interop mistakes can surface only in release. - Release-specific build options such as obfuscation and split debug info change stack traces and symbol names; their mechanics belong to the release tooling. ## A diagnosis order 1. **Reproduce in profile mode.** If it also fails in profile, the cause is AOT compilation or removed asserts and `kDebugMode` code, and you get DevTools to help. If profile works but release fails, focus on the differences between those two: variant-specific configuration such as the manifests, and any `kReleaseMode` gates. 2. **Diff the variant configuration**: manifests, entitlements, flavor settings. 3. **Search for side effects in asserts** and for business logic behind `kDebugMode` or `kReleaseMode`. 4. **Read the release logs and the crash report** with symbolized stack traces. 5. **Add a release smoke test** to the pipeline so this class of bug is caught before a store build. ## Prevention | Habit | Catches | |---|---| | Put permissions in `src/main`, never only in debug | Release-only network or hardware failures | | No side effects in `assert` | Work that vanishes in release | | Build mode gates only tooling, never behaviour | Mode-dependent logic | | Run a release build on a device before each release candidate | Everything above, early |
- Why does the Flutter template put INTERNET in the debug and profile manifests at all?The template's comment says the Flutter tool needs it during development to communicate with the running app, for breakpoints, hot reload and similar tooling. Release builds have none of that tooling, so the template leaves the decision to the app's main manifest.
- The bug reproduces in profile mode as well as release. What does that tell you?Profile and release share AOT compilation and both drop assertions and `kDebugMode` code, so the cause is likely one of those, not release-only configuration. Profile also lets DevTools connect, so debug it there.
saying these in an interview costs you the question
- If it works in debug, a release failure must be a Flutter engine bug.
- The debug manifest's permissions are merged into release builds.
- Asserts still evaluate their expressions in release, they just do not throw.
- Profile and release behave identically in every respect, so profile cannot help.
- Release builds still expose service extensions for DevTools.