fix(macOS): offload compression off the AppKit main thread - #398
Merged
Conversation
FlutterMethodChannel dispatches handle(_:result:) on the platform thread (main/UI on macOS). Decode, CGContext resize+rotate, CGImageDestination encode and disk writes all ran inline on that thread, so compressing anything nontrivial stalled the Flutter engine and froze the UI. Move the switch body onto a userInitiated global queue and marshal every FlutterResult back to main — the Android threadPool + main-Looper Handler and iOS global-queue handlers already worked this way; macOS now matches. Also close two reply-path gaps so every branch of handle(...) resolves `result`: showLog previously never called result(...) (Android returns 1, iOS returns @1), leaving the Dart future pending; and the [weak self]/nil branch on the background queue now replies with an error rather than silently swallowing the call. Ignore SwiftPM's .build/ output under macos/flutter_image_compress_macos/ so it doesn't leak into git status after `swift build` or IDE indexing.
AlexV525
enabled auto-merge (squash)
July 17, 2026 04:12
AlexV525
disabled auto-merge
July 17, 2026 04:16
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
FlutterMethodChanneldispatcheshandle(_:result:)on the platform thread — on macOS that's the AppKit main / UI thread. The plugin ran decode,CGContextresize+rotate,CGImageDestinationencode and disk writes inline on that thread, so compressing anything nontrivial stalled the Flutter engine and froze the UI (dropped frames, gesture/input hangs, animation stutter).Android and iOS already offload:
threadPool.execute { … }on a fixed 8-threadExecutorService, with results posted back viaHandler(Looper.getMainLooper())(ResultHandler.kt).ImageCompressPlugin.handleMethodCall:wraps the switch indispatch_async(dispatch_get_global_queue(0, 0), …)(ImageCompressPlugin.m).macOS now matches: compression runs on
DispatchQueue.global(qos: .userInitiated),FlutterResultis marshaled back to the main thread via a smallmainResultshim.What changed
FlutterImageCompressMacosPlugin.swift—handle(_:result:)only:compressAndGetFile,compressWithFile,compressWithList) plusdefault(FlutterMethodNotImplemented) run insideDispatchQueue.global(qos: .userInitiated).async { … }.mainResultshim — wrapsFlutterResultso every downstream callback (handleResult's error paths at lines 108/124/132,Compressor.compressToPath/compressToBytesat lines 402/423) always resolves on the main thread regardless of where it's invoked from.showLogreply gap — the previous switch case wasLogger.showLog(show: args as! Bool)with noresult(…)call, leaving the awaiting Dart future pending. Now returns1to match Android (result.success(1)) and iOS (result(@1)). Kept synchronous on the calling thread — it's a fast setter, no reason to hop.[weak self]nil branch — if the plugin is detached before the async block starts, previously theguard let self else { return }swallowed the call silently. Now replies withFlutterError(code: "plugin_detached", …)so the Dart future always resolves.Also updates
packages/flutter_image_compress_macos/macos/.gitignoreto ignore SwiftPM's.build/output that IDE indexing /swift buildleaves undermacos/flutter_image_compress_macos/.Test plan
flutter build macos --debugfrom the example — clean.flutter test integration_test/compress_test.dart -d macos— all 13 macOS-relevant tests pass (7 skipped are Android/iOS-only), including HEIC round-trip, EXIF preservation across JPEG/PNG/HEIC, rotate 90/180/270, transparent PNG, WebP interop.Notes for melos changelog
Single
fix(macOS): …commit — will land underflutter_image_compress_macos's Bug Fixes section.