Flutter Hotfixes in Production: Strategies for Instant Bug Patches on iOS and Android
The Flutter news you actually need
No spam, ever. Unsubscribe in one click.
It’s every mobile developer’s nightmare: you ship a new release, it passes review, and within hours you see a critical bug in production—maybe a crash on a specific device or OS version. On Android, you can push a fix relatively quickly. On iOS, you’re at the mercy of the App Store review, which can take a day or more. In the meantime, users are having a broken experience, and your team is scrambling.
The traditional “fix, resubmit, wait” cycle is simply unacceptable for mission-critical issues. Fortunately, as Flutter developers, we have several strategies to deliver instant (or near-instant) patches and mitigate user impact. Let’s explore the most practical approaches.
1. Server-Side Feature Flags: Your First Line of Defense
The most robust way to prevent a bug from affecting all users is to control feature availability remotely. By wrapping new features in a server-side toggle, you can disable a problematic feature instantly, without any app update.
How it works: Your app fetches a configuration (e.g., from Firebase Remote Config, your own API, or a service like LaunchDarkly) on startup or at runtime. This configuration determines whether a specific feature is enabled.
Example using Firebase Remote Config: First, wrap your feature in a conditional check:
import 'package:firebase_remote_config/firebase_remote_config.dart';
class FeatureFlagService {
static final RemoteConfig _remoteConfig = RemoteConfig.instance;
static Future<void> initialize() async {
await _remoteConfig.setConfigSettings(RemoteConfigSettings(
fetchTimeout: const Duration(seconds: 10),
minimumFetchInterval: const Duration(seconds: 30),
));
await _remoteConfig.fetchAndActivate();
}
static bool isFeatureEnabled(String featureKey) {
return _remoteConfig.getBool(featureKey);
}
}
// In your widget or service:
void loadFeature() async {
await FeatureFlagService.initialize();
if (FeatureFlagService.isFeatureEnabled('new_chat_ui')) {
// Launch the new, potentially buggy feature
Navigator.push(context, MaterialPageRoute(builder: (_) => NewChatUI()));
} else {
// Fall back to the stable, old UI
Navigator.push(context, MaterialPageRoute(builder: (_) => LegacyChatUI()));
}
}
On your Firebase Remote Config console, you can set the key new_chat_ui to false and publish changes. Within minutes, all users fetching the updated config will be routed away from the buggy screen. This gives you breathing room to develop and test a proper fix.
Common mistake: Not building a sensible fallback. Always design your flags so that disabling a feature reverts users to a stable, functional state.
2. Code Push with Shorebird: Instant Over-the-Air Updates
While server-side flags are great for toggling features, they can’t patch logic bugs inside your already-shipped Dart code. For that, you need a code push solution. In the Flutter ecosystem, Shorebird is the leading tool that allows you to push updates to your Dart code and assets directly to users’ devices, bypassing the store review process for both iOS and Android.
How it works: Shorebird creates a separate code push system for your Flutter app. You build your app with their tools, and they provide a channel to deliver patches.
Basic integration:
import 'package:shorebird_code_push/shorebird_code_push.dart';
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final _shorebirdCodePush = ShorebirdCodePush();
@override
void initState() {
super.initState();
_checkForUpdates();
}
Future<void> _checkForUpdates() async {
// Check if a new patch is available.
final isUpdateAvailable = await _shorebirdCodePush.isNewPatchAvailableForDownload();
if (isUpdateAvailable) {
// Download the new patch (it will be used on the next app restart).
await _shorebirdCodePush.downloadUpdateIfAvailable();
// In a real app, you might prompt the user to restart.
}
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Shorebird Code Push Demo')),
body: Center(
child: Text('Current patch number: ${_shorebirdCodePush.currentPatchNumber()}'),
),
),
);
}
}
To deploy a hotfix, you’d use the Shorebird CLI:
shorebird patch android
shorebird patch ios
Important Caveats:
- Apple’s Policies: While Shorebird works technically on iOS, Apple’s guidelines state that apps must not download executable code. Shorebird is designed to comply by only updating code written in Dart (which they interpret as “scripting”), but it’s crucial to understand the policy risk. Use it for critical bug fixes, not for wholesale feature changes.
- Binary Changes: Code push cannot modify native code (Swift, Kotlin, Objective-C, etc.) or plugin implementations. It’s for Dart code and assets only.
3. The Nuclear Option: Expedited App Store Review
When a bug is severe (causing crashes, data loss, or security issues) and you have no other mitigation, you can request an expedited review from Apple. This is not a tool for routine fixes but a genuine emergency channel. In the request, you must clearly explain the severity of the bug and its impact on users. If granted, reviews can happen in a matter of hours.
Putting It All Together: A Layered Strategy
The most resilient production strategy uses a combination of these tools:
- Wrap all new features in server-side flags. This is cheap insurance and allows for controlled rollouts and instant kill-switches.
- Integrate a code push solution like Shorebird for your codebase. Use it to deploy low-risk, critical bug fixes for logic errors in your Dart code.
- Reserve expedited reviews for true emergencies involving native code or when other methods aren’t viable.
By adopting this layered approach, you move from a reactive posture—hoping nothing slips through—to a proactive one where you have multiple levers to pull to protect your users’ experience the moment an issue is detected.
This blog is produced with the assistance of AI by a human editor. Learn more
Related Posts
Flutter's Hidden Gem: Building Powerful Linux Desktop Apps with Ease
Flutter is gaining traction as a robust framework for Linux desktop development, offering a smoother experience compared to traditional toolkits like GTK and Qt. This post will explore why Flutter excels for Linux apps, highlight its advantages, and provide practical insights for developers looking to leverage Flutter for their desktop projects.
Simplifying Flutter Desktop Deployment: Signing and Distribution for Windows
Deploying Flutter desktop apps on Windows can be challenging, especially regarding code signing and preventing Windows from blocking installations. This post will guide developers through the process of signing their Flutter desktop applications, exploring alternatives to the Microsoft Store like creating executable installers, and covering the necessary steps to ensure a smooth user experience.
Mastering Flutter Tooling: Streamlining SDK Management and Installation on Windows
Developers frequently voice frustrations about Flutter SDK version management and installation processes, particularly on Windows. This post will explore best practices for managing multiple Flutter versions using tools like FVM, discuss integrating Flutter with package managers like Winget for a smoother Windows setup, and offer strategies to ensure a consistent development environment across projects and teams.