Repository navigation
[pigeon] Empty generated Dart classes aren't serializable #160501
Copy link
Copy link
Closed
Yobari-Timeliners/packages
#2Closed
Copy link
Labels
P2Important issues not at the top of the work listImportant issues not at the top of the work listfound in release: 3.27Found to occur in 3.27Found to occur in 3.27found in release: 3.28Found to occur in 3.28Found to occur in 3.28has reproducible stepsThe issue has been confirmed reproducible and is ready to work onThe issue has been confirmed reproducible and is ready to work onp: pigeonrelated to pigeon messaging codegen toolrelated to pigeon messaging codegen toolpackageflutter/packages repository. See also p: labels.flutter/packages repository. See also p: labels.r: fixedIssue is closed as already fixed in a newer versionIssue is closed as already fixed in a newer versionteam-ecosystemOwned by Ecosystem teamOwned by Ecosystem teamtriaged-ecosystemTriaged by Ecosystem teamTriaged by Ecosystem team
Description
Steps to reproduce
-
create
pigeon/messages.dart: -
Define sealed class
A -
Define
Bwhich extendsAwith some fields -
Define
Cwhich extendsAwithout any fields -
run
flutter pub run pigeon --input ./pigeon/messages.dart -
get generated file
src/messages.g.dart
Expected results
C.decode/C.encode is generated and we got no errors
Actual results
C.decode/C.encode was not generated, however B.decode/B.encode was.
// Autogenerated from Pigeon (v22.7.0), do not edit directly.
// See also: https://pub.dev/packages/pigeon
// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers
import 'dart:async';
import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List;
import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer;
import 'package:flutter/services.dart';
PlatformException _createConnectionError(String channelName) {
return PlatformException(
code: 'channel-error',
message: 'Unable to establish connection on channel: "$channelName".',
);
}
sealed class A {
}
class B extends A {
B({
this.number,
this.message,
});
int? number;
String? message;
Object encode() {
return <Object?>[
number,
message,
];
}
static B decode(Object result) {
result as List<Object?>;
return B(
number: result[0] as int?,
message: result[1] as String?,
);
}
}
class C extends A { <----- no decode/encode
}
class _PigeonCodec extends StandardMessageCodec {
const _PigeonCodec();
@override
void writeValue(WriteBuffer buffer, Object? value) {
if (value is int) {
buffer.putUint8(4);
buffer.putInt64(value);
} else if (value is B) {
buffer.putUint8(129);
writeValue(buffer, value.encode());
} else if (value is C) {
buffer.putUint8(130);
writeValue(buffer, value.encode()); <----- not generated, error
} else {
super.writeValue(buffer, value);
}
}
@override
Object? readValueOfType(int type, ReadBuffer buffer) {
switch (type) {
case 129:
return B.decode(readValue(buffer)!);
case 130:
return C.decode(readValue(buffer)!); <----- not generated, error
default:
return super.readValueOfType(type, buffer);
}
}
}
class SomeHostApi {
/// Constructor for [SomeHostApi]. The [binaryMessenger] named argument is
/// available for dependency injection. If it is left null, the default
/// BinaryMessenger will be used which routes to the host platform.
SomeHostApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''})
: pigeonVar_binaryMessenger = binaryMessenger,
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
final BinaryMessenger? pigeonVar_binaryMessenger;
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
final String pigeonVar_messageChannelSuffix;
Future<C> getC(C c) async {
final String pigeonVar_channelName = 'dev.flutter.pigeon.pigeon_sealed_plugin_example.SomeHostApi.getC$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final List<Object?>? pigeonVar_replyList =
await pigeonVar_channel.send(<Object?>[c]) as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else if (pigeonVar_replyList[0] == null) {
throw PlatformException(
code: 'null-error',
message: 'Host platform returned null value for non-null return value.',
);
} else {
return (pigeonVar_replyList[0] as C?)!;
}
}
Future<A> getA(A a) async {
final String pigeonVar_channelName = 'dev.flutter.pigeon.pigeon_sealed_plugin_example.SomeHostApi.getA$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final List<Object?>? pigeonVar_replyList =
await pigeonVar_channel.send(<Object?>[a]) as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else if (pigeonVar_replyList[0] == null) {
throw PlatformException(
code: 'null-error',
message: 'Host platform returned null value for non-null return value.',
);
} else {
return (pigeonVar_replyList[0] as A?)!;
}
}
}
class SomeFlutterApi {
/// Constructor for [SomeFlutterApi]. The [binaryMessenger] named argument is
/// available for dependency injection. If it is left null, the default
/// BinaryMessenger will be used which routes to the host platform.
SomeFlutterApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''})
: pigeonVar_binaryMessenger = binaryMessenger,
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
final BinaryMessenger? pigeonVar_binaryMessenger;
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
final String pigeonVar_messageChannelSuffix;
Future<C> getC(C c) async {
final String pigeonVar_channelName = 'dev.flutter.pigeon.pigeon_sealed_plugin_example.SomeFlutterApi.getC$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final List<Object?>? pigeonVar_replyList =
await pigeonVar_channel.send(<Object?>[c]) as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else if (pigeonVar_replyList[0] == null) {
throw PlatformException(
code: 'null-error',
message: 'Host platform returned null value for non-null return value.',
);
} else {
return (pigeonVar_replyList[0] as C?)!;
}
}
Future<A> getA(A a) async {
final String pigeonVar_channelName = 'dev.flutter.pigeon.pigeon_sealed_plugin_example.SomeFlutterApi.getA$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final List<Object?>? pigeonVar_replyList =
await pigeonVar_channel.send(<Object?>[a]) as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else if (pigeonVar_replyList[0] == null) {
throw PlatformException(
code: 'null-error',
message: 'Host platform returned null value for non-null return value.',
);
} else {
return (pigeonVar_replyList[0] as A?)!;
}
}
}Code sample
Code sample
import 'package:pigeon/pigeon.dart';
// #docregion config
@ConfigurePigeon(
PigeonOptions(
dartOut: 'lib/src/messages.g.dart',
dartOptions: DartOptions(),
kotlinOut:
'android/src/main/kotlin/com/pigeonsealedexample/pigeon_sealed_plugin/Messages.g.kt',
kotlinOptions:
KotlinOptions(package: 'com.pigeonsealedexample.somepackage'),
swiftOut: 'ios/Classes/Messages.g.swift',
swiftOptions: SwiftOptions(),
dartPackageName: 'pigeon_sealed_plugin_example',
),
)
sealed class A {
A();
}
class B extends A {
final int? number;
final String? message;
B({
required this.number,
this.message,
});
}
class C extends A {
C();
}
@HostApi()
abstract class SomeHostApi {
C getC(C c);
A getA(A a);
}
@HostApi()
abstract class SomeFlutterApi {
C getC(C c);
A getA(A a);
}
Screenshots or Video
Screenshots / Video demonstration
[Upload media here]
Logs
Logs
Deprecated. Use `dart run` instead.
Building package executable...
Built pigeon:pigeon.Flutter Doctor output
Doctor output
Doctor summary (to see all details, run flutter doctor -v):
[!] Flutter (Channel master, 3.26.0-1.0.pre.168, on macOS 14.5 23F79 darwin-arm64, locale en-RU)
! Warning: `dart` on your path resolves to /opt/homebrew/Cellar/dart/3.5.2/libexec/bin/dart, which is not inside your current Flutter SDK checkout at /Users/feduke-nukem/Desktop/git-projects/flutter/flutter. Consider adding
/Users/feduke-nukem/Desktop/git-projects/flutter/flutter/bin to the front of your path.
! Upstream repository https://github.com/feduke-nukem/flutter is not a standard remote.
Set environment variable "FLUTTER_GIT_URL" to https://github.com/feduke-nukem/flutter to dismiss this error.
[!] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
! Some Android licenses not accepted. To resolve this, run: flutter doctor --android-licenses
[✓] Xcode - develop for iOS and macOS (Xcode 15.4)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2023.3)
[✓] VS Code (version 1.96.0)
[✓] Connected device (6 available)
! Error: Browsing on the local area network for iPhone (Анна). Ensure the device is unlocked and attached with a cable or associated with the same local area network as this Mac.
The device must be opted into Developer Mode to connect wirelessly. (code -27)
[✓] Network resourcesReactions are currently unavailable
Metadata
Metadata
Assignees
Labels
P2Important issues not at the top of the work listImportant issues not at the top of the work listfound in release: 3.27Found to occur in 3.27Found to occur in 3.27found in release: 3.28Found to occur in 3.28Found to occur in 3.28has reproducible stepsThe issue has been confirmed reproducible and is ready to work onThe issue has been confirmed reproducible and is ready to work onp: pigeonrelated to pigeon messaging codegen toolrelated to pigeon messaging codegen toolpackageflutter/packages repository. See also p: labels.flutter/packages repository. See also p: labels.r: fixedIssue is closed as already fixed in a newer versionIssue is closed as already fixed in a newer versionteam-ecosystemOwned by Ecosystem teamOwned by Ecosystem teamtriaged-ecosystemTriaged by Ecosystem teamTriaged by Ecosystem team
Activity
feduke-nukem commentedon Dec 18, 2024
feduke-nukem commentedon Dec 18, 2024
Relates to #153995
stuartmorgan-g commentedon Dec 18, 2024
Please do not ping individual people when filling issues; we have a triage process.
feduke-nukem commentedon Dec 18, 2024
darshankawar commentedon Dec 19, 2024
I was able to replicate the reported behavior, wherein the generated
messages.g.dartfile shows below:tarrinneal commentedon Dec 19, 2024
This is actually a result of the class having no fields.
Just for my understanding and to guide future work, what is the use case of having the empty class?
feduke-nukem commentedon Dec 20, 2024
Well, first of all, Kotlin and Swift do not have such problem:
Secondly, the typical use case for sealed classes are events/states so let's image we are implementing custom video player. I would definitely have (especially with
EventChannelsupport) something like:As you can see - I don't need properties/fields for all of these classes.
In my opinion, described behaviour in this issue is unexpected and should not limit user for specific implementations. Even if I want all my classes to have no fields - it should work as it works for Kotlin/Swift.
If it is expected behaviour - Pigeon should throw exception with verbose description of a reason why user shouldn't generate empty classes (can't imagine such reason)
[-][pigeon] No encode/decode generated dart class (22.7.0 version, sealed class)[/-][+][pigeon] Empty generated Dart classes aren't serializable[/+]github-actions commentedon Feb 14, 2025
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.