← Back to posts Cover image for Fixing Flutter ANR Issues: Strategies for Unblocking the Main Thread

Fixing Flutter ANR Issues: Strategies for Unblocking the Main Thread

· 5 min read
Weekly Digest

The Flutter news you actually need

No spam, ever. Unsubscribe in one click.

Chris
By Chris

Is your Flutter app freezing, showing the dreaded “App isn’t responding” dialog, or just feeling unresponsive? You might be dealing with an Application Not Responding (ANR) error. A common misconception is to write these off as general performance issues—“the app is just slow.” In reality, ANRs are a specific and critical failure: your app’s main thread (also called the UI thread) has been completely blocked for too long, preventing it from processing user input or rendering frames.

The main thread in Flutter is a single-threaded event loop. It’s responsible for everything you see and interact with: drawing widgets, handling taps, running animations, and executing your build methods. If you perform a heavy, synchronous operation on this thread, the entire UI freezes until that task is complete. The operating system watches for this. If your app fails to respond to input events (like a tap) within a certain window (typically 5 seconds on Android), it triggers the ANR dialog.

The key to fixing ANRs is shifting your mindset: stop looking for generic “slowness” and start hunting for blocking operations on the main thread.

Common Culprits That Block Your UI

Let’s look at some typical offenders:

  1. Synchronous I/O Operations: Reading/writing large files, making synchronous network calls, or performing heavy database queries on the main thread.
  2. Complex Synchronous Computations: Image processing, complex list filtering/sorting, JSON parsing of large data, or any CPU-intensive task done synchronously.
  3. Dart’s compute Function Gone Wrong: While compute is the go-to for moving work to an isolate, passing excessively large arguments to or from it can cause blocking serialization/deserialization on the main thread.
  4. Poorly Managed Stream/Future Chains: Using .then or callbacks that ultimately run expensive code on the main thread because you didn’t specify where the work should happen.

Strategies and Practical Fixes

Here’s how you can identify and resolve these blockers.

1. Offload Work with compute and Isolates

For pure, heavy Dart computations, use compute. It spawns a separate isolate, keeping the main thread free.

The Problem (Blocking UI):

void _processUserData() {
  // This blocks the UI thread for a long time!
  List<ComplexUser> filteredUsers = allUsers.where((user) {
    // Simulating a complex evaluation
    return someExpensiveValidation(user.profileData);
  }).toList();
  setState(() {
    _users = filteredUsers;
  });
}

The Solution (Using compute):

// This function MUST be top-level or static to be sent to another isolate.
static List<ComplexUser> _filterUsersInBackground(List<ComplexUser> allUsers) {
  return allUsers.where((user) {
    return someExpensiveValidation(user.profileData);
  }).toList();
}

Future<void> _processUserData() async {
  // This returns immediately, the UI stays responsive.
  List<ComplexUser> filteredUsers = await compute(_filterUsersInBackground, allUsers);
  setState(() {
    _users = filteredUsers;
  });
}

2. Use Asynchronous I/O Correctly

The dart:io and dart:convert libraries provide asynchronous versions of most methods. Use them! Remember: async doesn’t automatically mean non-blocking; you must use the async APIs.

The Problem (Blocking File Read):

String _loadAppConfig() {
  // BAD: Synchronous file read.
  File configFile = File('path/to/config.json');
  return configFile.readAsStringSync(); // UI waits here.
}

The Solution (Async File Read):

Future<void> _loadAppConfig() async {
  File configFile = File('path/to/config.json');
  // GOOD: Asynchronous read. The main thread can process other events while waiting for the OS.
  String contents = await configFile.readAsString();
  // Parse the JSON AFTER it's loaded, but parsing could also be heavy...
  Map<String, dynamic> config = jsonDecode(contents);
  // For large JSON, consider moving decode to compute as well.
  _updateConfig(config);
}

3. Profile to Find the Hidden Block

Use the Dart DevTools Performance view and CPU Profiler. Record a performance session while performing the action that leads to the hang.

  • Look for long, solid bars on the main thread (UI) track.
  • Click on those bars to see the exact method that was running. You’ll often find your culprit right there—a parsing function, a file operation, or a dense loop.

4. Be Smart with State Management and Builds

While less likely to cause a full 5-second ANR, excessive work in build() methods or state change notifications can make the app feel frozen and contribute to jank.

  • Break up large widget trees with const constructors and AutomaticKeepAliveClientMixin.
  • Use ListView.builder for lists; it’s non-blocking.
  • Throttle state updates from streams if they fire too rapidly.

Key Takeaway

Fixing ANRs isn’t about micro-optimizing every line of code. It’s about auditing your app for operations that have no business being on the main thread and moving them off. Treat the main thread with respect: its primary jobs are handling input and painting frames. Anything else that takes more than a few milliseconds should be a candidate for asynchronous execution or offloading to an isolate.

Start by profiling your app during its slowest operations. The profiler will visually show you the blocking calls. Then, systematically apply the strategies above—use async/await with truly async APIs, employ compute for heavy lifting, and structure your app to keep the main thread as idle as possible, ready to respond to the user instantly.

This blog is produced with the assistance of AI by a human editor. Learn more

Related Posts

Cover image for Demystifying Dart's Reflection: When to Use Code Generation for Powerful Flutter Features

Demystifying Dart's Reflection: When to Use Code Generation for Powerful Flutter Features

Dart's lack of full runtime reflection in Flutter often frustrates developers used to languages like C#, limiting dynamic tool building. This post will clarify why Flutter restricts reflection (tree-shaking benefits), explain the `dart:mirrors` library's role, and most importantly, provide practical strategies for achieving similar powerful capabilities through compile-time code generation and annotations, with real-world examples.

Cover image for Fixing Flutter ANR Issues: Strategies for Unblocking the Main Thread

Fixing Flutter ANR Issues: Strategies for Unblocking the Main Thread

App Not Responding (ANR) errors plague many Flutter apps, often misdiagnosed as general slowness. This post will delve into identifying and resolving ANRs by focusing on common causes of main thread blocks, providing practical tools and techniques for ensuring a smooth, responsive user experience.

Cover image for Mastering CI/CD for Flutter: A Practical Guide to Fastlane and GitHub Actions

Mastering CI/CD for Flutter: A Practical Guide to Fastlane and GitHub Actions

Implementing robust Continuous Integration and Continuous Deployment (CI/CD) is essential for shipping Flutter apps efficiently, yet many developers struggle with setting up reliable pipelines for Android and iOS. This post will provide a practical guide to leveraging Fastlane and GitHub Actions to automate builds, testing, and deployments, addressing common challenges and sharing best practices for a streamlined release workflow.