[pigeon] Fix java crash for nullable nested type (#1192)
diff --git a/packages/pigeon/CHANGELOG.md b/packages/pigeon/CHANGELOG.md
index 2e8c814..d3afa4f 100644
--- a/packages/pigeon/CHANGELOG.md
+++ b/packages/pigeon/CHANGELOG.md
@@ -1,4 +1,8 @@
-## 2.0.1
+## 2.0.2
+
+* Fixes Java crash for nullable nested type.
+
+* ## 2.0.1
* Adds support for TaskQueues for serial background execution.
diff --git a/packages/pigeon/bin/run_tests.dart b/packages/pigeon/bin/run_tests.dart
index a506b78..a65d02e 100644
--- a/packages/pigeon/bin/run_tests.dart
+++ b/packages/pigeon/bin/run_tests.dart
@@ -144,6 +144,8 @@
'$flutterUnitTestsPath/lib/multiple_arity.gen.dart',
'pigeons/non_null_fields.dart':
'$flutterUnitTestsPath/lib/non_null_fields.gen.dart',
+ 'pigeons/null_fields.dart':
+ '$flutterUnitTestsPath/lib/null_fields.gen.dart',
'pigeons/nullable_returns.dart':
'$flutterUnitTestsPath/lib/nullable_returns.gen.dart',
});
diff --git a/packages/pigeon/lib/generator_tools.dart b/packages/pigeon/lib/generator_tools.dart
index 107f035..15272f7 100644
--- a/packages/pigeon/lib/generator_tools.dart
+++ b/packages/pigeon/lib/generator_tools.dart
@@ -8,7 +8,7 @@
import 'ast.dart';
/// The current version of pigeon. This must match the version in pubspec.yaml.
-const String pigeonVersion = '2.0.1';
+const String pigeonVersion = '2.0.2';
/// Read all the content from [stdin] to a String.
String readStdin() {
diff --git a/packages/pigeon/lib/java_generator.dart b/packages/pigeon/lib/java_generator.dart
index efa210c..8d0b15a 100644
--- a/packages/pigeon/lib/java_generator.dart
+++ b/packages/pigeon/lib/java_generator.dart
@@ -435,7 +435,7 @@
return '($varName == null) ? null : (($varName instanceof Integer) ? (Integer)$varName : (${hostDatatype.datatype})$varName)';
} else if (!hostDatatype.isBuiltin &&
classes.map((Class x) => x.name).contains(field.type.baseName)) {
- return '${hostDatatype.datatype}.fromMap((Map)$varName)';
+ return '($varName == null) ? null : ${hostDatatype.datatype}.fromMap((Map)$varName)';
} else {
return '(${hostDatatype.datatype})$varName';
}
diff --git a/packages/pigeon/pigeons/null_fields.dart b/packages/pigeon/pigeons/null_fields.dart
new file mode 100644
index 0000000..574b4f6
--- /dev/null
+++ b/packages/pigeon/pigeons/null_fields.dart
@@ -0,0 +1,43 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file is an example pigeon file that is used in compilation, unit, mock
+// handler, and e2e tests.
+
+import 'package:pigeon/pigeon.dart';
+
+class NullFieldsSearchRequest {
+ NullFieldsSearchRequest(this.query);
+ String? query;
+}
+
+enum NullFieldsSearchReplyType {
+ success,
+ failure,
+}
+
+class NullFieldsSearchReply {
+ NullFieldsSearchReply(
+ this.result,
+ this.error,
+ this.indices,
+ this.request,
+ this.type,
+ );
+ String? result;
+ String? error;
+ List<int?>? indices;
+ NullFieldsSearchRequest? request;
+ NullFieldsSearchReplyType? type;
+}
+
+@HostApi()
+abstract class NullFieldsHostApi {
+ NullFieldsSearchReply search(NullFieldsSearchRequest nested);
+}
+
+@FlutterApi()
+abstract class NullFieldsFlutterApi {
+ NullFieldsSearchReply search(NullFieldsSearchRequest request);
+}
diff --git a/packages/pigeon/platform_tests/android_unit_tests/android/app/src/test/java/com/example/android_unit_tests/NullFieldsTest.java b/packages/pigeon/platform_tests/android_unit_tests/android/app/src/test/java/com/example/android_unit_tests/NullFieldsTest.java
new file mode 100644
index 0000000..1b88470
--- /dev/null
+++ b/packages/pigeon/platform_tests/android_unit_tests/android/app/src/test/java/com/example/android_unit_tests/NullFieldsTest.java
@@ -0,0 +1,171 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package com.example.android_unit_tests;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.Test;
+
+public class NullFieldsTest {
+ @Test
+ public void builderWithValues() {
+ NullFields.NullFieldsSearchRequest request =
+ new NullFields.NullFieldsSearchRequest.Builder().setQuery("hello").build();
+
+ NullFields.NullFieldsSearchReply reply =
+ new NullFields.NullFieldsSearchReply.Builder()
+ .setResult("result")
+ .setError("error")
+ .setIndices(Arrays.asList(1L, 2L, 3L))
+ .setRequest(request)
+ .setType(NullFields.NullFieldsSearchReplyType.success)
+ .build();
+
+ assertEquals(reply.getResult(), "result");
+ assertEquals(reply.getError(), "error");
+ assertEquals(reply.getIndices(), Arrays.asList(1L, 2L, 3L));
+ assertEquals(reply.getRequest().getQuery(), "hello");
+ assertEquals(reply.getType(), NullFields.NullFieldsSearchReplyType.success);
+ }
+
+ @Test
+ public void builderRequestWithNulls() {
+ NullFields.NullFieldsSearchRequest request =
+ new NullFields.NullFieldsSearchRequest.Builder().setQuery(null).build();
+ }
+
+ @Test
+ public void builderReplyWithNulls() {
+ NullFields.NullFieldsSearchReply reply =
+ new NullFields.NullFieldsSearchReply.Builder()
+ .setResult(null)
+ .setError(null)
+ .setIndices(null)
+ .setRequest(null)
+ .setType(null)
+ .build();
+
+ assertNull(reply.getResult());
+ assertNull(reply.getError());
+ assertNull(reply.getIndices());
+ assertNull(reply.getRequest());
+ assertNull(reply.getType());
+ }
+
+ @Test
+ public void requestFromMapWithValues() {
+ HashMap<String, Object> map = new HashMap<>();
+ map.put("query", "hello");
+
+ NullFields.NullFieldsSearchRequest request = NullFields.NullFieldsSearchRequest.fromMap(map);
+ assertEquals(request.getQuery(), "hello");
+ }
+
+ @Test
+ public void requestFromMapWithNulls() {
+ HashMap<String, Object> map = new HashMap<>();
+ map.put("query", null);
+
+ NullFields.NullFieldsSearchRequest request = NullFields.NullFieldsSearchRequest.fromMap(map);
+ assertNull(request.getQuery());
+ }
+
+ @Test
+ public void replyFromMapWithValues() {
+ HashMap<String, Object> requestMap = new HashMap<>();
+ requestMap.put("query", "hello");
+
+ HashMap<String, Object> map = new HashMap<>();
+ map.put("result", "result");
+ map.put("error", "error");
+ map.put("indices", Arrays.asList(1L, 2L, 3L));
+ map.put("request", requestMap);
+ map.put("type", NullFields.NullFieldsSearchReplyType.success.ordinal());
+
+ NullFields.NullFieldsSearchReply reply = NullFields.NullFieldsSearchReply.fromMap(map);
+ assertEquals(reply.getResult(), "result");
+ assertEquals(reply.getError(), "error");
+ assertEquals(reply.getIndices(), Arrays.asList(1L, 2L, 3L));
+ assertEquals(reply.getRequest().getQuery(), "hello");
+ assertEquals(reply.getType(), NullFields.NullFieldsSearchReplyType.success);
+ }
+
+ @Test
+ public void replyFromMapWithNulls() {
+ HashMap<String, Object> map = new HashMap<>();
+ map.put("result", null);
+ map.put("error", null);
+ map.put("indices", null);
+ map.put("request", null);
+ map.put("type", null);
+
+ NullFields.NullFieldsSearchReply reply = NullFields.NullFieldsSearchReply.fromMap(map);
+ assertNull(reply.getResult());
+ assertNull(reply.getError());
+ assertNull(reply.getIndices());
+ assertNull(reply.getRequest());
+ assertNull(reply.getType());
+ }
+
+ @Test
+ public void requestToMapWithValues() {
+ NullFields.NullFieldsSearchRequest request =
+ new NullFields.NullFieldsSearchRequest.Builder().setQuery("hello").build();
+
+ Map<String, Object> map = request.toMap();
+ assertEquals(map.get("query"), "hello");
+ }
+
+ @Test
+ public void requestToMapWithNulls() {
+ NullFields.NullFieldsSearchRequest request =
+ new NullFields.NullFieldsSearchRequest.Builder().setQuery(null).build();
+
+ Map<String, Object> map = request.toMap();
+ assertNull(map.get("query"));
+ }
+
+ @Test
+ public void replyToMapWithValues() {
+ NullFields.NullFieldsSearchReply reply =
+ new NullFields.NullFieldsSearchReply.Builder()
+ .setResult("result")
+ .setError("error")
+ .setIndices(Arrays.asList(1L, 2L, 3L))
+ .setRequest(new NullFields.NullFieldsSearchRequest.Builder().setQuery("hello").build())
+ .setType(NullFields.NullFieldsSearchReplyType.success)
+ .build();
+
+ Map<String, Object> map = reply.toMap();
+ assertEquals(map.get("result"), "result");
+ assertEquals(map.get("error"), "error");
+ assertEquals(map.get("indices"), Arrays.asList(1L, 2L, 3L));
+ assertEquals(map.get("request"), reply.getRequest().toMap());
+ assertEquals(map.get("type"), NullFields.NullFieldsSearchReplyType.success.ordinal());
+ }
+
+ @Test
+ public void replyToMapWithNulls() {
+ NullFields.NullFieldsSearchReply reply =
+ new NullFields.NullFieldsSearchReply.Builder()
+ .setResult(null)
+ .setError(null)
+ .setIndices(null)
+ .setRequest(null)
+ .setType(null)
+ .build();
+
+ Map<String, Object> map = reply.toMap();
+ assertNull(map.get("result"));
+ assertNull(map.get("error"));
+ assertNull(map.get("indices"));
+ assertNull(map.get("request"));
+ assertNull(map.get("type"));
+ }
+}
diff --git a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/null_fields.gen.dart b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/null_fields.gen.dart
new file mode 100644
index 0000000..9f332d2
--- /dev/null
+++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/null_fields.gen.dart
@@ -0,0 +1,211 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Autogenerated from Pigeon (v2.0.2), 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
+// @dart = 2.12
+import 'dart:async';
+import 'dart:typed_data' show Uint8List, Int32List, Int64List, Float64List;
+
+import 'package:flutter/foundation.dart' show WriteBuffer, ReadBuffer;
+import 'package:flutter/services.dart';
+
+enum NullFieldsSearchReplyType {
+ success,
+ failure,
+}
+
+class NullFieldsSearchRequest {
+ NullFieldsSearchRequest({
+ this.query,
+ });
+
+ String? query;
+
+ Object encode() {
+ final Map<Object?, Object?> pigeonMap = <Object?, Object?>{};
+ pigeonMap['query'] = query;
+ return pigeonMap;
+ }
+
+ static NullFieldsSearchRequest decode(Object message) {
+ final Map<Object?, Object?> pigeonMap = message as Map<Object?, Object?>;
+ return NullFieldsSearchRequest(
+ query: pigeonMap['query'] as String?,
+ );
+ }
+}
+
+class NullFieldsSearchReply {
+ NullFieldsSearchReply({
+ this.result,
+ this.error,
+ this.indices,
+ this.request,
+ this.type,
+ });
+
+ String? result;
+ String? error;
+ List<int?>? indices;
+ NullFieldsSearchRequest? request;
+ NullFieldsSearchReplyType? type;
+
+ Object encode() {
+ final Map<Object?, Object?> pigeonMap = <Object?, Object?>{};
+ pigeonMap['result'] = result;
+ pigeonMap['error'] = error;
+ pigeonMap['indices'] = indices;
+ pigeonMap['request'] = request == null ? null : request!.encode();
+ pigeonMap['type'] = type == null ? null : type!.index;
+ return pigeonMap;
+ }
+
+ static NullFieldsSearchReply decode(Object message) {
+ final Map<Object?, Object?> pigeonMap = message as Map<Object?, Object?>;
+ return NullFieldsSearchReply(
+ result: pigeonMap['result'] as String?,
+ error: pigeonMap['error'] as String?,
+ indices: (pigeonMap['indices'] as List<Object?>?)?.cast<int?>(),
+ request: pigeonMap['request'] != null
+ ? NullFieldsSearchRequest.decode(pigeonMap['request']!)
+ : null,
+ type: pigeonMap['type'] != null
+ ? NullFieldsSearchReplyType.values[pigeonMap['type']! as int]
+ : null,
+ );
+ }
+}
+
+class _NullFieldsHostApiCodec extends StandardMessageCodec {
+ const _NullFieldsHostApiCodec();
+ @override
+ void writeValue(WriteBuffer buffer, Object? value) {
+ if (value is NullFieldsSearchReply) {
+ buffer.putUint8(128);
+ writeValue(buffer, value.encode());
+ } else if (value is NullFieldsSearchRequest) {
+ buffer.putUint8(129);
+ writeValue(buffer, value.encode());
+ } else {
+ super.writeValue(buffer, value);
+ }
+ }
+
+ @override
+ Object? readValueOfType(int type, ReadBuffer buffer) {
+ switch (type) {
+ case 128:
+ return NullFieldsSearchReply.decode(readValue(buffer)!);
+
+ case 129:
+ return NullFieldsSearchRequest.decode(readValue(buffer)!);
+
+ default:
+ return super.readValueOfType(type, buffer);
+ }
+ }
+}
+
+class NullFieldsHostApi {
+ /// Constructor for [NullFieldsHostApi]. 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.
+ NullFieldsHostApi({BinaryMessenger? binaryMessenger})
+ : _binaryMessenger = binaryMessenger;
+
+ final BinaryMessenger? _binaryMessenger;
+
+ static const MessageCodec<Object?> codec = _NullFieldsHostApiCodec();
+
+ Future<NullFieldsSearchReply> search(
+ NullFieldsSearchRequest arg_nested) async {
+ final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+ 'dev.flutter.pigeon.NullFieldsHostApi.search', codec,
+ binaryMessenger: _binaryMessenger);
+ final Map<Object?, Object?>? replyMap =
+ await channel.send(<Object?>[arg_nested]) as Map<Object?, Object?>?;
+ if (replyMap == null) {
+ throw PlatformException(
+ code: 'channel-error',
+ message: 'Unable to establish connection on channel.',
+ );
+ } else if (replyMap['error'] != null) {
+ final Map<Object?, Object?> error =
+ (replyMap['error'] as Map<Object?, Object?>?)!;
+ throw PlatformException(
+ code: (error['code'] as String?)!,
+ message: error['message'] as String?,
+ details: error['details'],
+ );
+ } else if (replyMap['result'] == null) {
+ throw PlatformException(
+ code: 'null-error',
+ message: 'Host platform returned null value for non-null return value.',
+ );
+ } else {
+ return (replyMap['result'] as NullFieldsSearchReply?)!;
+ }
+ }
+}
+
+class _NullFieldsFlutterApiCodec extends StandardMessageCodec {
+ const _NullFieldsFlutterApiCodec();
+ @override
+ void writeValue(WriteBuffer buffer, Object? value) {
+ if (value is NullFieldsSearchReply) {
+ buffer.putUint8(128);
+ writeValue(buffer, value.encode());
+ } else if (value is NullFieldsSearchRequest) {
+ buffer.putUint8(129);
+ writeValue(buffer, value.encode());
+ } else {
+ super.writeValue(buffer, value);
+ }
+ }
+
+ @override
+ Object? readValueOfType(int type, ReadBuffer buffer) {
+ switch (type) {
+ case 128:
+ return NullFieldsSearchReply.decode(readValue(buffer)!);
+
+ case 129:
+ return NullFieldsSearchRequest.decode(readValue(buffer)!);
+
+ default:
+ return super.readValueOfType(type, buffer);
+ }
+ }
+}
+
+abstract class NullFieldsFlutterApi {
+ static const MessageCodec<Object?> codec = _NullFieldsFlutterApiCodec();
+
+ NullFieldsSearchReply search(NullFieldsSearchRequest request);
+ static void setup(NullFieldsFlutterApi? api,
+ {BinaryMessenger? binaryMessenger}) {
+ {
+ final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+ 'dev.flutter.pigeon.NullFieldsFlutterApi.search', codec,
+ binaryMessenger: binaryMessenger);
+ if (api == null) {
+ channel.setMessageHandler(null);
+ } else {
+ channel.setMessageHandler((Object? message) async {
+ assert(message != null,
+ 'Argument for dev.flutter.pigeon.NullFieldsFlutterApi.search was null.');
+ final List<Object?> args = (message as List<Object?>?)!;
+ final NullFieldsSearchRequest? arg_request =
+ (args[0] as NullFieldsSearchRequest?);
+ assert(arg_request != null,
+ 'Argument for dev.flutter.pigeon.NullFieldsFlutterApi.search was null, expected non-null NullFieldsSearchRequest.');
+ final NullFieldsSearchReply output = api.search(arg_request!);
+ return output;
+ });
+ }
+ }
+ }
+}
diff --git a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/test/null_fields_test.dart b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/test/null_fields_test.dart
new file mode 100644
index 0000000..fe030a3
--- /dev/null
+++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/test/null_fields_test.dart
@@ -0,0 +1,160 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter_test/flutter_test.dart';
+import 'package:flutter_unit_tests/null_fields.gen.dart';
+
+void main() {
+ test('test constructor with values', () {
+ final NullFieldsSearchRequest request =
+ NullFieldsSearchRequest(query: 'query');
+
+ final NullFieldsSearchReply reply = NullFieldsSearchReply(
+ result: 'result',
+ error: 'error',
+ indices: <int>[1, 2, 3],
+ request: request,
+ type: NullFieldsSearchReplyType.success,
+ );
+
+ expect(reply.result, 'result');
+ expect(reply.error, 'error');
+ expect(reply.indices, <int>[1, 2, 3]);
+ expect(reply.request!.query, 'query');
+ expect(reply.type, NullFieldsSearchReplyType.success);
+ });
+
+ test('test request constructor with nulls', () {
+ final NullFieldsSearchRequest request =
+ NullFieldsSearchRequest(query: null);
+
+ expect(request.query, isNull);
+ });
+
+ test('test reply constructor with nulls', () {
+ final NullFieldsSearchReply reply = NullFieldsSearchReply(
+ result: null,
+ error: null,
+ indices: null,
+ request: null,
+ type: null,
+ );
+
+ expect(reply.result, isNull);
+ expect(reply.error, isNull);
+ expect(reply.indices, isNull);
+ expect(reply.request, isNull);
+ expect(reply.type, isNull);
+ });
+
+ test('test request decode with values', () {
+ final NullFieldsSearchRequest request =
+ NullFieldsSearchRequest.decode(<String, dynamic>{
+ 'query': 'query',
+ });
+
+ expect(request.query, 'query');
+ });
+
+ test('test request decode with null', () {
+ final NullFieldsSearchRequest request =
+ NullFieldsSearchRequest.decode(<String, dynamic>{
+ 'query': null,
+ });
+
+ expect(request.query, isNull);
+ });
+
+ test('test reply decode with values', () {
+ final NullFieldsSearchReply reply =
+ NullFieldsSearchReply.decode(<String, dynamic>{
+ 'result': 'result',
+ 'error': 'error',
+ 'indices': <int>[1, 2, 3],
+ 'request': <String, dynamic>{
+ 'query': 'query',
+ },
+ 'type': NullFieldsSearchReplyType.success.index,
+ });
+
+ expect(reply.result, 'result');
+ expect(reply.error, 'error');
+ expect(reply.indices, <int>[1, 2, 3]);
+ expect(reply.request!.query, 'query');
+ expect(reply.type, NullFieldsSearchReplyType.success);
+ });
+
+ test('test reply decode with nulls', () {
+ final NullFieldsSearchReply reply =
+ NullFieldsSearchReply.decode(<String, dynamic>{
+ 'result': null,
+ 'error': null,
+ 'indices': null,
+ 'request': null,
+ 'type': null,
+ });
+
+ expect(reply.result, isNull);
+ expect(reply.error, isNull);
+ expect(reply.indices, isNull);
+ expect(reply.request, isNull);
+ expect(reply.type, isNull);
+ });
+
+ test('test request encode with values', () {
+ final NullFieldsSearchRequest request =
+ NullFieldsSearchRequest(query: 'query');
+
+ expect(request.encode(), <String, dynamic>{
+ 'query': 'query',
+ });
+ });
+
+ test('test request encode with null', () {
+ final NullFieldsSearchRequest request =
+ NullFieldsSearchRequest(query: null);
+
+ expect(request.encode(), <String, dynamic>{
+ 'query': null,
+ });
+ });
+
+ test('test reply encode with values', () {
+ final NullFieldsSearchReply reply = NullFieldsSearchReply(
+ result: 'result',
+ error: 'error',
+ indices: <int>[1, 2, 3],
+ request: NullFieldsSearchRequest(query: 'query'),
+ type: NullFieldsSearchReplyType.success,
+ );
+
+ expect(reply.encode(), <String, dynamic>{
+ 'result': 'result',
+ 'error': 'error',
+ 'indices': <int>[1, 2, 3],
+ 'request': <String, dynamic>{
+ 'query': 'query',
+ },
+ 'type': NullFieldsSearchReplyType.success.index,
+ });
+ });
+
+ test('test reply encode with nulls', () {
+ final NullFieldsSearchReply reply = NullFieldsSearchReply(
+ result: null,
+ error: null,
+ indices: null,
+ request: null,
+ type: null,
+ );
+
+ expect(reply.encode(), <String, dynamic>{
+ 'result': null,
+ 'error': null,
+ 'indices': null,
+ 'request': null,
+ 'type': null,
+ });
+ });
+}
diff --git a/packages/pigeon/platform_tests/ios_unit_tests/ios/Runner.xcodeproj/project.pbxproj b/packages/pigeon/platform_tests/ios_unit_tests/ios/Runner.xcodeproj/project.pbxproj
index 2abcbc5..94e5288 100644
--- a/packages/pigeon/platform_tests/ios_unit_tests/ios/Runner.xcodeproj/project.pbxproj
+++ b/packages/pigeon/platform_tests/ios_unit_tests/ios/Runner.xcodeproj/project.pbxproj
@@ -8,9 +8,9 @@
/* Begin PBXBuildFile section */
0D02163D27BC7B48009BD76F /* nullable_returns.gen.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D02163C27BC7B48009BD76F /* nullable_returns.gen.m */; };
+ 0D21E59A27D0502D0051D07D /* background_platform_channels.gen.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D21E59827D0502D0051D07D /* background_platform_channels.gen.m */; };
0D36469D27C6BE3C0069B7BF /* NullableReturnsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D36469C27C6BE3C0069B7BF /* NullableReturnsTest.m */; };
0D3646A027C6DCEC0069B7BF /* MockBinaryMessenger.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D36469F27C6DCEC0069B7BF /* MockBinaryMessenger.m */; };
- 0D21E59A27D0502D0051D07D /* background_platform_channels.gen.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D21E59827D0502D0051D07D /* background_platform_channels.gen.m */; };
0D50127523FF75B100CD5B95 /* RunnerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D50127423FF75B100CD5B95 /* RunnerTests.m */; };
0D6FD3C526A76D400046D8BD /* primitive.gen.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D6FD3C426A76D400046D8BD /* primitive.gen.m */; };
0D6FD3C726A777C00046D8BD /* PrimitiveTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D6FD3C626A777C00046D8BD /* PrimitiveTest.m */; };
@@ -42,6 +42,8 @@
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
+ CEA7789327DE9EEB00FE0824 /* null_fields.gen.m in Sources */ = {isa = PBXBuildFile; fileRef = CEA7789127DE9EEB00FE0824 /* null_fields.gen.m */; };
+ CEA7789527DE9F1800FE0824 /* NullFieldsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = CEA7789427DE9F1800FE0824 /* NullFieldsTest.m */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -70,11 +72,11 @@
/* Begin PBXFileReference section */
0D02163B27BC7B48009BD76F /* nullable_returns.gen.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = nullable_returns.gen.h; sourceTree = "<group>"; };
0D02163C27BC7B48009BD76F /* nullable_returns.gen.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = nullable_returns.gen.m; sourceTree = "<group>"; };
+ 0D21E59827D0502D0051D07D /* background_platform_channels.gen.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = background_platform_channels.gen.m; sourceTree = "<group>"; };
+ 0D21E59927D0502D0051D07D /* background_platform_channels.gen.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = background_platform_channels.gen.h; sourceTree = "<group>"; };
0D36469C27C6BE3C0069B7BF /* NullableReturnsTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NullableReturnsTest.m; sourceTree = "<group>"; };
0D36469E27C6DCEC0069B7BF /* MockBinaryMessenger.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MockBinaryMessenger.h; sourceTree = "<group>"; };
0D36469F27C6DCEC0069B7BF /* MockBinaryMessenger.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MockBinaryMessenger.m; sourceTree = "<group>"; };
- 0D21E59827D0502D0051D07D /* background_platform_channels.gen.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = background_platform_channels.gen.m; sourceTree = "<group>"; };
- 0D21E59927D0502D0051D07D /* background_platform_channels.gen.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = background_platform_channels.gen.h; sourceTree = "<group>"; };
0D50127223FF75B100CD5B95 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
0D50127423FF75B100CD5B95 /* RunnerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RunnerTests.m; sourceTree = "<group>"; };
0D50127623FF75B100CD5B95 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@@ -131,6 +133,9 @@
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+ CEA7789127DE9EEB00FE0824 /* null_fields.gen.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = null_fields.gen.m; sourceTree = "<group>"; };
+ CEA7789227DE9EEB00FE0824 /* null_fields.gen.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = null_fields.gen.h; sourceTree = "<group>"; };
+ CEA7789427DE9F1800FE0824 /* NullFieldsTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NullFieldsTest.m; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -170,6 +175,7 @@
0DBD8C3D279B73F700E4FDBA /* NonNullFieldsTest.m */,
0D36469E27C6DCEC0069B7BF /* MockBinaryMessenger.h */,
0D36469F27C6DCEC0069B7BF /* MockBinaryMessenger.m */,
+ CEA7789427DE9F1800FE0824 /* NullFieldsTest.m */,
);
path = RunnerTests;
sourceTree = "<group>";
@@ -207,6 +213,8 @@
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
+ CEA7789227DE9EEB00FE0824 /* null_fields.gen.h */,
+ CEA7789127DE9EEB00FE0824 /* null_fields.gen.m */,
0D21E59927D0502D0051D07D /* background_platform_channels.gen.h */,
0D21E59827D0502D0051D07D /* background_platform_channels.gen.m */,
0D02163B27BC7B48009BD76F /* nullable_returns.gen.h */,
@@ -396,6 +404,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ CEA7789527DE9F1800FE0824 /* NullFieldsTest.m in Sources */,
0D50127523FF75B100CD5B95 /* RunnerTests.m in Sources */,
0DBD8C3E279B73F700E4FDBA /* NonNullFieldsTest.m in Sources */,
0DF4E5C5266ECF4A00AEA855 /* AllDatatypesTest.m in Sources */,
@@ -428,6 +437,7 @@
97C146F31CF9000F007C117D /* main.m in Sources */,
0DD2E6BC2684031300A7D764 /* host2flutter.gen.m in Sources */,
0DA5DFD626CC39D600D2354B /* multiple_arity.gen.m in Sources */,
+ CEA7789327DE9EEB00FE0824 /* null_fields.gen.m in Sources */,
0DD2E6BE2684031300A7D764 /* message.gen.m in Sources */,
0D21E59A27D0502D0051D07D /* background_platform_channels.gen.m in Sources */,
0DD2E6BA2684031300A7D764 /* void_arg_host.gen.m in Sources */,
diff --git a/packages/pigeon/platform_tests/ios_unit_tests/ios/RunnerTests/NullFieldsTest.m b/packages/pigeon/platform_tests/ios_unit_tests/ios/RunnerTests/NullFieldsTest.m
new file mode 100644
index 0000000..7fee2dd
--- /dev/null
+++ b/packages/pigeon/platform_tests/ios_unit_tests/ios/RunnerTests/NullFieldsTest.m
@@ -0,0 +1,160 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import <Flutter/Flutter.h>
+#import <XCTest/XCTest.h>
+#import "EchoMessenger.h"
+#import "null_fields.gen.h"
+
+///////////////////////////////////////////////////////////////////////////////////////////
+@interface NullFieldsSearchRequest ()
++ (NullFieldsSearchRequest*)fromMap:(NSDictionary*)dict;
+- (NSDictionary*)toMap;
+@end
+
+///////////////////////////////////////////////////////////////////////////////////////////
+@interface NullFieldsSearchReply ()
++ (NullFieldsSearchReply*)fromMap:(NSDictionary*)dict;
+- (NSDictionary*)toMap;
+@end
+
+///////////////////////////////////////////////////////////////////////////////////////////
+@interface NullFieldsTest : XCTestCase
+@end
+
+///////////////////////////////////////////////////////////////////////////////////////////
+@implementation NullFieldsTest
+
+- (void)testMakeWithValues {
+ NullFieldsSearchRequest* request = [NullFieldsSearchRequest makeWithQuery:@"hello"];
+
+ NullFieldsSearchReply* reply =
+ [NullFieldsSearchReply makeWithResult:@"result"
+ error:@"error"
+ indices:@[ @1, @2, @3 ]
+ request:request
+ type:NullFieldsSearchReplyTypeSuccess];
+
+ NSArray* indices = @[ @1, @2, @3 ];
+ XCTAssertEqualObjects(@"result", reply.result);
+ XCTAssertEqualObjects(@"error", reply.error);
+ XCTAssertEqualObjects(indices, reply.indices);
+ XCTAssertEqualObjects(@"hello", reply.request.query);
+ XCTAssertEqual(NullFieldsSearchReplyTypeSuccess, reply.type);
+}
+
+- (void)testMakeRequestWithNulls {
+ NullFieldsSearchRequest* request = [NullFieldsSearchRequest makeWithQuery:nil];
+ XCTAssertNil(request.query);
+}
+
+- (void)testMakeReplyWithNulls {
+ NullFieldsSearchReply* reply =
+ [NullFieldsSearchReply makeWithResult:nil
+ error:nil
+ indices:nil
+ request:nil
+ type:NullFieldsSearchReplyTypeSuccess];
+ XCTAssertNil(reply.result);
+ XCTAssertNil(reply.error);
+ XCTAssertNil(reply.indices);
+ XCTAssertNil(reply.request);
+ XCTAssertEqual(NullFieldsSearchReplyTypeSuccess, reply.type);
+}
+
+- (void)testRequestFromMapWithValues {
+ NSDictionary* map = @{
+ @"query" : @"hello",
+ };
+ NullFieldsSearchRequest* request = [NullFieldsSearchRequest fromMap:map];
+ XCTAssertEqualObjects(@"hello", request.query);
+}
+
+- (void)testRequestFromMapWithNulls {
+ NSDictionary* map = @{
+ @"query" : [NSNull null],
+ };
+ NullFieldsSearchRequest* request = [NullFieldsSearchRequest fromMap:map];
+ XCTAssertNil(request.query);
+}
+
+- (void)testReplyFromMapWithValues {
+ NSDictionary* map = @{
+ @"result" : @"result",
+ @"error" : @"error",
+ @"indices" : @[ @1, @2, @3 ],
+ @"request" : @{
+ @"query" : @"hello",
+ },
+ @"type" : @0,
+ };
+
+ NSArray* indices = @[ @1, @2, @3 ];
+ NullFieldsSearchReply* reply = [NullFieldsSearchReply fromMap:map];
+ XCTAssertEqualObjects(@"result", reply.result);
+ XCTAssertEqualObjects(@"error", reply.error);
+ XCTAssertEqualObjects(indices, reply.indices);
+ XCTAssertEqualObjects(@"hello", reply.request.query);
+ XCTAssertEqual(NullFieldsSearchReplyTypeSuccess, reply.type);
+}
+
+- (void)testReplyFromMapWithNulls {
+ NSDictionary* map = @{
+ @"result" : [NSNull null],
+ @"error" : [NSNull null],
+ @"indices" : [NSNull null],
+ @"request" : [NSNull null],
+ @"type" : [NSNull null],
+ };
+ NullFieldsSearchReply* reply = [NullFieldsSearchReply fromMap:map];
+ XCTAssertNil(reply.result);
+ XCTAssertNil(reply.error);
+ XCTAssertNil(reply.indices);
+ XCTAssertNil(reply.request.query);
+ XCTAssertEqual(NullFieldsSearchReplyTypeSuccess, reply.type);
+}
+
+- (void)testRequestToMapWithValuess {
+ NullFieldsSearchRequest* request = [NullFieldsSearchRequest makeWithQuery:@"hello"];
+ NSDictionary* dict = [request toMap];
+ XCTAssertEqual(@"hello", dict[@"query"]);
+}
+
+- (void)testRequestToMapWithNulls {
+ NullFieldsSearchRequest* request = [NullFieldsSearchRequest makeWithQuery:nil];
+ NSDictionary* dict = [request toMap];
+ XCTAssertEqual([NSNull null], dict[@"query"]);
+}
+
+- (void)testReplyToMapWithValuess {
+ NullFieldsSearchReply* reply =
+ [NullFieldsSearchReply makeWithResult:@"result"
+ error:@"error"
+ indices:@[ @1, @2, @3 ]
+ request:[NullFieldsSearchRequest makeWithQuery:@"hello"]
+ type:NullFieldsSearchReplyTypeSuccess];
+ NSDictionary* dict = [reply toMap];
+ NSArray* indices = @[ @1, @2, @3 ];
+ XCTAssertEqualObjects(@"result", dict[@"result"]);
+ XCTAssertEqualObjects(@"error", dict[@"error"]);
+ XCTAssertEqualObjects(indices, dict[@"indices"]);
+ XCTAssertEqualObjects(@"hello", dict[@"request"][@"query"]);
+ XCTAssertEqualObjects(@0, dict[@"type"]);
+}
+
+- (void)testReplyToMapWithNulls {
+ NullFieldsSearchReply* reply =
+ [NullFieldsSearchReply makeWithResult:nil
+ error:nil
+ indices:nil
+ request:nil
+ type:NullFieldsSearchReplyTypeSuccess];
+ NSDictionary* dict = [reply toMap];
+ XCTAssertEqual([NSNull null], dict[@"result"]);
+ XCTAssertEqual([NSNull null], dict[@"error"]);
+ XCTAssertEqual([NSNull null], dict[@"indices"]);
+ XCTAssertEqual([NSNull null], dict[@"request"]);
+}
+
+@end
diff --git a/packages/pigeon/pubspec.yaml b/packages/pigeon/pubspec.yaml
index 098cfac..5efd347 100644
--- a/packages/pigeon/pubspec.yaml
+++ b/packages/pigeon/pubspec.yaml
@@ -2,7 +2,7 @@
description: Code generator tool to make communication between Flutter and the host platform type-safe and easier.
repository: https://github.com/flutter/packages/tree/main/packages/pigeon
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3Apigeon
-version: 2.0.1 # This must match the version in lib/generator_tools.dart
+version: 2.0.2 # This must match the version in lib/generator_tools.dart
environment:
sdk: ">=2.12.0 <3.0.0"
diff --git a/packages/pigeon/run_tests.sh b/packages/pigeon/run_tests.sh
index 91d86ed..265c027 100755
--- a/packages/pigeon/run_tests.sh
+++ b/packages/pigeon/run_tests.sh
@@ -242,6 +242,7 @@
gen_ios_unittests_code ./pigeons/message.dart ""
gen_ios_unittests_code ./pigeons/multiple_arity.dart ""
gen_ios_unittests_code ./pigeons/non_null_fields.dart "NNF"
+ gen_ios_unittests_code ./pigeons/null_fields.dart ""
gen_ios_unittests_code ./pigeons/nullable_returns.dart "NR"
gen_ios_unittests_code ./pigeons/primitive.dart ""
gen_ios_unittests_code ./pigeons/void_arg_flutter.dart "VAF"
@@ -303,6 +304,7 @@
gen_android_unittests_code ./pigeons/message.dart MessagePigeon
gen_android_unittests_code ./pigeons/multiple_arity.dart MultipleArity
gen_android_unittests_code ./pigeons/non_null_fields.dart NonNullFields
+ gen_android_unittests_code ./pigeons/null_fields.dart NullFields
gen_android_unittests_code ./pigeons/nullable_returns.dart NullableReturns
gen_android_unittests_code ./pigeons/primitive.dart Primitive
gen_android_unittests_code ./pigeons/void_arg_flutter.dart VoidArgFlutter
diff --git a/packages/pigeon/test/java_generator_test.dart b/packages/pigeon/test/java_generator_test.dart
index f65fe58..6b54307 100644
--- a/packages/pigeon/test/java_generator_test.dart
+++ b/packages/pigeon/test/java_generator_test.dart
@@ -470,7 +470,8 @@
expect(code, contains('public static class Outer'));
expect(code, contains('public static class Nested'));
expect(code, contains('private @Nullable Nested nested;'));
- expect(code, contains('Nested.fromMap((Map)nested)'));
+ expect(code,
+ contains('(nested == null) ? null : Nested.fromMap((Map)nested)'));
expect(code,
contains('put("nested", (nested == null) ? null : nested.toMap());'));
});