Question #301MediumTools & DevOps

Application runs in debug mode but crashes in release mode why ?

Answer

Overview

A very common Flutter issue: the app works perfectly in debug mode but crashes in release mode. This happens because debug and release builds are compiled and optimized differently.


Key Differences: Debug vs Release

FeatureDebugRelease
CompilationJIT (Just-In-Time)AOT (Ahead-Of-Time)
OptimizationNoneAggressive (R8/ProGuard)
Assertions✅ Enabled❌ Disabled
Observatory✅ Enabled❌ Disabled
Code shrinking❌ No✅ Yes (ProGuard/R8)
Obfuscation❌ No✅ Optional

Common Causes & Fixes

1. ProGuard/R8 Shrinking removes needed classes

text
Error: MissingPluginException / ClassNotFoundException in release
proguard
# android/app/proguard-rules.pro
# Keep your model classes
-keep class com.example.myapp.models.** { *; }

# Keep Flutter-specific classes
-keep class io.flutter.** { *; }
-keep class io.flutter.plugins.** { *; }

# If using Gson
-keepattributes Signature
-keepattributes *Annotation*
-keep class com.google.gson.** { *; }

# If using Retrofit
-keepattributes *Annotation*
-keep class retrofit2.** { *; }

2. dart:mirrors / Reflection not available in AOT

dart
// ❌ dart:mirrors is NOT supported in Flutter release builds
import 'dart:mirrors'; // Causes crash in release!

// ✅ Use code generation instead (json_serializable, freezed)
()
class User {
  final String name;
  User({required this.name});
  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
}

3. assert() statements that crash

dart
// assert() runs in debug but is REMOVED in release
// If your logic depends on assert side-effects, extract it:

// ❌ Bad — logic inside assert
assert((value = computeValue()) != null);

// ✅ Good — assert only for validation
final value = computeValue();
assert(value != null, 'Value must not be null');

4. Missing permissions in release manifest

xml
<!-- android/app/src/main/AndroidManifest.xml -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />

5. Debug vs Release API endpoints

dart
// dart-define lets you switch environments
const apiUrl = String.fromEnvironment(
  'API_URL',
  defaultValue: 'https://api.prod.example.com
);

// Build with:
// flutter build apk --dart-define=API_URL=https://staging.example.com

Debugging Release Crashes

bash
# Test release build locally first
flutter run --release

# Check release logs on Android
adb logcat | grep flutter

# Symbolicate crash stack traces (if obfuscated)
flutter symbolize --input=crash_log.txt --debug-info=build/app.android-arm64.symbols

Release Mode Checklist

  • Test with
    text
    flutter run --release
    before submitting
  • Check ProGuard rules for all third-party libraries
  • Remove all
    text
    dart:mirrors
    usage
  • Verify all permissions are in
    text
    AndroidManifest.xml
  • Check
    text
    Info.plist
    for iOS permission descriptions
  • Test on a physical device, not just emulator
  • Verify API endpoints are production URLs
  • Check for hardcoded debug credentials

Rule: Always test

text
flutter run --release
on a real device before publishing. Most release crashes are caught immediately this way.