Repository navigation
Description
Description
When using Pigeon's Swift FFI generator (createWithNativeInteropApi()), any Objective-C boolean (__NSCFBoolean / CFBoolean, a subclass of NSNumber) boxed inside a Swift Any or [String: Any] (such as values returned from UserDefaults.object(forKey:) or UserDefaults.persistentDomain(forName:)) is serialized across FFI to Dart as an int (1 or 0) instead of a bool (true or false).
Root Cause
In packages/pigeon/lib/src/swift/swift_generator.dart (around line 2321), the generated wrapNumber(number: Any) helper switches on number with case let value as Int: before case let value as Bool::
private func wrapNumber(number: Any) -> MessagesNumberWrapper {
switch number {
case let value as Int:
return MessagesNumberWrapper(number: NSNumber(value: value), type: 1)
case let value as Int64:
return MessagesNumberWrapper(number: NSNumber(value: value), type: 1)
case let value as Double:
return MessagesNumberWrapper(number: NSNumber(value: value), type: 2)
case let value as Float:
return MessagesNumberWrapper(number: NSNumber(value: value), type: 2)
case let value as Bool:
return MessagesNumberWrapper(number: NSNumber(value: value), type: 3)
...
When Foundation is imported in Swift, an NSNumber (including __NSCFBoolean) boxed in Any succeeds when dynamically cast with as? Int (bridging true to 1 and false to 0). Because case let value as Int: is evaluated first, any CFBoolean instance matches as Int (type: 1) and never reaches case let value as Bool: (type: 3).
(By contrast, when using standard platform channels, FlutterStandardCodec checks CFGetTypeID((__bridge CFTypeRef)value) == CFBooleanGetTypeID() before treating an NSNumber as an integer or floating-point number.)
Steps to Reproduce
Define a Pigeon @hostapi() method returning Object? or Map<String, Object> (for example, UserDefaultsApi.getValue or UserDefaultsApi.getAll in shared_preferences_foundation).
In the Swift implementation, return a boolean value retrieved from an Objective-C API such as UserDefaults.standard.object(forKey:) or UserDefaults.standard.persistentDomain(forName:).
Call the API from Dart using the FFI client (createWithNativeInteropApi()).
Observe that Dart receives 1 (int) instead of true (bool).
Expected Results
Booleans backed by CFBoolean / __NSCFBoolean inside Any should be wrapped with type: 3 (Bool) and decoded as bool in Dart, matching the behavior of FlutterStandardCodec.
Suggested Fix
Check CFGetTypeID(number as CFTypeRef) == CFBooleanGetTypeID() at the beginning of wrapNumber(number: Any) (or before checking Int / Double) in packages/pigeon/lib/src/swift/swift_generator.dart:
if CFGetTypeID(number as CFTypeRef) == CFBooleanGetTypeID(), let value = number as? Bool {
return MessagesNumberWrapper(number: NSNumber(value: value), type: 3)
}
Activity
[pigeon] Fix NSnumber edge cases and null value bug (flutter#12997)
github-actions commentedon Oct 9, 2026
This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of
flutter doctor -vand a minimal reproduction of the issue.