Flutter has made it genuinely easier to ship apps across iOS, Android, and the web from a single codebase. But easier doesn’t mean effortless. Even experienced teams run into problems that quietly drag down performance, frustrate users, and inflate budgets.
At FBIP, we’ve built and maintained Flutter apps across industries, and we’ve seen the same failure patterns repeat. This post breaks down the most common Flutter app failures we’ve encountered, what caused them, and exactly how we fixed them. If you’re building with Flutter or thinking about it, this is the honest version of that conversation.
1. Jank and Dropped Frames: The Most Common Flutter App Failure
Jank that choppy, stuttery scrolling is probably the most frequent complaint we hear from clients who come to us after a bad experience with another team. Flutter targets 60fps (or 120fps on supported devices), and when you drop frames, users notice immediately.
What causes it:
The most common culprit is running heavy work on the main UI thread. This includes synchronous HTTP calls, large JSON decoding, image processing, or even poorly structured widget trees with excessive rebuilds.
How we fixed it:
- Move work off the main thread. Flutter’s Isolate API lets you spin up a separate thread for CPU-heavy tasks. For JSON parsing in particular, the compute() function is a clean, readable way to push that work to a background isolate.
- Use const constructors wherever possible. If a widget doesn’t change, marking it const tells Flutter to skip rebuilding it entirely. This is a small change with a real impact at scale.
- Audit widget rebuilds with Flutter DevTools. The Widget Rebuild Stats tool shows exactly which widgets are rebuilding on every frame. We typically find at least two or three widgets rebuilding unnecessarily in apps that come to us with performance complaints.
- Lazy-load lists. ListView.builder only builds widgets that are actually on screen. If you’re using a plain ListView with hundreds of children, that’s a straightforward fix.
2. State Management Chaos
This one shows up in apps that started simple and grew fast. What began as a clean setState() call turns into a tangled mess of callbacks, global variables, and bugs that appear only in specific sequences of user actions.
What causes it:
No architectural plan from the start. Teams reach for setState() for everything, which works fine until your app has five screens and shared data between them.
How we fixed it:
There’s no single right answer here it depends on app size and team preference but the pattern that’s worked best for us is Bloc/Cubit for medium-to-large apps and Riverpod for apps where testability and composability matter most.
Here’s the practical checklist we use when inheriting a broken state management setup:
- Map out all the state that needs to be shared across screens.
- Identify which state is truly local (keep it local with setState) versus shared (move it up).
- Introduce a state management solution one screen at a time, not all at once.
- Write tests for your state logic before refactoring UI code.
Trying to rip out state management all at once in a live app is a recipe for new bugs. Incremental migration is slower but far safer.
3. Flutter App Crashes on Specific Android or iOS Versions
Cross-platform doesn’t mean zero platform-specific bugs. We regularly see apps that work perfectly in the emulator and on the developer’s device, then crash for users on older Android versions or specific iOS configurations.
What causes it:
- Using platform APIs or plugins that aren’t backward-compatible.
- Missing permissions declarations in AndroidManifest.xml or Info.plist.
- Plugin versions that haven’t been updated to match Flutter’s latest SDK changes.
- Null safety migration done incompletely code that compiles but throws at runtime.
How we fixed it:
- Set your minSdkVersion deliberately. Know your audience. If 20% of your users are on Android 8, don’t build features that only work on Android 12.
- Test on real devices, not just emulators. Firebase Test Lab gives you access to a wide range of real physical devices and is worth the cost for any serious app.
- Keep your pubspec.yaml dependencies current but controlled. We run flutter pub outdated regularly and update packages in isolation, testing after each one rather than doing a bulk update and hoping nothing breaks.
- Run dart analyze and address every warning. Warnings that seem harmless often point to runtime failures on specific platforms.
4. App Size Ballooning Out of Control
Users delete apps that are too large, especially in markets with slower connections or limited storage. We’ve seen Flutter apps come in at 80MB+ when they should be 20MB.
What causes it:
- Including assets (images, fonts, animations) that aren’t used.
- Importing entire packages when only one utility function is needed.
- Not enabling tree shaking or code splitting.
- Bundling debug symbols in release builds.
How we fixed it:
Here’s a quick checklist to reduce Flutter app size:
- Run flutter build apk –analyze-size to get a breakdown of what’s taking up space.
- Use –split-per-abi when building for Android. Instead of one fat APK that includes code for every CPU architecture, you generate separate APKs per architecture. This alone can cut app size by 30-50%.
- Compress images before adding them to the project. Tools like TinyPNG or Squoosh work well.
- Replace heavy Lottie animation files with simpler alternatives where possible, or trim unused layers from the Lottie JSON.
- For web builds, enable –dart2js-optimization=O4 and –tree-shake-icons to strip unused Material or Cupertino icons.
5. Network Failures That Break the Entire App
A surprising number of Flutter apps have zero handling for network errors. The app makes an API call, the call fails, and the screen either freezes, shows a blank white box, or crashes entirely.
What causes it:
Developers test primarily on fast, stable Wi-Fi. Edge cases like timeouts, 5xx server errors, DNS failures, and intermittent connectivity rarely show up in development.
How we fixed it:
- Never trust the network. Every API call should have a timeout, a retry mechanism, and a fallback UI state.
- Use a Result/Either pattern to represent API responses. Instead of throwing exceptions everywhere, wrap responses in a Success or Failure type. Your UI then just checks which one it received.
- Cache aggressively but thoughtfully. For content that doesn’t change often, cache the last successful response and show it while the app retries in the background. The hive or shared_preferences packages handle this well for simple data.
- Show meaningful error states. A screen that says “Something went wrong. Tap to retry.” is infinitely better than a white screen. Users will retry; they won’t wait forever.
- Test with airplane mode. It sounds obvious, but 90% of network-related bugs we’ve fixed were caught by this simple step.
6. Memory Leaks Causing Slow Degradation Over Time
This one is sneaky. The app feels fine on first launch, but after 10 minutes of use, it gets slower and eventually crashes. Users report it as “the app slows down,” which is hard to reproduce and debug without the right tools.
What causes it:
- StreamSubscription objects not being cancelled in the dispose() method of StatefulWidgets.
- AnimationControllers not being disposed.
- Listeners added to ChangeNotifier but never removed.
- Holding references to BuildContext beyond the widget’s lifecycle.
How we fixed it:
The fix is mostly discipline:
- Every StreamSubscription, AnimationController, TextEditingController, and ScrollController that gets initialized in initState() must be disposed in dispose().
- Use Flutter DevTools’ Memory tab to track memory growth over time. If memory climbs steadily while you’re navigating through the app, you have a leak.
- The flutter_lints package flags many of these issues at analysis time. It’s not a complete safety net, but it catches the obvious ones.
How FBIP Approaches Flutter App Development
When the team at FBIP takes on a Flutter project whether it’s a new build or rescuing an existing app we start with a technical audit. That means looking at architecture, dependency health, test coverage, and performance baselines before writing a single new line of code.
What we’ve found over hundreds of hours of Flutter development is that most failures are preventable. They come from moving fast without a plan, skipping testing, or not thinking ahead about how the app will grow.
The fixes described in this post aren’t advanced. They’re disciplined, methodical, and consistent. That’s what separates apps that work at scale from apps that limp along.
If your Flutter app is struggling with any of these issues, the path forward is usually clearer than it feels in the moment.
FAQs: Common Flutter App Failures
1. Why does my Flutter app lag on older Android devices even though it runs fine on newer ones?
Older Android devices have less RAM and slower CPUs, so performance issues that don’t appear on flagship devices become obvious there. Check for heavy work running on the main thread, unoptimized images, and deeply nested widget trees. Testing on a mid-range Android device during development catches most of these issues early.
2. My Flutter app crashes on iOS but not Android. What should I look for?
Start with your app’s Info.plist — missing permission descriptions are a common iOS-specific crash trigger. Also check if any plugins you’re using have iOS-specific bugs. Run the app on a physical iOS device with Xcode’s debugger attached to get the full crash log rather than relying on Flutter’s console output alone.
3. How do I reduce Flutter app size for Play Store and App Store submissions?
Build with –split-per-abi for Android to generate architecture-specific APKs. Enable tree shaking for icons and fonts. Compress all image assets before bundling them. Use flutter build apk –analyze-size to see a full breakdown before submitting.
4. What is the best state management solution for Flutter apps in 2025?
It depends on your app’s scale. Riverpod works well for most apps because it’s testable and doesn’t rely on BuildContext. Bloc suits larger teams that want strict separation between business logic and UI. Avoid using only setState for anything beyond simple, local UI state.
5. How do I fix Flutter app crashes that only happen in production but not in development?
Production crashes often involve null safety violations, missing environment variables, or API responses that differ from your mock data. Set up a crash reporting tool like Firebase Crashlytics from day one. It gives you stack traces and device information that make production-only bugs reproducible. Also check that your release build configuration matches your debug configuration for things like base URLs and API keys.





