[pigeon] implements nullable arguments (#895)

* [pigeon] implemented nullable arguements

* implemented dart

* implemented java

* objc implementation

* added generated unit tests

* fixed some objc tests

* added a dart platform test

* added ios platform tests

* added platform test for android

* fixed rebase

* updated pubspec

* stuart feedback

* updated readme

* updated nullability of async nonnull parameters for objc

* updated version to 2.0.0

* stuart feedback
diff --git a/packages/pigeon/CHANGELOG.md b/packages/pigeon/CHANGELOG.md
index a8c1255..31676bd 100644
--- a/packages/pigeon/CHANGELOG.md
+++ b/packages/pigeon/CHANGELOG.md
@@ -1,3 +1,9 @@
+## 2.0.0
+
+* Implements nullable parameters.
+* **BREAKING CHANGE** - Nonnull parameters to async methods on HostApis for ObjC
+  now have the proper nullability hints.
+
 ## 1.0.19
 
 * Implements nullable return types.
diff --git a/packages/pigeon/README.md b/packages/pigeon/README.md
index b74f4fe..e773591 100644
--- a/packages/pigeon/README.md
+++ b/packages/pigeon/README.md
@@ -64,8 +64,6 @@
    `void`.
 1) Generics are supported, but can currently only be used with nullable types
    (example: `List<int?>`).
-1) Arguments must be non-nullable.  Fields on classes and return types can be
-   nullable or non-nullable.
 
 ## Supported Datatypes
 
@@ -122,13 +120,13 @@
 
 Pigeon supports generating null-safe code, but it doesn't yet support:
 
-1) Nullable method parameters
 1) Nullable generics type arguments
 
 It does support:
 
 1) Nullable and Non-nullable class fields.
 1) Nullable return values
+1) Nullable method parameters
 
 The default is to generate null-safe code but in order to generate non-null-safe
 code run Pigeon with the extra argument `--no-dart_null_safety`. For example:
diff --git a/packages/pigeon/lib/dart_generator.dart b/packages/pigeon/lib/dart_generator.dart
index 2f735b4..95ecbcb 100644
--- a/packages/pigeon/lib/dart_generator.dart
+++ b/packages/pigeon/lib/dart_generator.dart
@@ -134,7 +134,7 @@
   return func.arguments.isEmpty
       ? ''
       : indexMap(func.arguments, (int index, NamedType arg) {
-          final String type = _addGenericTypes(arg.type, nullTag);
+          final String type = _addGenericTypesNullable(arg.type, nullTag);
           final String argName = getArgumentName(index, arg);
           return '$type $argName';
         }).join(', ');
@@ -182,7 +182,7 @@
         String argNameFunc(int index, NamedType type) =>
             _getSafeArgumentName(index, type);
         final Iterable<String> argNames = indexMap(func.arguments, argNameFunc);
-        sendArgument = '<Object>[${argNames.join(', ')}]';
+        sendArgument = '<Object?>[${argNames.join(', ')}]';
         argSignature = _getMethodArgumentsSignature(func, argNameFunc, nullTag);
       }
       indent.write(
diff --git a/packages/pigeon/lib/generator_tools.dart b/packages/pigeon/lib/generator_tools.dart
index 9a7487a..948c197 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 = '1.0.19';
+const String pigeonVersion = '2.0.0';
 
 /// 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 ef17076..9e97488 100644
--- a/packages/pigeon/lib/java_generator.dart
+++ b/packages/pigeon/lib/java_generator.dart
@@ -127,8 +127,8 @@
         : _nullsafeJavaTypeForDartType(method.returnType);
     final List<String> argSignature = <String>[];
     if (method.arguments.isNotEmpty) {
-      final Iterable<String> argTypes =
-          method.arguments.map((NamedType e) => _javaTypeForDartType(e.type));
+      final Iterable<String> argTypes = method.arguments
+          .map((NamedType e) => _nullsafeJavaTypeForDartType(e.type));
       final Iterable<String> argNames =
           method.arguments.map((NamedType e) => e.name);
       argSignature
@@ -180,16 +180,20 @@
                 final bool isInt = arg.type.baseName == 'int';
                 final String argType =
                     isInt ? 'Number' : _javaTypeForDartType(arg.type);
-                final String argCast = isInt ? '.longValue()' : '';
                 final String argName = _getSafeArgumentName(index, arg);
+                final String argExpression = isInt
+                    ? '($argName == null) ? null : $argName.longValue()'
+                    : argName;
                 indent
                     .writeln('$argType $argName = ($argType)args.get($index);');
-                indent.write('if ($argName == null) ');
-                indent.scoped('{', '}', () {
-                  indent.writeln(
-                      'throw new NullPointerException("$argName unexpectedly null.");');
-                });
-                methodArgument.add('$argName$argCast');
+                if (!arg.type.isNullable) {
+                  indent.write('if ($argName == null) ');
+                  indent.scoped('{', '}', () {
+                    indent.writeln(
+                        'throw new NullPointerException("$argName unexpectedly null.");');
+                  });
+                }
+                methodArgument.add(argExpression);
               });
             }
             if (method.isAsynchronous) {
@@ -311,8 +315,8 @@
         indent.write('public void ${func.name}(Reply<$returnType> callback) ');
         sendArgument = 'null';
       } else {
-        final Iterable<String> argTypes =
-            func.arguments.map((NamedType e) => _javaTypeForDartType(e.type));
+        final Iterable<String> argTypes = func.arguments
+            .map((NamedType e) => _nullsafeJavaTypeForDartType(e.type));
         final Iterable<String> argNames =
             indexMap(func.arguments, _getSafeArgumentName);
         sendArgument =
@@ -404,8 +408,9 @@
 }
 
 String _nullsafeJavaTypeForDartType(TypeDeclaration type) {
-  final String nullSafe = type.isNullable ? '@Nullable' : '@NonNull';
-  return '$nullSafe ${_javaTypeForDartType(type)}';
+  final String nullSafe =
+      type.isVoid ? '' : (type.isNullable ? '@Nullable ' : '@NonNull ');
+  return '$nullSafe${_javaTypeForDartType(type)}';
 }
 
 /// Casts variable named [varName] to the correct host datatype for [field].
diff --git a/packages/pigeon/lib/objc_generator.dart b/packages/pigeon/lib/objc_generator.dart
index 87a8d23..7839b38 100644
--- a/packages/pigeon/lib/objc_generator.dart
+++ b/packages/pigeon/lib/objc_generator.dart
@@ -375,7 +375,7 @@
       _getSelectorComponents(func, lastArgName);
   final Iterable<String> argTypes = followedByOne(
     func.arguments.map((NamedType arg) {
-      final String nullable = func.isAsynchronous ? 'nullable ' : '';
+      final String nullable = arg.type.isNullable ? 'nullable ' : '';
       final _ObjcPtr argType = _objcTypeForDartType(options.prefix, arg.type);
       return '$nullable${argType.ptr.trim()}';
     }),
@@ -427,7 +427,9 @@
       lastArgType = 'FlutterError *_Nullable *_Nonnull';
       lastArgName = 'error';
     }
-    if (!func.returnType.isNullable) {
+    if (!func.returnType.isNullable &&
+        !func.returnType.isVoid &&
+        !func.isAsynchronous) {
       indent.writeln('/// @return `nil` only when `error != nil`.');
     }
     indent.writeln(_makeObjcSignature(
@@ -605,7 +607,7 @@
       map3(wholeNumbers.take(func.arguments.length), argNames, func.arguments,
           (int count, String argName, NamedType arg) {
         final _ObjcPtr argType = _objcTypeForDartType(options.prefix, arg.type);
-        return '${argType.ptr}$argName = args[$count];';
+        return '${argType.ptr}$argName = GetNullableObjectAtIndex(args, $count);';
       }).forEach(indent.writeln);
     }
 
@@ -747,7 +749,9 @@
     if (func.arguments.isEmpty) {
       sendArgument = 'nil';
     } else {
-      sendArgument = '@[${argNames.join(', ')}]';
+      String makeVarOrNSNullExpression(String x) =>
+          '($x == nil) ? [NSNull null] : $x';
+      sendArgument = '@[${argNames.map(makeVarOrNSNullExpression).join(', ')}]';
     }
     indent.write(_makeObjcSignature(
       func: func,
@@ -837,6 +841,10 @@
 \tid result = dict[key];
 \treturn (result == [NSNull null]) ? nil : result;
 }
+static id GetNullableObjectAtIndex(NSArray* array, NSInteger key) {
+\tid result = array[key];
+\treturn (result == [NSNull null]) ? nil : result;
+}
 ''');
   }
 
diff --git a/packages/pigeon/lib/pigeon_lib.dart b/packages/pigeon/lib/pigeon_lib.dart
index 71082dd..a53a637 100644
--- a/packages/pigeon/lib/pigeon_lib.dart
+++ b/packages/pigeon/lib/pigeon_lib.dart
@@ -489,15 +489,6 @@
               'Enums aren\'t yet supported for primitive return types: "${method.returnType}" in API: "${api.name}" method: "${method.name}" (https://github.com/flutter/flutter/issues/87307)',
         ));
       }
-      if (method.arguments.isNotEmpty &&
-          method.arguments
-              .any((NamedType element) => element.type.isNullable)) {
-        result.add(Error(
-          message:
-              'Nullable argument types aren\'t supported for Pigeon in API: "${api.name}" method: "${method.name}"',
-          lineNumber: _calculateLineNumberNullable(source, method.offset),
-        ));
-      }
       for (final NamedType unnamedType in method.arguments
           .where((NamedType element) => element.type.baseName.isEmpty)) {
         result.add(Error(
diff --git a/packages/pigeon/pigeons/nullable_returns.dart b/packages/pigeon/pigeons/nullable_returns.dart
index caa33ef..150d5d3 100644
--- a/packages/pigeon/pigeons/nullable_returns.dart
+++ b/packages/pigeon/pigeons/nullable_returns.dart
@@ -16,3 +16,13 @@
 abstract class NonNullFlutterApi {
   int? doit();
 }
+
+@HostApi()
+abstract class NullableArgHostApi {
+  int doit(int? x);
+}
+
+@FlutterApi()
+abstract class NullableArgFlutterApi {
+  int doit(int? x);
+}
diff --git a/packages/pigeon/platform_tests/android_unit_tests/android/.settings/org.eclipse.buildship.core.prefs b/packages/pigeon/platform_tests/android_unit_tests/android/.settings/org.eclipse.buildship.core.prefs
index 51db0ab..6154443 100644
--- a/packages/pigeon/platform_tests/android_unit_tests/android/.settings/org.eclipse.buildship.core.prefs
+++ b/packages/pigeon/platform_tests/android_unit_tests/android/.settings/org.eclipse.buildship.core.prefs
@@ -5,7 +5,7 @@
 connection.project.dir=
 eclipse.preferences.version=1
 gradle.user.home=
-java.home=/Library/Java/JavaVirtualMachines/jdk-11-latest/Contents/Home
+java.home=/Library/Java/JavaVirtualMachines/zulu-11.jdk/Contents/Home
 jvm.arguments=
 offline.mode=false
 override.workspace.settings=true
diff --git a/packages/pigeon/platform_tests/android_unit_tests/android/app/src/test/java/com/example/android_unit_tests/NullableReturnsTest.java b/packages/pigeon/platform_tests/android_unit_tests/android/app/src/test/java/com/example/android_unit_tests/NullableReturnsTest.java
new file mode 100644
index 0000000..1f79171
--- /dev/null
+++ b/packages/pigeon/platform_tests/android_unit_tests/android/app/src/test/java/com/example/android_unit_tests/NullableReturnsTest.java
@@ -0,0 +1,78 @@
+// 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.*;
+import static org.mockito.Mockito.*;
+
+import io.flutter.plugin.common.BinaryMessenger;
+import io.flutter.plugin.common.MessageCodec;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Map;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+public class NullableReturnsTest {
+  @Test
+  public void nullArgHostApi() {
+    NullableReturns.NullableArgHostApi mockApi = mock(NullableReturns.NullableArgHostApi.class);
+    BinaryMessenger binaryMessenger = mock(BinaryMessenger.class);
+    NullableReturns.NullableArgHostApi.setup(binaryMessenger, mockApi);
+    ArgumentCaptor<BinaryMessenger.BinaryMessageHandler> handler =
+        ArgumentCaptor.forClass(BinaryMessenger.BinaryMessageHandler.class);
+    verify(binaryMessenger).setMessageHandler(anyString(), handler.capture());
+    MessageCodec<Object> codec = NullableReturns.NullableArgHostApi.getCodec();
+    ByteBuffer message =
+        codec.encodeMessage(
+            new ArrayList<Object>() {
+              {
+                add(null);
+              }
+            });
+    message.rewind();
+    handler
+        .getValue()
+        .onMessage(
+            message,
+            (bytes) -> {
+              bytes.rewind();
+              @SuppressWarnings("unchecked")
+              Map<String, Object> wrapped = (Map<String, Object>) codec.decodeMessage(bytes);
+              assertTrue(wrapped.containsKey("result"));
+            });
+  }
+
+  @Test
+  public void nullArgFlutterApi() {
+    BinaryMessenger binaryMessenger = mock(BinaryMessenger.class);
+    doAnswer(
+            invocation -> {
+              ByteBuffer message = invocation.getArgument(1);
+              BinaryMessenger.BinaryReply reply = invocation.getArgument(2);
+              message.position(0);
+              ArrayList<Object> args =
+                  (ArrayList<Object>)
+                      NullableReturns.NullableArgFlutterApi.getCodec().decodeMessage(message);
+              assertNull(args.get(0));
+              ByteBuffer replyData =
+                  NullableReturns.NullableArgFlutterApi.getCodec().encodeMessage(args.get(0));
+              reply.reply(replyData);
+              return null;
+            })
+        .when(binaryMessenger)
+        .send(anyString(), any(), any());
+    NullableReturns.NullableArgFlutterApi api =
+        new NullableReturns.NullableArgFlutterApi(binaryMessenger);
+    boolean[] didCall = {false};
+    api.doit(
+        null,
+        (Long result) -> {
+          didCall[0] = true;
+          assertNull(result);
+        });
+    assertTrue(didCall[0]);
+  }
+}
diff --git a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/all_datatypes.dart b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/all_datatypes.dart
index f318dc5..ae7444f 100644
--- a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/all_datatypes.dart
+++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/all_datatypes.dart
@@ -154,7 +154,7 @@
         'dev.flutter.pigeon.HostEverything.echo', codec,
         binaryMessenger: _binaryMessenger);
     final Map<Object?, Object?>? replyMap =
-        await channel.send(<Object>[arg_everything]) as Map<Object?, Object?>?;
+        await channel.send(<Object?>[arg_everything]) as Map<Object?, Object?>?;
     if (replyMap == null) {
       throw PlatformException(
         code: 'channel-error',
diff --git a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/multiple_arity.gen.dart b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/multiple_arity.gen.dart
index 8c80603..e5926f0 100644
--- a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/multiple_arity.gen.dart
+++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/multiple_arity.gen.dart
@@ -32,7 +32,7 @@
         'dev.flutter.pigeon.MultipleArityHostApi.subtract', codec,
         binaryMessenger: _binaryMessenger);
     final Map<Object?, Object?>? replyMap =
-        await channel.send(<Object>[arg_x, arg_y]) as Map<Object?, Object?>?;
+        await channel.send(<Object?>[arg_x, arg_y]) as Map<Object?, Object?>?;
     if (replyMap == null) {
       throw PlatformException(
         code: 'channel-error',
diff --git a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/non_null_fields.gen.dart b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/non_null_fields.gen.dart
index 97ea6a4..a06d692 100644
--- a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/non_null_fields.gen.dart
+++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/non_null_fields.gen.dart
@@ -108,7 +108,7 @@
         'dev.flutter.pigeon.NonNullHostApi.search', codec,
         binaryMessenger: _binaryMessenger);
     final Map<Object?, Object?>? replyMap =
-        await channel.send(<Object>[arg_nested]) as Map<Object?, Object?>?;
+        await channel.send(<Object?>[arg_nested]) as Map<Object?, Object?>?;
     if (replyMap == null) {
       throw PlatformException(
         code: 'channel-error',
diff --git a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/null_safe_pigeon.dart b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/null_safe_pigeon.dart
index 5cce608..994ac01 100644
--- a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/null_safe_pigeon.dart
+++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/null_safe_pigeon.dart
@@ -157,7 +157,7 @@
         'dev.flutter.pigeon.Api.search', codec,
         binaryMessenger: _binaryMessenger);
     final Map<Object?, Object?>? replyMap =
-        await channel.send(<Object>[arg_request]) as Map<Object?, Object?>?;
+        await channel.send(<Object?>[arg_request]) as Map<Object?, Object?>?;
     if (replyMap == null) {
       throw PlatformException(
         code: 'channel-error',
@@ -186,7 +186,7 @@
         'dev.flutter.pigeon.Api.doSearches', codec,
         binaryMessenger: _binaryMessenger);
     final Map<Object?, Object?>? replyMap =
-        await channel.send(<Object>[arg_request]) as Map<Object?, Object?>?;
+        await channel.send(<Object?>[arg_request]) as Map<Object?, Object?>?;
     if (replyMap == null) {
       throw PlatformException(
         code: 'channel-error',
@@ -215,7 +215,7 @@
         'dev.flutter.pigeon.Api.echo', codec,
         binaryMessenger: _binaryMessenger);
     final Map<Object?, Object?>? replyMap =
-        await channel.send(<Object>[arg_requests]) as Map<Object?, Object?>?;
+        await channel.send(<Object?>[arg_requests]) as Map<Object?, Object?>?;
     if (replyMap == null) {
       throw PlatformException(
         code: 'channel-error',
@@ -244,7 +244,7 @@
         'dev.flutter.pigeon.Api.anInt', codec,
         binaryMessenger: _binaryMessenger);
     final Map<Object?, Object?>? replyMap =
-        await channel.send(<Object>[arg_value]) as Map<Object?, Object?>?;
+        await channel.send(<Object?>[arg_value]) as Map<Object?, Object?>?;
     if (replyMap == null) {
       throw PlatformException(
         code: 'channel-error',
diff --git a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/nullable_returns.gen.dart b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/nullable_returns.gen.dart
index f80c7d3..3766d06 100644
--- a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/nullable_returns.gen.dart
+++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/nullable_returns.gen.dart
@@ -78,3 +78,80 @@
     }
   }
 }
+
+class _NullableArgHostApiCodec extends StandardMessageCodec {
+  const _NullableArgHostApiCodec();
+}
+
+class NullableArgHostApi {
+  /// Constructor for [NullableArgHostApi].  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.
+  NullableArgHostApi({BinaryMessenger? binaryMessenger})
+      : _binaryMessenger = binaryMessenger;
+
+  final BinaryMessenger? _binaryMessenger;
+
+  static const MessageCodec<Object?> codec = _NullableArgHostApiCodec();
+
+  Future<int> doit(int? arg_x) async {
+    final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+        'dev.flutter.pigeon.NullableArgHostApi.doit', codec,
+        binaryMessenger: _binaryMessenger);
+    final Map<Object?, Object?>? replyMap =
+        await channel.send(<Object?>[arg_x]) 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 int?)!;
+    }
+  }
+}
+
+class _NullableArgFlutterApiCodec extends StandardMessageCodec {
+  const _NullableArgFlutterApiCodec();
+}
+
+abstract class NullableArgFlutterApi {
+  static const MessageCodec<Object?> codec = _NullableArgFlutterApiCodec();
+
+  int doit(int? x);
+  static void setup(NullableArgFlutterApi? api,
+      {BinaryMessenger? binaryMessenger}) {
+    {
+      final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+          'dev.flutter.pigeon.NullableArgFlutterApi.doit', codec,
+          binaryMessenger: binaryMessenger);
+      if (api == null) {
+        channel.setMessageHandler(null);
+      } else {
+        channel.setMessageHandler((Object? message) async {
+          assert(message != null,
+              'Argument for dev.flutter.pigeon.NullableArgFlutterApi.doit was null.');
+          final List<Object?> args = (message as List<Object?>?)!;
+          final int? arg_x = (args[0] as int?);
+          assert(arg_x != null,
+              'Argument for dev.flutter.pigeon.NullableArgFlutterApi.doit was null, expected non-null int.');
+          final int output = api.doit(arg_x!);
+          return output;
+        });
+      }
+    }
+  }
+}
diff --git a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/primitive.dart b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/primitive.dart
index b71adc3..bd94b3c 100644
--- a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/primitive.dart
+++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/primitive.dart
@@ -32,7 +32,7 @@
         'dev.flutter.pigeon.PrimitiveHostApi.anInt', codec,
         binaryMessenger: _binaryMessenger);
     final Map<Object?, Object?>? replyMap =
-        await channel.send(<Object>[arg_value]) as Map<Object?, Object?>?;
+        await channel.send(<Object?>[arg_value]) as Map<Object?, Object?>?;
     if (replyMap == null) {
       throw PlatformException(
         code: 'channel-error',
@@ -61,7 +61,7 @@
         'dev.flutter.pigeon.PrimitiveHostApi.aBool', codec,
         binaryMessenger: _binaryMessenger);
     final Map<Object?, Object?>? replyMap =
-        await channel.send(<Object>[arg_value]) as Map<Object?, Object?>?;
+        await channel.send(<Object?>[arg_value]) as Map<Object?, Object?>?;
     if (replyMap == null) {
       throw PlatformException(
         code: 'channel-error',
@@ -90,7 +90,7 @@
         'dev.flutter.pigeon.PrimitiveHostApi.aString', codec,
         binaryMessenger: _binaryMessenger);
     final Map<Object?, Object?>? replyMap =
-        await channel.send(<Object>[arg_value]) as Map<Object?, Object?>?;
+        await channel.send(<Object?>[arg_value]) as Map<Object?, Object?>?;
     if (replyMap == null) {
       throw PlatformException(
         code: 'channel-error',
@@ -119,7 +119,7 @@
         'dev.flutter.pigeon.PrimitiveHostApi.aDouble', codec,
         binaryMessenger: _binaryMessenger);
     final Map<Object?, Object?>? replyMap =
-        await channel.send(<Object>[arg_value]) as Map<Object?, Object?>?;
+        await channel.send(<Object?>[arg_value]) as Map<Object?, Object?>?;
     if (replyMap == null) {
       throw PlatformException(
         code: 'channel-error',
@@ -148,7 +148,7 @@
         'dev.flutter.pigeon.PrimitiveHostApi.aMap', codec,
         binaryMessenger: _binaryMessenger);
     final Map<Object?, Object?>? replyMap =
-        await channel.send(<Object>[arg_value]) as Map<Object?, Object?>?;
+        await channel.send(<Object?>[arg_value]) as Map<Object?, Object?>?;
     if (replyMap == null) {
       throw PlatformException(
         code: 'channel-error',
@@ -177,7 +177,7 @@
         'dev.flutter.pigeon.PrimitiveHostApi.aList', codec,
         binaryMessenger: _binaryMessenger);
     final Map<Object?, Object?>? replyMap =
-        await channel.send(<Object>[arg_value]) as Map<Object?, Object?>?;
+        await channel.send(<Object?>[arg_value]) as Map<Object?, Object?>?;
     if (replyMap == null) {
       throw PlatformException(
         code: 'channel-error',
@@ -206,7 +206,7 @@
         'dev.flutter.pigeon.PrimitiveHostApi.anInt32List', codec,
         binaryMessenger: _binaryMessenger);
     final Map<Object?, Object?>? replyMap =
-        await channel.send(<Object>[arg_value]) as Map<Object?, Object?>?;
+        await channel.send(<Object?>[arg_value]) as Map<Object?, Object?>?;
     if (replyMap == null) {
       throw PlatformException(
         code: 'channel-error',
@@ -235,7 +235,7 @@
         'dev.flutter.pigeon.PrimitiveHostApi.aBoolList', codec,
         binaryMessenger: _binaryMessenger);
     final Map<Object?, Object?>? replyMap =
-        await channel.send(<Object>[arg_value]) as Map<Object?, Object?>?;
+        await channel.send(<Object?>[arg_value]) as Map<Object?, Object?>?;
     if (replyMap == null) {
       throw PlatformException(
         code: 'channel-error',
@@ -264,7 +264,7 @@
         'dev.flutter.pigeon.PrimitiveHostApi.aStringIntMap', codec,
         binaryMessenger: _binaryMessenger);
     final Map<Object?, Object?>? replyMap =
-        await channel.send(<Object>[arg_value]) as Map<Object?, Object?>?;
+        await channel.send(<Object?>[arg_value]) as Map<Object?, Object?>?;
     if (replyMap == null) {
       throw PlatformException(
         code: 'channel-error',
diff --git a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/test/null_safe_test.dart b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/test/null_safe_test.dart
index e412b78..ca4479e 100644
--- a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/test/null_safe_test.dart
+++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/test/null_safe_test.dart
@@ -7,6 +7,7 @@
 import 'package:flutter/services.dart';
 import 'package:flutter_test/flutter_test.dart';
 import 'package:flutter_unit_tests/null_safe_pigeon.dart';
+import 'package:flutter_unit_tests/nullable_returns.gen.dart';
 import 'package:mockito/annotations.dart';
 import 'package:mockito/mockito.dart';
 import 'null_safe_test.mocks.dart';
@@ -89,4 +90,16 @@
     expect(() async => api.anInt(1),
         throwsA(const TypeMatcher<PlatformException>()));
   });
+
+  test('send null parameter', () async {
+    final BinaryMessenger mockMessenger = MockBinaryMessenger();
+    const String channel = 'dev.flutter.pigeon.NullableArgHostApi.doit';
+    when(mockMessenger.send(channel, any))
+        .thenAnswer((Invocation realInvocation) async {
+      return Api.codec.encodeMessage(<String?, Object?>{'result': 123});
+    });
+    final NullableArgHostApi api =
+        NullableArgHostApi(binaryMessenger: mockMessenger);
+    expect(await api.doit(null), 123);
+  });
 }
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 ed58c5c..aea750f 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
@@ -3,11 +3,13 @@
 	archiveVersion = 1;
 	classes = {
 	};
-	objectVersion = 46;
+	objectVersion = 50;
 	objects = {
 
 /* Begin PBXBuildFile section */
 		0D02163D27BC7B48009BD76F /* nullable_returns.gen.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D02163C27BC7B48009BD76F /* nullable_returns.gen.m */; };
+		0D36469D27C6BE3C0069B7BF /* NullableReturnsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D36469C27C6BE3C0069B7BF /* NullableReturnsTest.m */; };
+		0D3646A027C6DCEC0069B7BF /* MockBinaryMessenger.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D36469F27C6DCEC0069B7BF /* MockBinaryMessenger.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 */; };
@@ -67,6 +69,9 @@
 /* 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>"; };
+		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>"; };
 		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>"; };
@@ -146,6 +151,7 @@
 		0D50127323FF75B100CD5B95 /* RunnerTests */ = {
 			isa = PBXGroup;
 			children = (
+				0D36469C27C6BE3C0069B7BF /* NullableReturnsTest.m */,
 				0D50127423FF75B100CD5B95 /* RunnerTests.m */,
 				0D50127623FF75B100CD5B95 /* Info.plist */,
 				0D8C35EA25D45A7900B76435 /* AsyncHandlersTest.m */,
@@ -159,6 +165,8 @@
 				0DA5DFD926CC3B3700D2354B /* HandlerBinaryMessenger.h */,
 				0DA5DFDA26CC3B3700D2354B /* HandlerBinaryMessenger.m */,
 				0DBD8C3D279B73F700E4FDBA /* NonNullFieldsTest.m */,
+				0D36469E27C6DCEC0069B7BF /* MockBinaryMessenger.h */,
+				0D36469F27C6DCEC0069B7BF /* MockBinaryMessenger.m */,
 			);
 			path = RunnerTests;
 			sourceTree = "<group>";
@@ -388,8 +396,10 @@
 				0DF4E5C5266ECF4A00AEA855 /* AllDatatypesTest.m in Sources */,
 				0DF4E5C8266ED80900AEA855 /* EchoMessenger.m in Sources */,
 				0DA5DFD826CC3A2100D2354B /* MultipleArityTest.m in Sources */,
+				0D36469D27C6BE3C0069B7BF /* NullableReturnsTest.m in Sources */,
 				0DF4E5CB266FDAE300AEA855 /* EnumTest.m in Sources */,
 				0D8C35EB25D45A7900B76435 /* AsyncHandlersTest.m in Sources */,
+				0D3646A027C6DCEC0069B7BF /* MockBinaryMessenger.m in Sources */,
 				0D6FD3C726A777C00046D8BD /* PrimitiveTest.m in Sources */,
 				0D7A910A268D4A050056B5E1 /* ListTest.m in Sources */,
 				0DA5DFDB26CC3B3700D2354B /* HandlerBinaryMessenger.m in Sources */,
diff --git a/packages/pigeon/platform_tests/ios_unit_tests/ios/RunnerTests/AsyncHandlersTest.m b/packages/pigeon/platform_tests/ios_unit_tests/ios/RunnerTests/AsyncHandlersTest.m
index 398aeb3..90d3fdd 100644
--- a/packages/pigeon/platform_tests/ios_unit_tests/ios/RunnerTests/AsyncHandlersTest.m
+++ b/packages/pigeon/platform_tests/ios_unit_tests/ios/RunnerTests/AsyncHandlersTest.m
@@ -4,6 +4,7 @@
 
 #import <Flutter/Flutter.h>
 #import <XCTest/XCTest.h>
+#import "MockBinaryMessenger.h"
 #import "async_handlers.gen.h"
 
 ///////////////////////////////////////////////////////////////////////////////////////////
@@ -13,51 +14,6 @@
 @end
 
 ///////////////////////////////////////////////////////////////////////////////////////////
-@interface MockBinaryMessenger : NSObject <FlutterBinaryMessenger>
-@property(nonatomic, copy) NSNumber *result;
-@property(nonatomic, retain) NSObject<FlutterMessageCodec> *codec;
-@property(nonatomic, retain) NSMutableDictionary<NSString *, FlutterBinaryMessageHandler> *handlers;
-- (instancetype)init NS_UNAVAILABLE;
-@end
-
-///////////////////////////////////////////////////////////////////////////////////////////
-@implementation MockBinaryMessenger
-
-- (instancetype)initWithCodec:(NSObject<FlutterMessageCodec> *)codec {
-  self = [super init];
-  if (self) {
-    _codec = codec;
-    _handlers = [[NSMutableDictionary alloc] init];
-  }
-  return self;
-}
-
-- (void)cleanupConnection:(FlutterBinaryMessengerConnection)connection {
-}
-
-- (void)sendOnChannel:(nonnull NSString *)channel message:(NSData *_Nullable)message {
-}
-
-- (void)sendOnChannel:(nonnull NSString *)channel
-              message:(NSData *_Nullable)message
-          binaryReply:(FlutterBinaryReply _Nullable)callback {
-  if (self.result) {
-    Value *output = [[Value alloc] init];
-    output.number = self.result;
-    callback([_codec encode:output]);
-  }
-}
-
-- (FlutterBinaryMessengerConnection)setMessageHandlerOnChannel:(nonnull NSString *)channel
-                                          binaryMessageHandler:
-                                              (FlutterBinaryMessageHandler _Nullable)handler {
-  _handlers[channel] = [handler copy];
-  return _handlers.count;
-}
-
-@end
-
-///////////////////////////////////////////////////////////////////////////////////////////
 @interface MockApi2Host : NSObject <Api2Host>
 @property(nonatomic, copy) NSNumber *output;
 @property(nonatomic, retain) FlutterError *voidVoidError;
@@ -66,7 +22,7 @@
 ///////////////////////////////////////////////////////////////////////////////////////////
 @implementation MockApi2Host
 
-- (void)calculateValue:(Value *_Nullable)input
+- (void)calculateValue:(Value *)input
             completion:(nonnull void (^)(Value *_Nullable, FlutterError *_Nullable))completion {
   if (self.output) {
     Value *output = [[Value alloc] init];
@@ -93,7 +49,7 @@
 - (void)testAsyncHost2Flutter {
   MockBinaryMessenger *binaryMessenger =
       [[MockBinaryMessenger alloc] initWithCodec:Api2FlutterGetCodec()];
-  binaryMessenger.result = @(2);
+  binaryMessenger.result = [Value makeWithNumber:@(2)];
   Api2Flutter *api2Flutter = [[Api2Flutter alloc] initWithBinaryMessenger:binaryMessenger];
   Value *input = [[Value alloc] init];
   input.number = @(1);
diff --git a/packages/pigeon/platform_tests/ios_unit_tests/ios/RunnerTests/MockBinaryMessenger.h b/packages/pigeon/platform_tests/ios_unit_tests/ios/RunnerTests/MockBinaryMessenger.h
new file mode 100644
index 0000000..1f76103
--- /dev/null
+++ b/packages/pigeon/platform_tests/ios_unit_tests/ios/RunnerTests/MockBinaryMessenger.h
@@ -0,0 +1,18 @@
+// 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 <Foundation/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface MockBinaryMessenger : NSObject <FlutterBinaryMessenger>
+@property(nonatomic, retain) NSObject *result;
+@property(nonatomic, retain) NSObject<FlutterMessageCodec> *codec;
+@property(nonatomic, retain) NSMutableDictionary<NSString *, FlutterBinaryMessageHandler> *handlers;
+- (instancetype)init NS_UNAVAILABLE;
+- (instancetype)initWithCodec:(NSObject<FlutterMessageCodec> *)codec NS_DESIGNATED_INITIALIZER;
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/packages/pigeon/platform_tests/ios_unit_tests/ios/RunnerTests/MockBinaryMessenger.m b/packages/pigeon/platform_tests/ios_unit_tests/ios/RunnerTests/MockBinaryMessenger.m
new file mode 100644
index 0000000..7b9e9fd
--- /dev/null
+++ b/packages/pigeon/platform_tests/ios_unit_tests/ios/RunnerTests/MockBinaryMessenger.m
@@ -0,0 +1,42 @@
+// 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 "MockBinaryMessenger.h"
+
+@implementation MockBinaryMessenger
+
+- (instancetype)initWithCodec:(NSObject<FlutterMessageCodec> *)codec {
+  self = [super init];
+  if (self) {
+    _codec = codec;
+    _handlers = [[NSMutableDictionary alloc] init];
+  }
+  return self;
+}
+
+- (void)cleanupConnection:(FlutterBinaryMessengerConnection)connection {
+}
+
+- (void)sendOnChannel:(nonnull NSString *)channel message:(NSData *_Nullable)message {
+}
+
+- (void)sendOnChannel:(nonnull NSString *)channel
+              message:(NSData *_Nullable)message
+          binaryReply:(FlutterBinaryReply _Nullable)callback {
+  if (self.result) {
+    callback([_codec encode:self.result]);
+  }
+}
+
+- (FlutterBinaryMessengerConnection)setMessageHandlerOnChannel:(nonnull NSString *)channel
+                                          binaryMessageHandler:
+                                              (FlutterBinaryMessageHandler _Nullable)handler {
+  _handlers[channel] = [handler copy];
+  return _handlers.count;
+}
+
+- (void)cleanUpConnection:(FlutterBinaryMessengerConnection)connection {
+}
+
+@end
diff --git a/packages/pigeon/platform_tests/ios_unit_tests/ios/RunnerTests/NullableReturnsTest.m b/packages/pigeon/platform_tests/ios_unit_tests/ios/RunnerTests/NullableReturnsTest.m
new file mode 100644
index 0000000..df344b8
--- /dev/null
+++ b/packages/pigeon/platform_tests/ios_unit_tests/ios/RunnerTests/NullableReturnsTest.m
@@ -0,0 +1,65 @@
+// 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 <XCTest/XCTest.h>
+#import "EchoMessenger.h"
+#import "MockBinaryMessenger.h"
+#import "nullable_returns.gen.h"
+
+///////////////////////////////////////////////////////////////////////////////////////////
+@interface MockNullableArgHostApi : NSObject <NRNullableArgHostApi>
+@property(nonatomic, assign) BOOL didCall;
+@property(nonatomic, copy) NSNumber* x;
+@end
+
+///////////////////////////////////////////////////////////////////////////////////////////
+@implementation MockNullableArgHostApi
+- (nullable NSNumber*)doitX:(NSNumber* _Nullable)x
+                      error:(FlutterError* _Nullable __autoreleasing* _Nonnull)error {
+  _didCall = YES;
+  self.x = x;
+  return x;
+}
+
+@end
+
+///////////////////////////////////////////////////////////////////////////////////////////
+@interface NullableReturnsTest : XCTestCase
+@end
+
+///////////////////////////////////////////////////////////////////////////////////////////
+@implementation NullableReturnsTest
+
+- (void)testNullableParameterWithFlutterApi {
+  EchoBinaryMessenger* binaryMessenger =
+      [[EchoBinaryMessenger alloc] initWithCodec:NRNullableArgFlutterApiGetCodec()];
+  NRNullableArgFlutterApi* api =
+      [[NRNullableArgFlutterApi alloc] initWithBinaryMessenger:binaryMessenger];
+  XCTestExpectation* expectation = [self expectationWithDescription:@"callback"];
+  [api doitX:nil
+      completion:^(NSNumber* _Nonnull result, NSError* _Nullable error) {
+        XCTAssertNil(result);
+        [expectation fulfill];
+      }];
+  [self waitForExpectations:@[ expectation ] timeout:1.0];
+}
+
+- (void)testNullableParameterWithHostApi {
+  MockNullableArgHostApi* api = [[MockNullableArgHostApi alloc] init];
+  MockBinaryMessenger* binaryMessenger =
+      [[MockBinaryMessenger alloc] initWithCodec:NRNullableArgHostApiGetCodec()];
+  NSString* channel = @"dev.flutter.pigeon.NullableArgHostApi.doit";
+  NRNullableArgHostApiSetup(binaryMessenger, api);
+  XCTAssertNotNil(binaryMessenger.handlers[channel]);
+  XCTestExpectation* expectation = [self expectationWithDescription:@"callback"];
+  NSData* arguments = [NRNullableArgHostApiGetCodec() encode:@[ [NSNull null] ]];
+  binaryMessenger.handlers[channel](arguments, ^(NSData* data) {
+    [expectation fulfill];
+  });
+  XCTAssertTrue(api.didCall);
+  XCTAssertNil(api.x);
+  [self waitForExpectations:@[ expectation ] timeout:1.0];
+}
+
+@end
diff --git a/packages/pigeon/pubspec.yaml b/packages/pigeon/pubspec.yaml
index 686205b..12f922e 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: 1.0.19 # This must match the version in lib/generator_tools.dart
+version: 2.0.0 # This must match the version in lib/generator_tools.dart
 
 environment:
   sdk: ">=2.12.0 <3.0.0"
diff --git a/packages/pigeon/test/dart_generator_test.dart b/packages/pigeon/test/dart_generator_test.dart
index 647376b..00c8158 100644
--- a/packages/pigeon/test/dart_generator_test.dart
+++ b/packages/pigeon/test/dart_generator_test.dart
@@ -125,7 +125,7 @@
     final String code = sink.toString();
     expect(code, contains('class Api'));
     expect(code, contains('Future<int> add(int arg_x, int arg_y)'));
-    expect(code, contains('await channel.send(<Object>[arg_x, arg_y])'));
+    expect(code, contains('await channel.send(<Object?>[arg_x, arg_y])'));
   });
 
   test('flutter multiple args', () {
@@ -1064,4 +1064,56 @@
         contains(
             'Host platform returned null value for non-null return value.'));
   });
+
+  test('nullable argument host', () {
+    final Root root = Root(
+      apis: <Api>[
+        Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
+          Method(
+              name: 'doit',
+              returnType: const TypeDeclaration.voidDeclaration(),
+              arguments: <NamedType>[
+                NamedType(
+                    name: 'foo',
+                    type: const TypeDeclaration(
+                      baseName: 'int',
+                      isNullable: true,
+                    )),
+              ])
+        ])
+      ],
+      classes: <Class>[],
+      enums: <Enum>[],
+    );
+    final StringBuffer sink = StringBuffer();
+    generateDart(const DartOptions(isNullSafe: true), root, sink);
+    final String code = sink.toString();
+    expect(code, contains('Future<void> doit(int? arg_foo) async {'));
+  });
+
+  test('nullable argument flutter', () {
+    final Root root = Root(
+      apis: <Api>[
+        Api(name: 'Api', location: ApiLocation.flutter, methods: <Method>[
+          Method(
+              name: 'doit',
+              returnType: const TypeDeclaration.voidDeclaration(),
+              arguments: <NamedType>[
+                NamedType(
+                    name: 'foo',
+                    type: const TypeDeclaration(
+                      baseName: 'int',
+                      isNullable: true,
+                    )),
+              ])
+        ])
+      ],
+      classes: <Class>[],
+      enums: <Enum>[],
+    );
+    final StringBuffer sink = StringBuffer();
+    generateDart(const DartOptions(isNullSafe: true), root, sink);
+    final String code = sink.toString();
+    expect(code, contains('void doit(int? foo);'));
+  });
 }
diff --git a/packages/pigeon/test/java_generator_test.dart b/packages/pigeon/test/java_generator_test.dart
index 95f4652..57c4ef9 100644
--- a/packages/pigeon/test/java_generator_test.dart
+++ b/packages/pigeon/test/java_generator_test.dart
@@ -521,7 +521,9 @@
     expect(code, contains('public interface Result<T> {'));
     expect(code, contains('void error(Throwable error);'));
     expect(
-        code, contains('void doSomething(Input arg, Result<Output> result);'));
+        code,
+        contains(
+            'void doSomething(@NonNull Input arg, Result<Output> result);'));
     expect(code, contains('api.doSomething(argArg, resultCallback);'));
     expect(code, contains('channel.setMessageHandler(null)'));
   });
@@ -719,7 +721,7 @@
     const JavaOptions javaOptions = JavaOptions(className: 'Messages');
     generateJava(javaOptions, root, sink);
     final String code = sink.toString();
-    expect(code, contains('doit(List<Long> arg'));
+    expect(code, contains('doit(@NonNull List<Long> arg'));
   });
 
   test('flutter generics argument', () {
@@ -749,7 +751,7 @@
     const JavaOptions javaOptions = JavaOptions(className: 'Messages');
     generateJava(javaOptions, root, sink);
     final String code = sink.toString();
-    expect(code, contains('doit(List<Long> arg'));
+    expect(code, contains('doit(@NonNull List<Long> arg'));
   });
 
   test('host generics return', () {
@@ -829,13 +831,15 @@
     generateJava(javaOptions, root, sink);
     final String code = sink.toString();
     expect(code, contains('class Messages'));
-    expect(code, contains('Long add(Long x, Long y)'));
+    expect(code, contains('Long add(@NonNull Long x, @NonNull Long y)'));
     expect(
         code, contains('ArrayList<Object> args = (ArrayList<Object>)message;'));
     expect(code, contains('Number xArg = (Number)args.get(0)'));
     expect(code, contains('Number yArg = (Number)args.get(1)'));
-    expect(code,
-        contains('Long output = api.add(xArg.longValue(), yArg.longValue())'));
+    expect(
+        code,
+        contains(
+            'Long output = api.add((xArg == null) ? null : xArg.longValue(), (yArg == null) ? null : yArg.longValue())'));
   });
 
   test('flutter multiple args', () {
@@ -868,7 +872,7 @@
     expect(
         code,
         contains(
-            'public void add(Long xArg, Long yArg, Reply<Long> callback)'));
+            'public void add(@NonNull Long xArg, @NonNull Long yArg, Reply<Long> callback)'));
     expect(
         code,
         contains(
@@ -922,4 +926,61 @@
     // Java doesn't accept nullability annotations in type arguments.
     expect(code, contains('Result<Long>'));
   });
+
+  test('nullable argument host', () {
+    final Root root = Root(
+      apis: <Api>[
+        Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
+          Method(
+              name: 'doit',
+              returnType: const TypeDeclaration.voidDeclaration(),
+              arguments: <NamedType>[
+                NamedType(
+                    name: 'foo',
+                    type: const TypeDeclaration(
+                      baseName: 'int',
+                      isNullable: true,
+                    )),
+              ])
+        ])
+      ],
+      classes: <Class>[],
+      enums: <Enum>[],
+    );
+    final StringBuffer sink = StringBuffer();
+    const JavaOptions javaOptions = JavaOptions(className: 'Messages');
+    generateJava(javaOptions, root, sink);
+    final String code = sink.toString();
+    expect(code, contains('  void doit(@Nullable Long foo);'));
+  });
+
+  test('nullable argument flutter', () {
+    final Root root = Root(
+      apis: <Api>[
+        Api(name: 'Api', location: ApiLocation.flutter, methods: <Method>[
+          Method(
+              name: 'doit',
+              returnType: const TypeDeclaration.voidDeclaration(),
+              arguments: <NamedType>[
+                NamedType(
+                    name: 'foo',
+                    type: const TypeDeclaration(
+                      baseName: 'int',
+                      isNullable: true,
+                    )),
+              ])
+        ])
+      ],
+      classes: <Class>[],
+      enums: <Enum>[],
+    );
+    final StringBuffer sink = StringBuffer();
+    const JavaOptions javaOptions = JavaOptions(className: 'Messages');
+    generateJava(javaOptions, root, sink);
+    final String code = sink.toString();
+    expect(
+        code,
+        contains(
+            'public void doit(@Nullable Long fooArg, Reply<Void> callback) {'));
+  });
 }
diff --git a/packages/pigeon/test/objc_generator_test.dart b/packages/pigeon/test/objc_generator_test.dart
index c03738c..7044ecf 100644
--- a/packages/pigeon/test/objc_generator_test.dart
+++ b/packages/pigeon/test/objc_generator_test.dart
@@ -466,7 +466,7 @@
     generateObjcSource(const ObjcOptions(prefix: 'ABC'), root, sink);
     final String code = sink.toString();
     expect(code, contains('ABCInput fromMap'));
-    expect(code, matches(r'ABCInput.*=.*args\[0\]'));
+    expect(code, matches(r'ABCInput.*=.*args.*0.*\;'));
     expect(code, contains('void ABCApiSetup('));
   });
 
@@ -848,7 +848,7 @@
                   name: 'foo',
                   type: const TypeDeclaration(
                       baseName: 'Map',
-                      isNullable: true,
+                      isNullable: false,
                       typeArguments: <TypeDeclaration>[
                         TypeDeclaration(baseName: 'String', isNullable: true),
                         TypeDeclaration(baseName: 'Object', isNullable: true),
@@ -900,7 +900,7 @@
     expect(
         code,
         contains(
-            '(void)doSomethingInput:(nullable ABCInput *)input completion:(void(^)(FlutterError *_Nullable))completion'));
+            '(void)doSomethingInput:(ABCInput *)input completion:(void(^)(FlutterError *_Nullable))completion'));
   });
 
   test('async output(input) HostApi header', () {
@@ -942,7 +942,7 @@
     expect(
         code,
         contains(
-            '(void)doSomethingInput:(nullable ABCInput *)input completion:(void(^)(ABCOutput *_Nullable, FlutterError *_Nullable))completion'));
+            '(void)doSomethingInput:(ABCInput *)input completion:(void(^)(ABCOutput *_Nullable, FlutterError *_Nullable))completion'));
   });
 
   test('async output(void) HostApi header', () {
@@ -1220,7 +1220,10 @@
       generateObjcSource(
           const ObjcOptions(header: 'foo.h', prefix: 'ABC'), root, sink);
       final String code = sink.toString();
-      expect(code, contains('NSArray<NSNumber *> *arg_arg = args[0]'));
+      expect(
+          code,
+          contains(
+              'NSArray<NSNumber *> *arg_arg = GetNullableObjectAtIndex(args, 0)'));
     }
   });
 
@@ -1408,8 +1411,10 @@
           const ObjcOptions(header: 'foo.h', prefix: 'ABC'), root, sink);
       final String code = sink.toString();
       expect(code, contains('NSArray *args = message;'));
-      expect(code, contains('NSNumber *arg_x = args[0];'));
-      expect(code, contains('NSNumber *arg_y = args[1];'));
+      expect(code,
+          contains('NSNumber *arg_x = GetNullableObjectAtIndex(args, 0);'));
+      expect(code,
+          contains('NSNumber *arg_y = GetNullableObjectAtIndex(args, 1);'));
       expect(code,
           contains('NSNumber *output = [api addX:arg_x y:arg_y error:&error]'));
     }
@@ -1443,7 +1448,7 @@
       expect(
           code,
           contains(
-              '- (void)addX:(nullable NSNumber *)x y:(nullable NSNumber *)y completion:(void(^)(NSNumber *_Nullable, FlutterError *_Nullable))completion;'));
+              '- (void)addX:(NSNumber *)x y:(NSNumber *)y completion:(void(^)(NSNumber *_Nullable, FlutterError *_Nullable))completion;'));
     }
     {
       final StringBuffer sink = StringBuffer();
@@ -1451,8 +1456,10 @@
           const ObjcOptions(header: 'foo.h', prefix: 'ABC'), root, sink);
       final String code = sink.toString();
       expect(code, contains('NSArray *args = message;'));
-      expect(code, contains('NSNumber *arg_x = args[0];'));
-      expect(code, contains('NSNumber *arg_y = args[1];'));
+      expect(code,
+          contains('NSNumber *arg_x = GetNullableObjectAtIndex(args, 0);'));
+      expect(code,
+          contains('NSNumber *arg_y = GetNullableObjectAtIndex(args, 1);'));
       expect(code, contains('[api addX:arg_x y:arg_y completion:'));
     }
   });
@@ -1496,7 +1503,10 @@
           code,
           contains(
               '- (void)addX:(NSNumber *)arg_x y:(NSNumber *)arg_y completion:(void(^)(NSNumber *_Nullable, NSError *_Nullable))completion {'));
-      expect(code, contains('[channel sendMessage:@[arg_x, arg_y] reply:'));
+      expect(
+          code,
+          contains(
+              '[channel sendMessage:@[(arg_x == nil) ? [NSNull null] : arg_x, (arg_y == nil) ? [NSNull null] : arg_y] reply:'));
     }
   });
 
@@ -1652,4 +1662,73 @@
     final String code = sink.toString();
     expect(code, matches(r'nullable NSNumber.*doitWithError'));
   });
+
+  test('nullable argument host', () {
+    final Root root = Root(
+      apis: <Api>[
+        Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
+          Method(
+              name: 'doit',
+              returnType: const TypeDeclaration.voidDeclaration(),
+              arguments: <NamedType>[
+                NamedType(
+                    name: 'foo',
+                    type: const TypeDeclaration(
+                      baseName: 'int',
+                      isNullable: true,
+                    )),
+              ])
+        ])
+      ],
+      classes: <Class>[],
+      enums: <Enum>[],
+    );
+    {
+      final StringBuffer sink = StringBuffer();
+      generateObjcHeader(const ObjcOptions(), root, sink);
+      final String code = sink.toString();
+      expect(code, contains('doitFoo:(nullable NSNumber *)foo'));
+    }
+    {
+      final StringBuffer sink = StringBuffer();
+      generateObjcSource(const ObjcOptions(), root, sink);
+      final String code = sink.toString();
+      expect(code,
+          contains('NSNumber *arg_foo = GetNullableObjectAtIndex(args, 0);'));
+    }
+  });
+
+  test('nullable argument flutter', () {
+    final Root root = Root(
+      apis: <Api>[
+        Api(name: 'Api', location: ApiLocation.flutter, methods: <Method>[
+          Method(
+              name: 'doit',
+              returnType: const TypeDeclaration.voidDeclaration(),
+              arguments: <NamedType>[
+                NamedType(
+                    name: 'foo',
+                    type: const TypeDeclaration(
+                      baseName: 'int',
+                      isNullable: true,
+                    )),
+              ])
+        ])
+      ],
+      classes: <Class>[],
+      enums: <Enum>[],
+    );
+    {
+      final StringBuffer sink = StringBuffer();
+      generateObjcHeader(const ObjcOptions(), root, sink);
+      final String code = sink.toString();
+      expect(code, contains('doitFoo:(nullable NSNumber *)foo'));
+    }
+    {
+      final StringBuffer sink = StringBuffer();
+      generateObjcSource(const ObjcOptions(), root, sink);
+      final String code = sink.toString();
+      expect(code, contains('- (void)doitFoo:(nullable NSNumber *)arg_foo'));
+    }
+  });
 }
diff --git a/packages/pigeon/test/pigeon_lib_test.dart b/packages/pigeon/test/pigeon_lib_test.dart
index 1d21f46..9922bc5 100644
--- a/packages/pigeon/test/pigeon_lib_test.dart
+++ b/packages/pigeon/test/pigeon_lib_test.dart
@@ -564,23 +564,6 @@
     expect(results.errors[0].message, contains('Constructor'));
   });
 
-  test('nullable api arguments', () {
-    const String code = '''
-class Foo {
-  int? x;
-}
-
-@HostApi()
-abstract class Api {
-  Foo doit(Foo foo1, Foo? foo2);
-}
-''';
-    final ParseResults results = _parseSource(code);
-    expect(results.errors.length, 1);
-    expect(results.errors[0].lineNumber, 7);
-    expect(results.errors[0].message, contains('Nullable'));
-  });
-
   test('test invalid import', () {
     const String code = 'import \'foo.dart\';\n';
     final ParseResults results = _parseSource(code);
@@ -1045,4 +1028,17 @@
     expect(results.errors.length, 0);
     expect(results.root.apis[0].methods[0].returnType.isNullable, isTrue);
   });
+
+  test('nullable parameters', () {
+    const String code = '''
+@HostApi()
+abstract class Api {
+  void calc(int? value);
+}
+''';
+    final ParseResults results = _parseSource(code);
+    expect(results.errors.length, 0);
+    expect(
+        results.root.apis[0].methods[0].arguments[0].type.isNullable, isTrue);
+  });
 }