[pigeon] Added non-null fields (#549)

* [pigeon] Added non-null types for fields
diff --git a/packages/pigeon/CHANGELOG.md b/packages/pigeon/CHANGELOG.md
index f4f3006..e85ec48 100644
--- a/packages/pigeon/CHANGELOG.md
+++ b/packages/pigeon/CHANGELOG.md
@@ -1,7 +1,12 @@
-## NEXT
+## 1.0.16
 
 * Updates behavior of run\_tests.dart with no arguments.
 * [debugging] Adds `ast_out` to help with debugging the compiler front-end.
+* [front-end, dart] Adds support for non-null fields in data classes in the
+  front-end parser and the Dart generator (unsupported languages ignore the
+  designation currently).
+* [front-end, dart, objc, java] Adds support for non-null fields in data
+  classes.
 
 ## 1.0.15
 
diff --git a/packages/pigeon/README.md b/packages/pigeon/README.md
index f9cc130..9dfcec7 100644
--- a/packages/pigeon/README.md
+++ b/packages/pigeon/README.md
@@ -64,8 +64,8 @@
    `void`.
 1) Generics are supported, but can currently only be used with nullable types
    (example: `List<int?>`).
-1) Fields on classes currently must be nullable, arguments and return values to
-   methods must be non-nullable.
+1) Arguments and return values to methods must be non-nullable.  Fields on
+   classes can be nullable or non-nullable.
 
 ## Supported Datatypes
 
@@ -120,8 +120,15 @@
 
 ### Null Safety (NNBD)
 
-Right now Pigeon supports generating null-safe code, but it doesn't yet support
-[non-null fields](https://github.com/flutter/flutter/issues/59118).
+Pigeon supports generating null-safe code, but it doesn't yet support:
+
+1) Nullable method parameters
+1) Nullable generics type arguments
+1) Nullable return values
+
+It does support:
+
+1) Nullable and Non-nullable class fields.
 
 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/bin/run_tests.dart b/packages/pigeon/bin/run_tests.dart
index 678df52..53e82c1 100644
--- a/packages/pigeon/bin/run_tests.dart
+++ b/packages/pigeon/bin/run_tests.dart
@@ -87,53 +87,47 @@
   return exitCode;
 }
 
+/// Generates multiple dart files based on the jobs defined in [jobs] which is
+/// in the format of (key: input pigeon file path, value: output dart file
+/// path).
+Future<int> _generateDart(Map<String, String> jobs) async {
+  for (final MapEntry<String, String> job in jobs.entries) {
+    // TODO(gaaclarke): Make this run the jobs in parallel.  A bug in Dart
+    // blocked this (https://github.com/dart-lang/pub/pull/3285).
+    final int result = await _runPigeon(
+        input: job.key, dartOut: job.value, streamOutput: false);
+    if (result != 0) {
+      return result;
+    }
+  }
+  return 0;
+}
+
 Future<int> _runFlutterUnitTests() async {
   const String flutterUnitTestsPath =
       'platform_tests/flutter_null_safe_unit_tests';
-  int generateCode = await _runPigeon(
-    input: 'pigeons/flutter_unittests.dart',
-    dartOut: '$flutterUnitTestsPath/lib/null_safe_pigeon.dart',
-  );
-  if (generateCode != 0) {
-    return generateCode;
-  }
-  generateCode = await _runPigeon(
-    input: 'pigeons/all_datatypes.dart',
-    dartOut: '$flutterUnitTestsPath/lib/all_datatypes.dart',
-  );
-  if (generateCode != 0) {
-    return generateCode;
-  }
-  generateCode = await _runPigeon(
-    input: 'pigeons/primitive.dart',
-    dartOut: '$flutterUnitTestsPath/lib/primitive.dart',
-  );
-  if (generateCode != 0) {
-    return generateCode;
-  }
-  generateCode = await _runPigeon(
-    input: 'pigeons/multiple_arity.dart',
-    dartOut: '$flutterUnitTestsPath/lib/multiple_arity.gen.dart',
-  );
+  final int generateCode = await _generateDart(<String, String>{
+    'pigeons/flutter_unittests.dart':
+        '$flutterUnitTestsPath/lib/null_safe_pigeon.dart',
+    'pigeons/all_datatypes.dart':
+        '$flutterUnitTestsPath/lib/all_datatypes.dart',
+    'pigeons/primitive.dart': '$flutterUnitTestsPath/lib/primitive.dart',
+    'pigeons/multiple_arity.dart':
+        '$flutterUnitTestsPath/lib/multiple_arity.gen.dart',
+    'pigeons/non_null_fields.dart':
+        '$flutterUnitTestsPath/lib/non_null_fields.gen.dart',
+  });
   if (generateCode != 0) {
     return generateCode;
   }
 
-  const List<String> testFiles = <String>[
-    'null_safe_test.dart',
-    'all_datatypes_test.dart',
-    'primitive_test.dart',
-    'multiple_arity_test.dart'
-  ];
-  for (final String testFile in testFiles) {
-    final int testCode = await _runProcess(
-      'flutter',
-      <String>['test', 'test/$testFile'],
-      workingDirectory: flutterUnitTestsPath,
-    );
-    if (testCode != 0) {
-      return testCode;
-    }
+  final int testCode = await _runProcess(
+    'flutter',
+    <String>['test'],
+    workingDirectory: flutterUnitTestsPath,
+  );
+  if (testCode != 0) {
+    return testCode;
   }
 
   return 0;
@@ -174,7 +168,8 @@
     String? cppHeaderOut,
     String? cppSourceOut,
     String? dartOut,
-    String? dartTestOut}) async {
+    String? dartTestOut,
+    bool streamOutput = true}) async {
   const bool hasDart = false;
   final List<String> args = <String>[
     'pub',
@@ -182,6 +177,8 @@
     'pigeon',
     '--input',
     input,
+    '--copyright_header',
+    './copyright_header.txt',
   ];
   if (cppHeaderOut != null) {
     args.addAll(<String>[
@@ -204,9 +201,16 @@
   if (!hasDart) {
     args.add('--one_language');
   }
-  final Process generate = await _streamOutput(Process.start('dart', args));
+  final Process generate = streamOutput
+      ? await _streamOutput(Process.start('dart', args))
+      : await Process.start('dart', args);
   final int generateCode = await generate.exitCode;
   if (generateCode != 0) {
+    if (!streamOutput) {
+      print('dart $args failed:');
+      generate.stdout.pipe(stdout);
+      generate.stderr.pipe(stderr);
+    }
     return generateCode;
   }
   return 0;
diff --git a/packages/pigeon/lib/dart_generator.dart b/packages/pigeon/lib/dart_generator.dart
index 625b1d0..29893dc 100644
--- a/packages/pigeon/lib/dart_generator.dart
+++ b/packages/pigeon/lib/dart_generator.dart
@@ -50,8 +50,13 @@
       .replaceAll(r"'", r"\'");
 }
 
+/// Calculates the name of the codec class that will be generated for [api].
 String _getCodecName(Api api) => '_${api.name}Codec';
 
+/// Writes the codec that will be used by [api].
+/// Example:
+///
+/// class FooCodec extends StandardMessageCodec {...}
 void _writeCodec(Indent indent, String codecName, Api api, Root root) {
   indent.write('class $codecName extends StandardMessageCodec ');
   indent.scoped('{', '}', () {
@@ -113,9 +118,12 @@
 String _getSafeArgumentName(int count, NamedType field) =>
     field.name.isEmpty ? 'arg$count' : 'arg_' + field.name;
 
+/// Generates an argument name if one isn't defined.
 String _getArgumentName(int count, NamedType field) =>
     field.name.isEmpty ? 'arg$count' : field.name;
 
+/// Generates the arguments code for [func]
+/// Example: (func, getArgumentName, nullTag) -> 'String? foo, int bar'
 String _getMethodArgumentsSignature(
   Method func,
   String Function(int index, NamedType arg) getArgumentName,
@@ -130,6 +138,15 @@
         }).join(', ');
 }
 
+/// Writes the code for host [Api], [api].
+/// Example:
+/// class FooCodec extends StandardMessageCodec {...}
+///
+/// class Foo {
+///   Foo(BinaryMessenger? binaryMessenger) {}
+///   static const MessageCodec<Object?> codec = FooCodec();
+///   Future<int> add(int x, int y) async {...}
+/// }
 void _writeHostApi(DartOptions opt, Indent indent, Api api, Root root) {
   assert(api.location == ApiLocation.host);
   final String codecName = _getCodecName(api);
@@ -207,6 +224,15 @@
   });
 }
 
+/// Writes the code for host [Api], [api].
+/// Example:
+/// class FooCodec extends StandardMessageCodec {...}
+///
+/// abstract class Foo {
+///   static const MessageCodec<Object?> codec = FooCodec();
+///   int add(int x, int y);
+///   static void setup(Foo api, {BinaryMessenger? binaryMessenger}) {...}
+/// }
 void _writeFlutterApi(
   DartOptions opt,
   Indent indent,
@@ -360,7 +386,8 @@
 }
 
 String _addGenericTypesNullable(NamedType field, String nullTag) {
-  return '${_addGenericTypes(field.type, nullTag)}$nullTag';
+  final String genericdType = _addGenericTypes(field.type, nullTag);
+  return field.type.isNullable ? '$genericdType$nullTag' : genericdType;
 }
 
 /// Generates Dart source code for the given AST represented by [root],
@@ -373,43 +400,54 @@
   final List<String> customEnumNames =
       root.enums.map((Enum x) => x.name).toList();
   final Indent indent = Indent(sink);
-  if (opt.copyrightHeader != null) {
-    addLines(indent, opt.copyrightHeader!, linePrefix: '// ');
+
+  void writeHeader() {
+    if (opt.copyrightHeader != null) {
+      addLines(indent, opt.copyrightHeader!, linePrefix: '// ');
+    }
+    indent.writeln('// $generatedCodeWarning');
+    indent.writeln('// $seeAlsoWarning');
+    indent.writeln(
+      '// 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',
+    );
+    indent.writeln('// @dart = ${opt.isNullSafe ? '2.12' : '2.8'}');
   }
-  indent.writeln('// $generatedCodeWarning');
-  indent.writeln('// $seeAlsoWarning');
-  indent.writeln(
-    '// 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',
-  );
-  indent.writeln('// @dart = ${opt.isNullSafe ? '2.12' : '2.8'}');
-  indent.writeln('import \'dart:async\';');
-  indent.writeln(
-    'import \'dart:typed_data\' show Uint8List, Int32List, Int64List, Float64List;',
-  );
-  indent.addln('');
-  indent.writeln(
-      'import \'package:flutter/foundation.dart\' show WriteBuffer, ReadBuffer;');
-  indent.writeln('import \'package:flutter/services.dart\';');
-  for (final Enum anEnum in root.enums) {
-    indent.writeln('');
-    indent.write('enum ${anEnum.name} ');
-    indent.scoped('{', '}', () {
-      for (final String member in anEnum.members) {
-        indent.writeln('$member,');
-      }
-    });
+
+  void writeEnums() {
+    for (final Enum anEnum in root.enums) {
+      indent.writeln('');
+      indent.write('enum ${anEnum.name} ');
+      indent.scoped('{', '}', () {
+        for (final String member in anEnum.members) {
+          indent.writeln('$member,');
+        }
+      });
+    }
   }
-  for (final Class klass in root.classes) {
-    indent.writeln('');
-    indent.write('class ${klass.name} ');
-    indent.scoped('{', '}', () {
-      for (final NamedType field in klass.fields) {
-        final String datatype = _addGenericTypesNullable(field, nullTag);
-        indent.writeln('$datatype ${field.name};');
-      }
-      if (klass.fields.isNotEmpty) {
-        indent.writeln('');
-      }
+
+  void writeImports() {
+    indent.writeln('import \'dart:async\';');
+    indent.writeln(
+      'import \'dart:typed_data\' show Uint8List, Int32List, Int64List, Float64List;',
+    );
+    indent.addln('');
+    indent.writeln(
+        'import \'package:flutter/foundation.dart\' show WriteBuffer, ReadBuffer;');
+    indent.writeln('import \'package:flutter/services.dart\';');
+  }
+
+  void writeDataClass(Class klass) {
+    void writeConstructor() {
+      indent.write(klass.name);
+      indent.scoped('({', '});', () {
+        for (final NamedType field in klass.fields) {
+          final String required = field.type.isNullable ? '' : 'required ';
+          indent.writeln('${required}this.${field.name},');
+        }
+      });
+    }
+
+    void writeEncode() {
       indent.write('Object encode() ');
       indent.scoped('{', '}', () {
         indent.writeln(
@@ -431,7 +469,43 @@
         }
         indent.writeln('return pigeonMap;');
       });
-      indent.writeln('');
+    }
+
+    void writeDecode() {
+      void writeValueDecode(NamedType field) {
+        if (customClassNames.contains(field.type.baseName)) {
+          indent.format('''
+pigeonMap['${field.name}'] != null
+\t\t? ${field.type.baseName}.decode(pigeonMap['${field.name}']$unwrapOperator)
+\t\t: null''', leadingSpace: false, trailingNewline: false);
+        } else if (customEnumNames.contains(field.type.baseName)) {
+          indent.format('''
+pigeonMap['${field.name}'] != null
+\t\t? ${field.type.baseName}.values[pigeonMap['${field.name}']$unwrapOperator as int]
+\t\t: null''', leadingSpace: false, trailingNewline: false);
+        } else if (field.type.typeArguments.isNotEmpty) {
+          final String genericType =
+              _makeGenericTypeArguments(field.type, nullTag);
+          final String castCall = _makeGenericCastCall(field.type, nullTag);
+          final String castCallPrefix =
+              field.type.isNullable ? nullTag : unwrapOperator;
+          indent.add(
+            '(pigeonMap[\'${field.name}\'] as $genericType$nullTag)$castCallPrefix$castCall',
+          );
+        } else {
+          final String genericdType = _addGenericTypesNullable(field, nullTag);
+          if (field.type.isNullable) {
+            indent.add(
+              'pigeonMap[\'${field.name}\'] as $genericdType',
+            );
+          } else {
+            indent.add(
+              'pigeonMap[\'${field.name}\']$unwrapOperator as $genericdType',
+            );
+          }
+        }
+      }
+
       indent.write(
         'static ${klass.name} decode(Object message) ',
       );
@@ -439,47 +513,54 @@
         indent.writeln(
           'final Map<Object$nullTag, Object$nullTag> pigeonMap = message as Map<Object$nullTag, Object$nullTag>;',
         );
-        indent.writeln('return ${klass.name}()');
-        indent.nest(1, () {
+        indent.write('return ${klass.name}');
+        indent.scoped('(', ');', () {
           for (int index = 0; index < klass.fields.length; index += 1) {
             final NamedType field = klass.fields[index];
-            indent.write('..${field.name} = ');
-            if (customClassNames.contains(field.type.baseName)) {
-              indent.format('''
-pigeonMap['${field.name}'] != null
-\t\t? ${field.type.baseName}.decode(pigeonMap['${field.name}']$unwrapOperator)
-\t\t: null''', leadingSpace: false, trailingNewline: false);
-            } else if (customEnumNames.contains(field.type.baseName)) {
-              indent.format('''
-pigeonMap['${field.name}'] != null
-\t\t? ${field.type.baseName}.values[pigeonMap['${field.name}']$unwrapOperator as int]
-\t\t: null''', leadingSpace: false, trailingNewline: false);
-            } else if (field.type.typeArguments.isNotEmpty) {
-              final String genericType =
-                  _makeGenericTypeArguments(field.type, nullTag);
-              final String castCall = _makeGenericCastCall(field.type, nullTag);
-              indent.add(
-                '(pigeonMap[\'${field.name}\'] as $genericType$nullTag)$nullTag$castCall',
-              );
-            } else {
-              indent.add(
-                'pigeonMap[\'${field.name}\'] as ${_addGenericTypesNullable(field, nullTag)}',
-              );
-            }
-            indent.addln(index == klass.fields.length - 1 ? ';' : '');
+            indent.write('${field.name}: ');
+            writeValueDecode(field);
+            indent.addln(',');
           }
         });
       });
+    }
+
+    indent.write('class ${klass.name} ');
+    indent.scoped('{', '}', () {
+      writeConstructor();
+      indent.addln('');
+      for (final NamedType field in klass.fields) {
+        final String datatype = _addGenericTypesNullable(field, nullTag);
+        indent.writeln('$datatype ${field.name};');
+      }
+      if (klass.fields.isNotEmpty) {
+        indent.writeln('');
+      }
+      writeEncode();
+      indent.writeln('');
+      writeDecode();
     });
   }
-  for (final Api api in root.apis) {
-    indent.writeln('');
+
+  void writeApi(Api api) {
     if (api.location == ApiLocation.host) {
       _writeHostApi(opt, indent, api, root);
     } else if (api.location == ApiLocation.flutter) {
       _writeFlutterApi(opt, indent, api, root);
     }
   }
+
+  writeHeader();
+  writeImports();
+  writeEnums();
+  for (final Class klass in root.classes) {
+    indent.writeln('');
+    writeDataClass(klass);
+  }
+  for (final Api api in root.apis) {
+    indent.writeln('');
+    writeApi(api);
+  }
 }
 
 /// Generates Dart source code for test support libraries based on the
diff --git a/packages/pigeon/lib/generator_tools.dart b/packages/pigeon/lib/generator_tools.dart
index e844daa..1dfbcdf 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.15';
+const String pigeonVersion = '1.0.16';
 
 /// Read all the content from [stdin] to a String.
 String readStdin() {
@@ -21,6 +21,9 @@
   return utf8.decode(bytes);
 }
 
+/// True if the generator line number should be printed out at the end of newlines.
+bool debugGenerators = false;
+
 /// A helper class for managing indentation, wrapping a [StringSink].
 class Indent {
   /// Constructor which takes a [StringSink] [Ident] will wrap.
@@ -30,7 +33,16 @@
   final StringSink _sink;
 
   /// String used for newlines (ex "\n").
-  final String newline = '\n';
+  String get newline {
+    if (debugGenerators) {
+      final List<String> frames = StackTrace.current.toString().split('\n');
+      return ' //' +
+          frames.firstWhere((String x) => x.contains('_generator.dart')) +
+          '\n';
+    } else {
+      return '\n';
+    }
+  }
 
   /// String used to represent a tab.
   final String tab = '  ';
diff --git a/packages/pigeon/lib/java_generator.dart b/packages/pigeon/lib/java_generator.dart
index c26205b..642d143 100644
--- a/packages/pigeon/lib/java_generator.dart
+++ b/packages/pigeon/lib/java_generator.dart
@@ -53,8 +53,12 @@
   }
 }
 
+/// Calculates the name of the codec that will be generated for [api].
 String _getCodecName(Api api) => '${api.name}Codec';
 
+/// Writes the codec class that will be used by [api].
+/// Example:
+/// private static class FooCodec extends StandardMessageCodec {...}
 void _writeCodec(Indent indent, Api api, Root root) {
   final String codecName = _getCodecName(api);
   indent.write('private static class $codecName extends StandardMessageCodec ');
@@ -103,6 +107,12 @@
   });
 }
 
+/// Write the java code that represents a host [Api], [api].
+/// Example:
+/// public interface Foo {
+///   int add(int x, int y);
+///   static void setup(BinaryMessenger binaryMessenger, Foo api) {...}
+/// }
 void _writeHostApi(Indent indent, Api api) {
   assert(api.location == ApiLocation.host);
 
@@ -249,6 +259,15 @@
 String _getSafeArgumentName(int count, NamedType argument) =>
     _getArgumentName(count, argument) + 'Arg';
 
+/// Writes the code for a flutter [Api], [api].
+/// Example:
+/// public static class Foo {
+///   public Foo(BinaryMessenger argBinaryMessenger) {...}
+///   public interface Reply<T> {
+///     void reply(T reply);
+///   }
+///   public int add(int x, int y, Reply<int> callback) {...}
+/// }
 void _writeFlutterApi(Indent indent, Api api) {
   assert(api.location == ApiLocation.flutter);
   indent.writeln(
@@ -397,138 +416,192 @@
   final Set<String> rootEnumNameSet =
       root.enums.map((Enum x) => x.name).toSet();
   final Indent indent = Indent(sink);
-  if (options.copyrightHeader != null) {
-    addLines(indent, options.copyrightHeader!, linePrefix: '// ');
-  }
-  indent.writeln('// $generatedCodeWarning');
-  indent.writeln('// $seeAlsoWarning');
-  indent.addln('');
-  if (options.package != null) {
-    indent.writeln('package ${options.package};');
-  }
-  indent.addln('');
-  indent.writeln('import android.util.Log;');
-  indent.writeln('import io.flutter.plugin.common.BasicMessageChannel;');
-  indent.writeln('import io.flutter.plugin.common.BinaryMessenger;');
-  indent.writeln('import io.flutter.plugin.common.MessageCodec;');
-  indent.writeln('import io.flutter.plugin.common.StandardMessageCodec;');
-  indent.writeln('import java.io.ByteArrayOutputStream;');
-  indent.writeln('import java.nio.ByteBuffer;');
-  indent.writeln('import java.util.Arrays;');
-  indent.writeln('import java.util.ArrayList;');
-  indent.writeln('import java.util.List;');
-  indent.writeln('import java.util.Map;');
-  indent.writeln('import java.util.HashMap;');
 
-  indent.addln('');
-  indent.writeln('/** Generated class from Pigeon. */');
-  indent.writeln(
-      '@SuppressWarnings({"unused", "unchecked", "CodeBlock2Expr", "RedundantSuppression"})');
-  indent.write('public class ${options.className!} ');
-  indent.scoped('{', '}', () {
-    for (final Enum anEnum in root.enums) {
+  void writeHeader() {
+    if (options.copyrightHeader != null) {
+      addLines(indent, options.copyrightHeader!, linePrefix: '// ');
+    }
+    indent.writeln('// $generatedCodeWarning');
+    indent.writeln('// $seeAlsoWarning');
+  }
+
+  void writeImports() {
+    indent.writeln('import android.util.Log;');
+    indent.writeln('import androidx.annotation.NonNull;');
+    indent.writeln('import androidx.annotation.Nullable;');
+    indent.writeln('import io.flutter.plugin.common.BasicMessageChannel;');
+    indent.writeln('import io.flutter.plugin.common.BinaryMessenger;');
+    indent.writeln('import io.flutter.plugin.common.MessageCodec;');
+    indent.writeln('import io.flutter.plugin.common.StandardMessageCodec;');
+    indent.writeln('import java.io.ByteArrayOutputStream;');
+    indent.writeln('import java.nio.ByteBuffer;');
+    indent.writeln('import java.util.Arrays;');
+    indent.writeln('import java.util.ArrayList;');
+    indent.writeln('import java.util.List;');
+    indent.writeln('import java.util.Map;');
+    indent.writeln('import java.util.HashMap;');
+  }
+
+  void writeEnum(Enum anEnum) {
+    indent.write('public enum ${anEnum.name} ');
+    indent.scoped('{', '}', () {
+      int index = 0;
+      for (final String member in anEnum.members) {
+        indent.writeln(
+            '$member($index)${index == anEnum.members.length - 1 ? ';' : ','}');
+        index++;
+      }
       indent.writeln('');
-      indent.write('public enum ${anEnum.name} ');
+      // We use explicit indexing here as use of the ordinal() method is
+      // discouraged. The toMap and fromMap API matches class API to allow
+      // the same code to work with enums and classes, but this
+      // can also be done directly in the host and flutter APIs.
+      indent.writeln('private int index;');
+      indent.write('private ${anEnum.name}(final int index) ');
       indent.scoped('{', '}', () {
-        int index = 0;
-        for (final String member in anEnum.members) {
-          indent.writeln(
-              '$member($index)${index == anEnum.members.length - 1 ? ';' : ','}');
-          index++;
+        indent.writeln('this.index = index;');
+      });
+    });
+  }
+
+  void writeDataClass(Class klass) {
+    void writeField(NamedType field) {
+      final HostDatatype hostDatatype = getHostDatatype(field, root.classes,
+          root.enums, (NamedType x) => _javaTypeForBuiltinDartType(x.type));
+      final String nullability =
+          field.type.isNullable ? '@Nullable' : '@NonNull';
+      indent.writeln(
+          'private $nullability ${hostDatatype.datatype} ${field.name};');
+      indent.writeln(
+          'public $nullability ${hostDatatype.datatype} ${_makeGetter(field)}() { return ${field.name}; }');
+      indent.writeScoped(
+          'public void ${_makeSetter(field)}($nullability ${hostDatatype.datatype} setterArg) {',
+          '}', () {
+        if (!field.type.isNullable) {
+          indent.writeScoped('if (setterArg == null) {', '}', () {
+            indent.writeln(
+                'throw new IllegalStateException("Nonnull field \\"${field.name}\\" is null.");');
+          });
         }
-        indent.writeln('');
-        // We use explicit indexing here as use of the ordinal() method is
-        // discouraged. The toMap and fromMap API matches class API to allow
-        // the same code to work with enums and classes, but this
-        // can also be done directly in the host and flutter APIs.
-        indent.writeln('private int index;');
-        indent.write('private ${anEnum.name}(final int index) ');
-        indent.scoped('{', '}', () {
-          indent.writeln('this.index = index;');
-        });
+        indent.writeln('this.${field.name} = setterArg;');
       });
     }
 
-    for (final Class klass in root.classes) {
-      indent.addln('');
-      indent.writeln(
-          '/** Generated class from Pigeon that represents data sent in messages. */');
-      indent.write('public static class ${klass.name} ');
+    void writeToMap() {
+      indent.write('@NonNull Map<String, Object> toMap() ');
+      indent.scoped('{', '}', () {
+        indent.writeln('Map<String, Object> toMapResult = new HashMap<>();');
+        for (final NamedType field in klass.fields) {
+          final HostDatatype hostDatatype = getHostDatatype(field, root.classes,
+              root.enums, (NamedType x) => _javaTypeForBuiltinDartType(x.type));
+          String toWriteValue = '';
+          final String fieldName = field.name;
+          if (!hostDatatype.isBuiltin &&
+              rootClassNameSet.contains(field.type.baseName)) {
+            toWriteValue = '($fieldName == null) ? null : $fieldName.toMap()';
+          } else if (!hostDatatype.isBuiltin &&
+              rootEnumNameSet.contains(field.type.baseName)) {
+            toWriteValue = '$fieldName == null ? null : $fieldName.index';
+          } else {
+            toWriteValue = field.name;
+          }
+          indent.writeln('toMapResult.put("${field.name}", $toWriteValue);');
+        }
+        indent.writeln('return toMapResult;');
+      });
+    }
+
+    void writeFromMap() {
+      indent.write(
+          'static @NonNull ${klass.name} fromMap(@NonNull Map<String, Object> map) ');
+      indent.scoped('{', '}', () {
+        const String result = 'pigeonResult';
+        indent.writeln('${klass.name} $result = new ${klass.name}();');
+        for (final NamedType field in klass.fields) {
+          final String fieldVariable = field.name;
+          final String setter = _makeSetter(field);
+          indent.writeln('Object $fieldVariable = map.get("${field.name}");');
+          if (rootEnumNameSet.contains(field.type.baseName)) {
+            indent.writeln(
+                '$result.$setter($fieldVariable == null ? null : ${field.type.baseName}.values()[(int)$fieldVariable]);');
+          } else {
+            indent.writeln(
+                '$result.$setter(${_castObject(field, root.classes, root.enums, fieldVariable)});');
+          }
+        }
+        indent.writeln('return $result;');
+      });
+    }
+
+    void writeBuilder() {
+      indent.write('public static class Builder ');
       indent.scoped('{', '}', () {
         for (final NamedType field in klass.fields) {
           final HostDatatype hostDatatype = getHostDatatype(field, root.classes,
               root.enums, (NamedType x) => _javaTypeForBuiltinDartType(x.type));
-          indent.writeln('private ${hostDatatype.datatype} ${field.name};');
+          final String nullability =
+              field.type.isNullable ? '@Nullable' : '@NonNull';
           indent.writeln(
-              'public ${hostDatatype.datatype} ${_makeGetter(field)}() { return ${field.name}; }');
-          indent.writeln(
-              'public void ${_makeSetter(field)}(${hostDatatype.datatype} setterArg) { this.${field.name} = setterArg; }');
-          indent.addln('');
+              'private @Nullable ${hostDatatype.datatype} ${field.name};');
+          indent.writeScoped(
+              'public @NonNull Builder ${_makeSetter(field)}($nullability ${hostDatatype.datatype} setterArg) {',
+              '}', () {
+            indent.writeln('this.${field.name} = setterArg;');
+            indent.writeln('return this;');
+          });
         }
-        indent.write('Map<String, Object> toMap() ');
+        indent.write('public @NonNull ${klass.name} build() ');
         indent.scoped('{', '}', () {
-          indent.writeln('Map<String, Object> toMapResult = new HashMap<>();');
+          const String returnVal = 'pigeonReturn';
+          indent.writeln('${klass.name} $returnVal = new ${klass.name}();');
           for (final NamedType field in klass.fields) {
-            final HostDatatype hostDatatype = getHostDatatype(
-                field,
-                root.classes,
-                root.enums,
-                (NamedType x) => _javaTypeForBuiltinDartType(x.type));
-            String toWriteValue = '';
-            final String fieldName = field.name;
-            if (!hostDatatype.isBuiltin &&
-                rootClassNameSet.contains(field.type.baseName)) {
-              toWriteValue = '($fieldName == null) ? null : $fieldName.toMap()';
-            } else if (!hostDatatype.isBuiltin &&
-                rootEnumNameSet.contains(field.type.baseName)) {
-              toWriteValue = '$fieldName == null ? null : $fieldName.index';
-            } else {
-              toWriteValue = field.name;
-            }
-            indent.writeln('toMapResult.put("${field.name}", $toWriteValue);');
+            indent.writeln('$returnVal.${_makeSetter(field)}(${field.name});');
           }
-          indent.writeln('return toMapResult;');
-        });
-        indent.write('static ${klass.name} fromMap(Map<String, Object> map) ');
-        indent.scoped('{', '}', () {
-          indent.writeln('${klass.name} fromMapResult = new ${klass.name}();');
-          for (final NamedType field in klass.fields) {
-            final String fieldVariable = field.name;
-            indent.writeln('Object $fieldVariable = map.get("${field.name}");');
-            if (rootEnumNameSet.contains(field.type.baseName)) {
-              indent.writeln(
-                  'fromMapResult.${field.name} = $fieldVariable == null ? null : ${field.type.baseName}.values()[(int)$fieldVariable];');
-            } else {
-              indent.writeln(
-                  'fromMapResult.${field.name} = ${_castObject(field, root.classes, root.enums, fieldVariable)};');
-            }
-          }
-          indent.writeln('return fromMapResult;');
+          indent.writeln('return $returnVal;');
         });
       });
     }
 
-    if (root.apis.any((Api api) =>
-        api.location == ApiLocation.host &&
-        api.methods.any((Method it) => it.isAsynchronous))) {
-      indent.addln('');
-      indent.write('public interface Result<T> ');
-      indent.scoped('{', '}', () {
-        indent.writeln('void success(T result);');
-        indent.writeln('void error(Throwable error);');
-      });
-    }
-
-    for (final Api api in root.apis) {
-      _writeCodec(indent, api, root);
-      indent.addln('');
-      if (api.location == ApiLocation.host) {
-        _writeHostApi(indent, api);
-      } else if (api.location == ApiLocation.flutter) {
-        _writeFlutterApi(indent, api);
+    indent.writeln(
+        '/** Generated class from Pigeon that represents data sent in messages. */');
+    indent.write('public static class ${klass.name} ');
+    indent.scoped('{', '}', () {
+      for (final NamedType field in klass.fields) {
+        writeField(field);
+        indent.addln('');
       }
-    }
 
+      if (klass.fields
+          .map((NamedType e) => !e.type.isNullable)
+          .any((bool e) => e)) {
+        indent.writeln(
+            '/** Constructor is private to enforce null safety; use Builder. */');
+        indent.writeln('private ${klass.name}() {}');
+      }
+
+      writeBuilder();
+      writeToMap();
+      writeFromMap();
+    });
+  }
+
+  void writeResultInterface() {
+    indent.write('public interface Result<T> ');
+    indent.scoped('{', '}', () {
+      indent.writeln('void success(T result);');
+      indent.writeln('void error(Throwable error);');
+    });
+  }
+
+  void writeApi(Api api) {
+    if (api.location == ApiLocation.host) {
+      _writeHostApi(indent, api);
+    } else if (api.location == ApiLocation.flutter) {
+      _writeFlutterApi(indent, api);
+    }
+  }
+
+  void writeWrapError() {
     indent.format('''
 private static Map<String, Object> wrapError(Throwable exception) {
 \tMap<String, Object> errorMap = new HashMap<>();
@@ -537,5 +610,44 @@
 \terrorMap.put("${Keys.errorDetails}", "Cause: " + exception.getCause() + ", Stacktrace: " + Log.getStackTraceString(exception));
 \treturn errorMap;
 }''');
+  }
+
+  writeHeader();
+  indent.addln('');
+  if (options.package != null) {
+    indent.writeln('package ${options.package};');
+  }
+  indent.addln('');
+  writeImports();
+  indent.addln('');
+  indent.writeln('/** Generated class from Pigeon. */');
+  indent.writeln(
+      '@SuppressWarnings({"unused", "unchecked", "CodeBlock2Expr", "RedundantSuppression"})');
+  indent.write('public class ${options.className!} ');
+  indent.scoped('{', '}', () {
+    for (final Enum anEnum in root.enums) {
+      indent.writeln('');
+      writeEnum(anEnum);
+    }
+
+    for (final Class klass in root.classes) {
+      indent.addln('');
+      writeDataClass(klass);
+    }
+
+    if (root.apis.any((Api api) =>
+        api.location == ApiLocation.host &&
+        api.methods.any((Method it) => it.isAsynchronous))) {
+      indent.addln('');
+      writeResultInterface();
+    }
+
+    for (final Api api in root.apis) {
+      _writeCodec(indent, api, root);
+      indent.addln('');
+      writeApi(api);
+    }
+
+    writeWrapError();
   });
 }
diff --git a/packages/pigeon/lib/objc_generator.dart b/packages/pigeon/lib/objc_generator.dart
index da9c4be..e232407 100644
--- a/packages/pigeon/lib/objc_generator.dart
+++ b/packages/pigeon/lib/objc_generator.dart
@@ -54,6 +54,7 @@
   }
 }
 
+/// Calculates the ObjC class name, possibly prefixed.
 String _className(String? prefix, String className) {
   if (prefix != null) {
     return '$prefix$className';
@@ -62,12 +63,14 @@
   }
 }
 
+/// Calculates callback block signature for for async methods.
 String _callbackForType(TypeDeclaration type, _ObjcPtr objcType) {
   return type.isVoid
       ? 'void(^)(NSError *_Nullable)'
       : 'void(^)(${objcType.ptr.trim()}, NSError *_Nullable)';
 }
 
+/// Represents an ObjC pointer (ex 'id', 'NSString *').
 class _ObjcPtr {
   const _ObjcPtr({required this.baseName}) : hasAsterisk = baseName != 'id';
   final String baseName;
@@ -75,6 +78,7 @@
   String get ptr => '$baseName${hasAsterisk ? ' *' : ' '}';
 }
 
+/// Maps between Dart types to ObjC pointer types (ex 'String' => 'NSString *').
 const Map<String, _ObjcPtr> _objcTypeForDartTypeMap = <String, _ObjcPtr>{
   'bool': _ObjcPtr(baseName: 'NSNumber'),
   'int': _ObjcPtr(baseName: 'NSNumber'),
@@ -89,6 +93,9 @@
   'Object': _ObjcPtr(baseName: 'id'),
 };
 
+/// Converts list of [TypeDeclaration] to a code string representing the type
+/// arguments for use in generics.
+/// Example: ('FOO', ['Foo', 'Bar']) -> 'FOOFoo *, FOOBar *').
 String _flattenTypeArguments(String? classPrefix, List<TypeDeclaration> args) {
   final String result = args
       .map<String>((TypeDeclaration e) =>
@@ -116,6 +123,7 @@
       : _ObjcPtr(baseName: _className(classPrefix, field.baseName));
 }
 
+/// Maps a type to a properties memory semantics (ie strong, copy).
 String _propertyTypeForDartType(NamedType field) {
   const Map<String, String> propertyTypeForDartTypeMap = <String, String>{
     'String': 'copy',
@@ -138,11 +146,61 @@
   }
 }
 
+bool _isNullable(HostDatatype hostDatatype, TypeDeclaration type) =>
+    hostDatatype.datatype.contains('*') && type.isNullable;
+
+/// Writes the method declaration for the initializer.
+///
+/// Example '+ (instancetype)makeWithFoo:(NSString *)foo'
+void _writeInitializerDeclaration(Indent indent, Class klass,
+    List<Class> classes, List<Enum> enums, String? prefix) {
+  final List<String> enumNames = enums.map((Enum x) => x.name).toList();
+  indent.write('+ (instancetype)makeWith');
+  bool isFirst = true;
+  indent.nest(2, () {
+    for (final NamedType field in klass.fields) {
+      final String label = isFirst ? _capitalize(field.name) : field.name;
+      final void Function(String) printer = isFirst
+          ? indent.add
+          : (String x) {
+              indent.addln('');
+              indent.write(x);
+            };
+      isFirst = false;
+      final HostDatatype hostDatatype = getHostDatatype(field, classes, enums,
+          (NamedType x) => _objcTypePtrForPrimitiveDartType(prefix, x),
+          customResolver: enumNames.contains(field.type.baseName)
+              ? (String x) => _className(prefix, x)
+              : (String x) => '${_className(prefix, x)} *');
+      final String nullable =
+          _isNullable(hostDatatype, field.type) ? 'nullable ' : '';
+      printer('$label:($nullable${hostDatatype.datatype})${field.name}');
+    }
+  });
+}
+
+/// Writes the class declaration for a data class.
+///
+/// Example:
+/// @interface Foo : NSObject
+/// @property (nonatomic, copy) NSString *bar;
+/// @end
 void _writeClassDeclarations(
     Indent indent, List<Class> classes, List<Enum> enums, String? prefix) {
   final List<String> enumNames = enums.map((Enum x) => x.name).toList();
   for (final Class klass in classes) {
     indent.writeln('@interface ${_className(prefix, klass.name)} : NSObject');
+    if (klass.fields.isNotEmpty) {
+      if (klass.fields
+          .map((NamedType e) => !e.type.isNullable)
+          .any((bool e) => e)) {
+        indent.writeln(
+            '/// `init` unavailable to enforce nonnull fields, see the `make` class method.');
+        indent.writeln('- (instancetype)init NS_UNAVAILABLE;');
+      }
+      _writeInitializerDeclaration(indent, klass, classes, enums, prefix);
+      indent.addln(';');
+    }
     for (final NamedType field in klass.fields) {
       final HostDatatype hostDatatype = getHostDatatype(field, classes, enums,
           (NamedType x) => _objcTypePtrForPrimitiveDartType(prefix, x),
@@ -156,7 +214,7 @@
         propertyType = _propertyTypeForDartType(field);
       }
       final String nullability =
-          hostDatatype.datatype.contains('*') ? ', nullable' : '';
+          _isNullable(hostDatatype, field.type) ? ', nullable' : '';
       indent.writeln(
           '@property(nonatomic, $propertyType$nullability) ${hostDatatype.datatype} ${field.name};');
     }
@@ -165,12 +223,25 @@
   }
 }
 
+/// Generates the name of the codec that will be generated.
 String _getCodecName(String? prefix, String className) =>
     '${_className(prefix, className)}Codec';
 
+/// Generates the name of the function for accessing the codec instance used by
+/// the api class named [className].
 String _getCodecGetterName(String? prefix, String className) =>
     '${_className(prefix, className)}GetCodec';
 
+/// Writes the codec that will be used for encoding messages for the [api].
+///
+/// Example:
+/// @interface FooHostApiCodecReader : FlutterStandardReader
+/// ...
+/// @interface FooHostApiCodecWriter : FlutterStandardWriter
+/// ...
+/// @interface FooHostApiCodecReaderWriter : FlutterStandardReaderWriter
+/// ...
+/// NSObject<FlutterMessageCodec> *FooHostApiCodecGetCodec() {...}
 void _writeCodec(
     Indent indent, String name, ObjcOptions options, Api api, Root root) {
   final String readerWriterName = '${name}ReaderWriter';
@@ -319,6 +390,14 @@
   return '- ($returnType)$argSignature';
 }
 
+/// Writes the declaration for an host [Api].
+///
+/// Example:
+/// @protocol Foo
+/// - (NSInteger)add:(NSInteger)x to:(NSInteger)y error:(NSError**)error;
+/// @end
+///
+/// extern void FooSetup(id<FlutterBinaryMessenger> binaryMessenger, NSObject<Foo> *_Nullable api);
 void _writeHostApiDeclaration(Indent indent, Api api, ObjcOptions options) {
   final String apiName = _className(options.prefix, api.name);
   indent.writeln('@protocol $apiName');
@@ -361,6 +440,14 @@
   indent.writeln('');
 }
 
+/// Writes the declaration for an flutter [Api].
+///
+/// Example:
+///
+/// @interface Foo : NSObject
+/// - (instancetype)initWithBinaryMessenger:(id<FlutterBinaryMessenger>)binaryMessenger;
+/// - (void)add:(NSInteger)x to:(NSInteger)y completion:(void(^)(NSError *, NSInteger result)completion;
+/// @end
 void _writeFlutterApiDeclaration(Indent indent, Api api, ObjcOptions options) {
   final String apiName = _className(options.prefix, api.name);
   indent.writeln('@interface $apiName : NSObject');
@@ -386,22 +473,27 @@
 /// provided [options].
 void generateObjcHeader(ObjcOptions options, Root root, StringSink sink) {
   final Indent indent = Indent(sink);
-  if (options.copyrightHeader != null) {
-    addLines(indent, options.copyrightHeader!, linePrefix: '// ');
+
+  void writeHeader() {
+    if (options.copyrightHeader != null) {
+      addLines(indent, options.copyrightHeader!, linePrefix: '// ');
+    }
+    indent.writeln('// $generatedCodeWarning');
+    indent.writeln('// $seeAlsoWarning');
   }
-  indent.writeln('// $generatedCodeWarning');
-  indent.writeln('// $seeAlsoWarning');
-  indent.writeln('#import <Foundation/Foundation.h>');
-  indent.writeln('@protocol FlutterBinaryMessenger;');
-  indent.writeln('@protocol FlutterMessageCodec;');
-  indent.writeln('@class FlutterError;');
-  indent.writeln('@class FlutterStandardTypedData;');
-  indent.writeln('');
 
-  indent.writeln('NS_ASSUME_NONNULL_BEGIN');
+  void writeImports() {
+    indent.writeln('#import <Foundation/Foundation.h>');
+  }
 
-  for (final Enum anEnum in root.enums) {
-    indent.writeln('');
+  void writeForwardDeclarations() {
+    indent.writeln('@protocol FlutterBinaryMessenger;');
+    indent.writeln('@protocol FlutterMessageCodec;');
+    indent.writeln('@class FlutterError;');
+    indent.writeln('@class FlutterStandardTypedData;');
+  }
+
+  void writeEnum(Enum anEnum) {
     final String enumName = _className(options.prefix, anEnum.name);
     indent.write('typedef NS_ENUM(NSUInteger, $enumName) ');
     indent.scoped('{', '};', () {
@@ -414,6 +506,18 @@
       }
     });
   }
+
+  writeHeader();
+  writeImports();
+  writeForwardDeclarations();
+  indent.writeln('');
+
+  indent.writeln('NS_ASSUME_NONNULL_BEGIN');
+
+  for (final Enum anEnum in root.enums) {
+    indent.writeln('');
+    writeEnum(anEnum);
+  }
   indent.writeln('');
 
   for (final Class klass in root.classes) {
@@ -471,137 +575,163 @@
 String _getSafeArgName(int count, NamedType arg) =>
     arg.name.isEmpty ? 'arg$count' : 'arg_${arg.name}';
 
+/// Writes the definition code for a host [Api].
+/// See also: [_writeHostApiDeclaration]
 void _writeHostApiSource(Indent indent, ObjcOptions options, Api api) {
   assert(api.location == ApiLocation.host);
   final String apiName = _className(options.prefix, api.name);
+
+  void writeChannelAllocation(Method func, String varName) {
+    indent.writeln('FlutterBasicMessageChannel *$varName =');
+    indent.inc();
+    indent.writeln('[FlutterBasicMessageChannel');
+    indent.inc();
+    indent.writeln('messageChannelWithName:@"${makeChannelName(api, func)}"');
+    indent.writeln('binaryMessenger:binaryMessenger');
+    indent
+        .writeln('codec:${_getCodecGetterName(options.prefix, api.name)}()];');
+    indent.dec();
+    indent.dec();
+  }
+
+  void writeChannelApiBinding(Method func, String channel) {
+    void unpackArgs(String variable, Iterable<String> argNames) {
+      indent.writeln('NSArray *args = $variable;');
+      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];';
+      }).forEach(indent.writeln);
+    }
+
+    void writeAsyncBindings(Iterable<String> selectorComponents,
+        String callSignature, _ObjcPtr returnType) {
+      if (func.returnType.isVoid) {
+        const String callback = 'callback(wrapResult(nil, error));';
+        if (func.arguments.isEmpty) {
+          indent.writeScoped(
+              '[api ${selectorComponents.first}:^(FlutterError *_Nullable error) {',
+              '}];', () {
+            indent.writeln(callback);
+          });
+        } else {
+          indent.writeScoped(
+              '[api $callSignature ${selectorComponents.last}:^(FlutterError *_Nullable error) {',
+              '}];', () {
+            indent.writeln(callback);
+          });
+        }
+      } else {
+        const String callback = 'callback(wrapResult(output, error));';
+        if (func.arguments.isEmpty) {
+          indent.writeScoped(
+              '[api ${selectorComponents.first}:^(${returnType.ptr}_Nullable output, FlutterError *_Nullable error) {',
+              '}];', () {
+            indent.writeln(callback);
+          });
+        } else {
+          indent.writeScoped(
+              '[api $callSignature ${selectorComponents.last}:^(${returnType.ptr}_Nullable output, FlutterError *_Nullable error) {',
+              '}];', () {
+            indent.writeln(callback);
+          });
+        }
+      }
+    }
+
+    void writeSyncBindings(String call, _ObjcPtr returnType) {
+      indent.writeln('FlutterError *error;');
+      if (func.returnType.isVoid) {
+        indent.writeln('$call;');
+        indent.writeln('callback(wrapResult(nil, error));');
+      } else {
+        indent.writeln('${returnType.ptr}output = $call;');
+        indent.writeln('callback(wrapResult(output, error));');
+      }
+    }
+
+    // TODO(gaaclarke): Incorporate this into _getSelectorComponents.
+    final String lastSelectorComponent =
+        func.isAsynchronous ? 'completion' : 'error';
+    final String selector = _getSelector(func, lastSelectorComponent);
+    indent.writeln(
+        'NSCAssert([api respondsToSelector:@selector($selector)], @"$apiName api (%@) doesn\'t respond to @selector($selector)", api);');
+    indent.write(
+        '[$channel setMessageHandler:^(id _Nullable message, FlutterReply callback) ');
+    indent.scoped('{', '}];', () {
+      final _ObjcPtr returnType =
+          _objcTypeForDartType(options.prefix, func.returnType);
+      final Iterable<String> selectorComponents =
+          _getSelectorComponents(func, lastSelectorComponent);
+      final Iterable<String> argNames =
+          indexMap(func.arguments, _getSafeArgName);
+      final String callSignature =
+          map2(selectorComponents.take(argNames.length), argNames,
+              (String selectorComponent, String argName) {
+        return '$selectorComponent:$argName';
+      }).join(' ');
+      if (func.arguments.isNotEmpty) {
+        unpackArgs('message', argNames);
+      }
+      if (func.isAsynchronous) {
+        writeAsyncBindings(selectorComponents, callSignature, returnType);
+      } else {
+        final String syncCall = func.arguments.isEmpty
+            ? '[api ${selectorComponents.first}:&error]'
+            : '[api $callSignature error:&error]';
+        writeSyncBindings(syncCall, returnType);
+      }
+    });
+  }
+
+  const String channelName = 'channel';
   indent.write(
       'void ${apiName}Setup(id<FlutterBinaryMessenger> binaryMessenger, NSObject<$apiName> *api) ');
   indent.scoped('{', '}', () {
     for (final Method func in api.methods) {
       indent.write('');
       indent.scoped('{', '}', () {
-        indent.writeln('FlutterBasicMessageChannel *channel =');
-        indent.inc();
-        indent.writeln('[FlutterBasicMessageChannel');
-        indent.inc();
-        indent
-            .writeln('messageChannelWithName:@"${makeChannelName(api, func)}"');
-        indent.writeln('binaryMessenger:binaryMessenger');
-        indent.writeln(
-            'codec:${_getCodecGetterName(options.prefix, api.name)}()];');
-        indent.dec();
-        indent.dec();
-
+        writeChannelAllocation(func, channelName);
         indent.write('if (api) ');
         indent.scoped('{', '}', () {
-          // TODO(gaaclarke): Incorporate this into _getSelectorComponents.
-          final String lastSelectorComponent =
-              func.isAsynchronous ? 'completion' : 'error';
-          final String selector = _getSelector(func, lastSelectorComponent);
-          indent.writeln(
-              'NSCAssert([api respondsToSelector:@selector($selector)], @"$apiName api (%@) doesn\'t respond to @selector($selector)", api);');
-          indent.write(
-              '[channel setMessageHandler:^(id _Nullable message, FlutterReply callback) ');
-          indent.scoped('{', '}];', () {
-            final _ObjcPtr returnType =
-                _objcTypeForDartType(options.prefix, func.returnType);
-            String syncCall;
-            String? callSignature;
-            final Iterable<String> selectorComponents =
-                _getSelectorComponents(func, lastSelectorComponent);
-            if (func.arguments.isEmpty) {
-              syncCall = '[api ${selectorComponents.first}:&error]';
-            } else {
-              indent.writeln('NSArray *args = message;');
-              final Iterable<String> argNames =
-                  indexMap(func.arguments, _getSafeArgName);
-              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];';
-              }).forEach(indent.writeln);
-              callSignature =
-                  map2(selectorComponents.take(argNames.length), argNames,
-                      (String selectorComponent, String argName) {
-                return '$selectorComponent:$argName';
-              }).join(' ');
-              syncCall = '[api $callSignature error:&error]';
-            }
-            if (func.isAsynchronous) {
-              if (func.returnType.isVoid) {
-                const String callback = 'callback(wrapResult(nil, error));';
-                if (func.arguments.isEmpty) {
-                  indent.writeScoped(
-                      '[api ${selectorComponents.first}:^(FlutterError *_Nullable error) {',
-                      '}];', () {
-                    indent.writeln(callback);
-                  });
-                } else {
-                  indent.writeScoped(
-                      '[api $callSignature ${selectorComponents.last}:^(FlutterError *_Nullable error) {',
-                      '}];', () {
-                    indent.writeln(callback);
-                  });
-                }
-              } else {
-                const String callback = 'callback(wrapResult(output, error));';
-                if (func.arguments.isEmpty) {
-                  indent.writeScoped(
-                      '[api ${selectorComponents.first}:^(${returnType.ptr}_Nullable output, FlutterError *_Nullable error) {',
-                      '}];', () {
-                    indent.writeln(callback);
-                  });
-                } else {
-                  indent.writeScoped(
-                      '[api $callSignature ${selectorComponents.last}:^(${returnType.ptr}_Nullable output, FlutterError *_Nullable error) {',
-                      '}];', () {
-                    indent.writeln(callback);
-                  });
-                }
-              }
-            } else {
-              indent.writeln('FlutterError *error;');
-              if (func.returnType.isVoid) {
-                indent.writeln('$syncCall;');
-                indent.writeln('callback(wrapResult(nil, error));');
-              } else {
-                indent.writeln('${returnType.ptr}output = $syncCall;');
-                indent.writeln('callback(wrapResult(output, error));');
-              }
-            }
-          });
+          writeChannelApiBinding(func, channelName);
         });
         indent.write('else ');
         indent.scoped('{', '}', () {
-          indent.writeln('[channel setMessageHandler:nil];');
+          indent.writeln('[$channelName setMessageHandler:nil];');
         });
       });
     }
   });
 }
 
+/// Writes the definition code for a flutter [Api].
+/// See also: [_writeFlutterApiDeclaration]
 void _writeFlutterApiSource(Indent indent, ObjcOptions options, Api api) {
   assert(api.location == ApiLocation.flutter);
   final String apiName = _className(options.prefix, api.name);
-  indent.writeln('@interface $apiName ()');
-  indent.writeln(
-      '@property (nonatomic, strong) NSObject<FlutterBinaryMessenger> *binaryMessenger;');
-  indent.writeln('@end');
-  indent.addln('');
-  indent.writeln('@implementation $apiName');
-  indent.write(
-      '- (instancetype)initWithBinaryMessenger:(NSObject<FlutterBinaryMessenger> *)binaryMessenger ');
-  indent.scoped('{', '}', () {
-    indent.writeln('self = [super init];');
-    indent.write('if (self) ');
+
+  void writeExtension() {
+    indent.writeln('@interface $apiName ()');
+    indent.writeln(
+        '@property (nonatomic, strong) NSObject<FlutterBinaryMessenger> *binaryMessenger;');
+    indent.writeln('@end');
+  }
+
+  void writeInitializer() {
+    indent.write(
+        '- (instancetype)initWithBinaryMessenger:(NSObject<FlutterBinaryMessenger> *)binaryMessenger ');
     indent.scoped('{', '}', () {
-      indent.writeln('_binaryMessenger = binaryMessenger;');
+      indent.writeln('self = [super init];');
+      indent.write('if (self) ');
+      indent.scoped('{', '}', () {
+        indent.writeln('_binaryMessenger = binaryMessenger;');
+      });
+      indent.writeln('return self;');
     });
-    indent.writeln('return self;');
-  });
-  indent.addln('');
-  for (final Method func in api.methods) {
+  }
+
+  void writeMethod(Method func) {
     final _ObjcPtr returnType =
         _objcTypeForDartType(options.prefix, func.returnType);
     final String callbackType = _callbackForType(func.returnType, returnType);
@@ -644,6 +774,13 @@
       });
     });
   }
+
+  writeExtension();
+  indent.addln('');
+  indent.writeln('@implementation $apiName');
+  indent.addln('');
+  writeInitializer();
+  api.methods.forEach(writeMethod);
   indent.writeln('@end');
 }
 
@@ -655,21 +792,27 @@
       root.classes.map((Class x) => x.name).toList();
   final List<String> enumNames = root.enums.map((Enum x) => x.name).toList();
 
-  if (options.copyrightHeader != null) {
-    addLines(indent, options.copyrightHeader!, linePrefix: '// ');
+  void writeHeader() {
+    if (options.copyrightHeader != null) {
+      addLines(indent, options.copyrightHeader!, linePrefix: '// ');
+    }
+    indent.writeln('// $generatedCodeWarning');
+    indent.writeln('// $seeAlsoWarning');
   }
-  indent.writeln('// $generatedCodeWarning');
-  indent.writeln('// $seeAlsoWarning');
-  indent.writeln('#import "${options.header}"');
-  indent.writeln('#import <Flutter/Flutter.h>');
-  indent.writeln('');
 
-  indent.writeln('#if !__has_feature(objc_arc)');
-  indent.writeln('#error File requires ARC to be enabled.');
-  indent.writeln('#endif');
-  indent.addln('');
+  void writeImports() {
+    indent.writeln('#import "${options.header}"');
+    indent.writeln('#import <Flutter/Flutter.h>');
+  }
 
-  indent.format('''
+  void writeArcEnforcer() {
+    indent.writeln('#if !__has_feature(objc_arc)');
+    indent.writeln('#error File requires ARC to be enabled.');
+    indent.writeln('#endif');
+  }
+
+  void writeWrapResultFunction() {
+    indent.format('''
 static NSDictionary<NSString *, id> *wrapResult(id result, FlutterError *error) {
 \tNSDictionary *errorDict = (NSDictionary *)[NSNull null];
 \tif (error) {
@@ -684,9 +827,9 @@
 \t\t\t@"${Keys.error}": errorDict,
 \t\t\t};
 }''');
-  indent.addln('');
+  }
 
-  for (final Class klass in root.classes) {
+  void writeDataClassExtension(Class klass) {
     final String className = _className(options.prefix, klass.name);
     indent.writeln('@interface $className ()');
     indent.writeln('+ ($className *)fromMap:(NSDictionary *)dict;');
@@ -694,45 +837,69 @@
     indent.writeln('@end');
   }
 
-  indent.writeln('');
-
-  for (final Class klass in root.classes) {
+  void writeDataClassImplementation(Class klass) {
     final String className = _className(options.prefix, klass.name);
-    indent.writeln('@implementation $className');
-    indent.write('+ ($className *)fromMap:(NSDictionary *)dict ');
-    indent.scoped('{', '}', () {
-      const String resultName = 'result';
-      indent.writeln('$className *$resultName = [[$className alloc] init];');
-      for (final NamedType field in klass.fields) {
-        if (enumNames.contains(field.type.baseName)) {
-          indent.writeln(
-              '$resultName.${field.name} = [${_dictGetter(classNames, 'dict', field, options.prefix)} integerValue];');
-        } else {
-          indent.writeln(
-              '$resultName.${field.name} = ${_dictGetter(classNames, 'dict', field, options.prefix)};');
-          indent.write(
-              'if ((NSNull *)$resultName.${field.name} == [NSNull null]) ');
-          indent.scoped('{', '}', () {
-            indent.writeln('$resultName.${field.name} = nil;');
-          });
+    void writeInitializer() {
+      _writeInitializerDeclaration(
+          indent, klass, root.classes, root.enums, options.prefix);
+      indent.writeScoped(' {', '}', () {
+        const String result = 'pigeonResult';
+        indent.writeln('$className* $result = [[$className alloc] init];');
+        for (final NamedType field in klass.fields) {
+          indent.writeln('$result.${field.name} = ${field.name};');
         }
-      }
-      indent.writeln('return $resultName;');
-    });
-    indent.write('- (NSDictionary *)toMap ');
-    indent.scoped('{', '}', () {
-      indent.write('return [NSDictionary dictionaryWithObjectsAndKeys:');
-      for (final NamedType field in klass.fields) {
-        indent.add(
-            _dictValue(classNames, enumNames, field) + ', @"${field.name}", ');
-      }
-      indent.addln('nil];');
-    });
+        indent.writeln('return $result;');
+      });
+    }
+
+    void writeFromMap() {
+      indent.write('+ ($className *)fromMap:(NSDictionary *)dict ');
+      indent.scoped('{', '}', () {
+        const String resultName = 'pigeonResult';
+        indent.writeln('$className *$resultName = [[$className alloc] init];');
+        for (final NamedType field in klass.fields) {
+          if (enumNames.contains(field.type.baseName)) {
+            indent.writeln(
+                '$resultName.${field.name} = [${_dictGetter(classNames, 'dict', field, options.prefix)} integerValue];');
+          } else {
+            indent.writeln(
+                '$resultName.${field.name} = ${_dictGetter(classNames, 'dict', field, options.prefix)};');
+            if (field.type.isNullable) {
+              indent.write(
+                  'if ((NSNull *)$resultName.${field.name} == [NSNull null]) ');
+              indent.scoped('{', '}', () {
+                indent.writeln('$resultName.${field.name} = nil;');
+              });
+            } else {
+              indent.writeln(
+                  'NSAssert((NSNull *)$resultName.${field.name} != [NSNull null], @"");');
+            }
+          }
+        }
+        indent.writeln('return $resultName;');
+      });
+    }
+
+    void writeToMap() {
+      indent.write('- (NSDictionary *)toMap ');
+      indent.scoped('{', '}', () {
+        indent.write('return [NSDictionary dictionaryWithObjectsAndKeys:');
+        for (final NamedType field in klass.fields) {
+          indent.add(_dictValue(classNames, enumNames, field) +
+              ', @"${field.name}", ');
+        }
+        indent.addln('nil];');
+      });
+    }
+
+    indent.writeln('@implementation $className');
+    writeInitializer();
+    writeFromMap();
+    writeToMap();
     indent.writeln('@end');
-    indent.writeln('');
   }
 
-  for (final Api api in root.apis) {
+  void writeApi(Api api) {
     final String codecName = _getCodecName(options.prefix, api.name);
     _writeCodec(indent, codecName, options, api, root);
     indent.addln('');
@@ -742,4 +909,19 @@
       _writeFlutterApiSource(indent, options, api);
     }
   }
+
+  writeHeader();
+  writeImports();
+  indent.writeln('');
+  writeArcEnforcer();
+  indent.addln('');
+  writeWrapResultFunction();
+  indent.addln('');
+  root.classes.forEach(writeDataClassExtension);
+  indent.writeln('');
+  for (final Class klass in root.classes) {
+    writeDataClassImplementation(klass);
+    indent.writeln('');
+  }
+  root.apis.forEach(writeApi);
 }
diff --git a/packages/pigeon/lib/pigeon_lib.dart b/packages/pigeon/lib/pigeon_lib.dart
index f78145b..dd31610 100644
--- a/packages/pigeon/lib/pigeon_lib.dart
+++ b/packages/pigeon/lib/pigeon_lib.dart
@@ -25,6 +25,7 @@
 import 'ast.dart';
 import 'ast_generator.dart';
 import 'dart_generator.dart';
+import 'generator_tools.dart' as generator_tools;
 import 'objc_generator.dart';
 
 class _Asynchronous {
@@ -132,7 +133,8 @@
       this.dartOptions,
       this.copyrightHeader,
       this.oneLanguage,
-      this.astOut});
+      this.astOut,
+      this.debugGenerators});
 
   /// Path to the file which will be processed.
   final String? input;
@@ -170,6 +172,9 @@
   /// Path to AST debugging output.
   final String? astOut;
 
+  /// True means print out line number of generators in comments at newlines.
+  final bool? debugGenerators;
+
   /// Creates a [PigeonOptions] from a Map representation where:
   /// `x = PigeonOptions.fromMap(x.toMap())`.
   static PigeonOptions fromMap(Map<String, Object> map) {
@@ -192,6 +197,7 @@
       copyrightHeader: map['copyrightHeader'] as String?,
       oneLanguage: map['oneLanguage'] as bool?,
       astOut: map['astOut'] as String?,
+      debugGenerators: map['debugGenerators'] as bool?,
     );
   }
 
@@ -210,6 +216,8 @@
       if (dartOptions != null) 'dartOptions': dartOptions!.toMap(),
       if (copyrightHeader != null) 'copyrightHeader': copyrightHeader!,
       if (astOut != null) 'astOut': astOut!,
+      if (oneLanguage != null) 'oneLanguage': oneLanguage!,
+      if (debugGenerators != null) 'debugGenerators': debugGenerators!,
     };
     return result;
   }
@@ -861,10 +869,23 @@
 
   @override
   Object? visitConstructorDeclaration(dart_ast.ConstructorDeclaration node) {
-    final String type = _currentApi != null ? 'API classes' : 'data classes';
-    _errors.add(Error(
-        message: 'Constructors aren\'t supported in $type ("$node").',
-        lineNumber: _calculateLineNumber(source, node.offset)));
+    if (_currentApi != null) {
+      _errors.add(Error(
+          message: 'Constructors aren\'t supported in API classes ("$node").',
+          lineNumber: _calculateLineNumber(source, node.offset)));
+    } else {
+      if (node.body.beginToken.lexeme != ';') {
+        _errors.add(Error(
+            message:
+                'Constructor bodies aren\'t supported in data classes ("$node").',
+            lineNumber: _calculateLineNumber(source, node.offset)));
+      } else if (node.initializers.isNotEmpty) {
+        _errors.add(Error(
+            message:
+                'Constructor initializers aren\'t supported in data classes (use "this.fieldName") ("$node").',
+            lineNumber: _calculateLineNumber(source, node.offset)));
+      }
+    }
     node.visitChildren(this);
     return null;
   }
@@ -980,7 +1001,10 @@
         defaultsTo: false)
     ..addOption('ast_out',
         help:
-            'Path to generated AST debugging info. (Warning: format subject to change)');
+            'Path to generated AST debugging info. (Warning: format subject to change)')
+    ..addFlag('debug_generators',
+        help: 'Print the line number of the generator in comments at newlines.',
+        defaultsTo: false);
 
   /// Convert command-line arguments to [PigeonOptions].
   static PigeonOptions parseArgs(List<String> args) {
@@ -1009,6 +1033,7 @@
       copyrightHeader: results['copyright_header'],
       oneLanguage: results['one_language'],
       astOut: results['ast_out'],
+      debugGenerators: results['debug_generators'],
     );
     return opts;
   }
@@ -1040,6 +1065,9 @@
       {List<Generator>? generators, String? sdkPath}) async {
     final Pigeon pigeon = Pigeon.setup();
     PigeonOptions options = Pigeon.parseArgs(args);
+    if (options.debugGenerators ?? false) {
+      generator_tools.debugGenerators = true;
+    }
     final List<Generator> safeGenerators = generators ??
         <Generator>[
           const DartGenerator(),
diff --git a/packages/pigeon/pigeons/non_null_fields.dart b/packages/pigeon/pigeons/non_null_fields.dart
new file mode 100644
index 0000000..bbce336
--- /dev/null
+++ b/packages/pigeon/pigeons/non_null_fields.dart
@@ -0,0 +1,30 @@
+// 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 SearchRequest {
+  SearchRequest({required this.query});
+  String query;
+}
+
+class SearchReply {
+  SearchReply(this.result, this.error, this.indices);
+  String result;
+  String error;
+  List<int?> indices;
+}
+
+@HostApi()
+abstract class NonNullHostApi {
+  SearchReply search(SearchRequest nested);
+}
+
+@FlutterApi()
+abstract class NonNullFlutterApi {
+  SearchReply search(SearchRequest request);
+}
diff --git a/packages/pigeon/platform_tests/android_unit_tests/android/app/src/test/java/com/example/android_unit_tests/NonNullFieldsTest.java b/packages/pigeon/platform_tests/android_unit_tests/android/app/src/test/java/com/example/android_unit_tests/NonNullFieldsTest.java
new file mode 100644
index 0000000..3b1e150
--- /dev/null
+++ b/packages/pigeon/platform_tests/android_unit_tests/android/app/src/test/java/com/example/android_unit_tests/NonNullFieldsTest.java
@@ -0,0 +1,24 @@
+// 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 com.example.android_unit_tests.NonNullFields.SearchRequest;
+import java.lang.IllegalStateException;
+import org.junit.Test;
+
+public class NonNullFieldsTest {
+  @Test
+  public void builder() {
+    SearchRequest request = new SearchRequest.Builder().setQuery("hello").build();
+    assertEquals(request.getQuery(), "hello");
+  }
+
+  @Test(expected = IllegalStateException.class)
+  public void builderThrowsIfNull() {
+    SearchRequest request = new SearchRequest.Builder().build();
+  }
+}
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 f1b63b0..d71bddc 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
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 //
-// Autogenerated from Pigeon (v1.0.8), do not edit directly.
+// Autogenerated from Pigeon (v1.0.15), 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
@@ -13,6 +13,22 @@
 import 'package:flutter/services.dart';
 
 class Everything {
+  Everything({
+    this.aBool,
+    this.anInt,
+    this.aDouble,
+    this.aString,
+    this.aByteArray,
+    this.a4ByteArray,
+    this.a8ByteArray,
+    this.aFloatArray,
+    this.aList,
+    this.aMap,
+    this.nestedList,
+    this.mapWithAnnotations,
+    this.mapWithObject,
+  });
+
   bool? aBool;
   int? anInt;
   double? aDouble;
@@ -47,24 +63,25 @@
 
   static Everything decode(Object message) {
     final Map<Object?, Object?> pigeonMap = message as Map<Object?, Object?>;
-    return Everything()
-      ..aBool = pigeonMap['aBool'] as bool?
-      ..anInt = pigeonMap['anInt'] as int?
-      ..aDouble = pigeonMap['aDouble'] as double?
-      ..aString = pigeonMap['aString'] as String?
-      ..aByteArray = pigeonMap['aByteArray'] as Uint8List?
-      ..a4ByteArray = pigeonMap['a4ByteArray'] as Int32List?
-      ..a8ByteArray = pigeonMap['a8ByteArray'] as Int64List?
-      ..aFloatArray = pigeonMap['aFloatArray'] as Float64List?
-      ..aList = pigeonMap['aList'] as List<Object?>?
-      ..aMap = pigeonMap['aMap'] as Map<Object?, Object?>?
-      ..nestedList =
-          (pigeonMap['nestedList'] as List<Object?>?)?.cast<List<bool?>?>()
-      ..mapWithAnnotations =
+    return Everything(
+      aBool: pigeonMap['aBool'] as bool?,
+      anInt: pigeonMap['anInt'] as int?,
+      aDouble: pigeonMap['aDouble'] as double?,
+      aString: pigeonMap['aString'] as String?,
+      aByteArray: pigeonMap['aByteArray'] as Uint8List?,
+      a4ByteArray: pigeonMap['a4ByteArray'] as Int32List?,
+      a8ByteArray: pigeonMap['a8ByteArray'] as Int64List?,
+      aFloatArray: pigeonMap['aFloatArray'] as Float64List?,
+      aList: pigeonMap['aList'] as List<Object?>?,
+      aMap: pigeonMap['aMap'] as Map<Object?, Object?>?,
+      nestedList:
+          (pigeonMap['nestedList'] as List<Object?>?)?.cast<List<bool?>?>(),
+      mapWithAnnotations:
           (pigeonMap['mapWithAnnotations'] as Map<Object?, Object?>?)
-              ?.cast<String?, String?>()
-      ..mapWithObject = (pigeonMap['mapWithObject'] as Map<Object?, Object?>?)
-          ?.cast<String?, Object?>();
+              ?.cast<String?, String?>(),
+      mapWithObject: (pigeonMap['mapWithObject'] as Map<Object?, Object?>?)
+          ?.cast<String?, Object?>(),
+    );
   }
 }
 
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 1a8f6b6..6421b9f 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
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 //
-// Autogenerated from Pigeon (v1.0.8), do not edit directly.
+// Autogenerated from Pigeon (v1.0.15), 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
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
new file mode 100644
index 0000000..c894284
--- /dev/null
+++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/non_null_fields.gen.dart
@@ -0,0 +1,188 @@
+// 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 (v1.0.15), 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';
+
+class SearchRequest {
+  SearchRequest({
+    required this.query,
+  });
+
+  String query;
+
+  Object encode() {
+    final Map<Object?, Object?> pigeonMap = <Object?, Object?>{};
+    pigeonMap['query'] = query;
+    return pigeonMap;
+  }
+
+  static SearchRequest decode(Object message) {
+    final Map<Object?, Object?> pigeonMap = message as Map<Object?, Object?>;
+    return SearchRequest(
+      query: (pigeonMap['query'] as String?)!,
+    );
+  }
+}
+
+class SearchReply {
+  SearchReply({
+    required this.result,
+    required this.error,
+    required this.indices,
+  });
+
+  String result;
+  String error;
+  List<int?> indices;
+
+  Object encode() {
+    final Map<Object?, Object?> pigeonMap = <Object?, Object?>{};
+    pigeonMap['result'] = result;
+    pigeonMap['error'] = error;
+    pigeonMap['indices'] = indices;
+    return pigeonMap;
+  }
+
+  static SearchReply decode(Object message) {
+    final Map<Object?, Object?> pigeonMap = message as Map<Object?, Object?>;
+    return SearchReply(
+      result: (pigeonMap['result'] as String?)!,
+      error: (pigeonMap['error'] as String?)!,
+      indices: (pigeonMap['indices'] as List<Object?>?)!.cast<int?>(),
+    );
+  }
+}
+
+class _NonNullHostApiCodec extends StandardMessageCodec {
+  const _NonNullHostApiCodec();
+  @override
+  void writeValue(WriteBuffer buffer, Object? value) {
+    if (value is SearchReply) {
+      buffer.putUint8(128);
+      writeValue(buffer, value.encode());
+    } else if (value is SearchRequest) {
+      buffer.putUint8(129);
+      writeValue(buffer, value.encode());
+    } else {
+      super.writeValue(buffer, value);
+    }
+  }
+
+  @override
+  Object? readValueOfType(int type, ReadBuffer buffer) {
+    switch (type) {
+      case 128:
+        return SearchReply.decode(readValue(buffer)!);
+
+      case 129:
+        return SearchRequest.decode(readValue(buffer)!);
+
+      default:
+        return super.readValueOfType(type, buffer);
+    }
+  }
+}
+
+class NonNullHostApi {
+  /// Constructor for [NonNullHostApi].  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.
+  NonNullHostApi({BinaryMessenger? binaryMessenger})
+      : _binaryMessenger = binaryMessenger;
+
+  final BinaryMessenger? _binaryMessenger;
+
+  static const MessageCodec<Object?> codec = _NonNullHostApiCodec();
+
+  Future<SearchReply> search(SearchRequest arg_nested) async {
+    final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+        'dev.flutter.pigeon.NonNullHostApi.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.',
+        details: null,
+      );
+    } 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 {
+      return (replyMap['result'] as SearchReply?)!;
+    }
+  }
+}
+
+class _NonNullFlutterApiCodec extends StandardMessageCodec {
+  const _NonNullFlutterApiCodec();
+  @override
+  void writeValue(WriteBuffer buffer, Object? value) {
+    if (value is SearchReply) {
+      buffer.putUint8(128);
+      writeValue(buffer, value.encode());
+    } else if (value is SearchRequest) {
+      buffer.putUint8(129);
+      writeValue(buffer, value.encode());
+    } else {
+      super.writeValue(buffer, value);
+    }
+  }
+
+  @override
+  Object? readValueOfType(int type, ReadBuffer buffer) {
+    switch (type) {
+      case 128:
+        return SearchReply.decode(readValue(buffer)!);
+
+      case 129:
+        return SearchRequest.decode(readValue(buffer)!);
+
+      default:
+        return super.readValueOfType(type, buffer);
+    }
+  }
+}
+
+abstract class NonNullFlutterApi {
+  static const MessageCodec<Object?> codec = _NonNullFlutterApiCodec();
+
+  SearchReply search(SearchRequest request);
+  static void setup(NonNullFlutterApi? api,
+      {BinaryMessenger? binaryMessenger}) {
+    {
+      final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+          'dev.flutter.pigeon.NonNullFlutterApi.search', codec,
+          binaryMessenger: binaryMessenger);
+      if (api == null) {
+        channel.setMessageHandler(null);
+      } else {
+        channel.setMessageHandler((Object? message) async {
+          assert(message != null,
+              'Argument for dev.flutter.pigeon.NonNullFlutterApi.search was null.');
+          final List<Object?> args = (message as List<Object?>?)!;
+          final SearchRequest? arg_request = (args[0] as SearchRequest?);
+          assert(arg_request != null,
+              'Argument for dev.flutter.pigeon.NonNullFlutterApi.search was null, expected non-null SearchRequest.');
+          final SearchReply output = api.search(arg_request!);
+          return output;
+        });
+      }
+    }
+  }
+}
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 b7e3f89..5b3c5c0 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
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 //
-// Autogenerated from Pigeon (v1.0.8), do not edit directly.
+// Autogenerated from Pigeon (v1.0.15), 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
@@ -13,6 +13,10 @@
 import 'package:flutter/services.dart';
 
 class SearchRequest {
+  SearchRequest({
+    this.query,
+  });
+
   String? query;
 
   Object encode() {
@@ -23,11 +27,18 @@
 
   static SearchRequest decode(Object message) {
     final Map<Object?, Object?> pigeonMap = message as Map<Object?, Object?>;
-    return SearchRequest()..query = pigeonMap['query'] as String?;
+    return SearchRequest(
+      query: pigeonMap['query'] as String?,
+    );
   }
 }
 
 class SearchReply {
+  SearchReply({
+    this.result,
+    this.error,
+  });
+
   String? result;
   String? error;
 
@@ -40,13 +51,18 @@
 
   static SearchReply decode(Object message) {
     final Map<Object?, Object?> pigeonMap = message as Map<Object?, Object?>;
-    return SearchReply()
-      ..result = pigeonMap['result'] as String?
-      ..error = pigeonMap['error'] as String?;
+    return SearchReply(
+      result: pigeonMap['result'] as String?,
+      error: pigeonMap['error'] as String?,
+    );
   }
 }
 
 class SearchRequests {
+  SearchRequests({
+    this.requests,
+  });
+
   List<Object?>? requests;
 
   Object encode() {
@@ -57,11 +73,17 @@
 
   static SearchRequests decode(Object message) {
     final Map<Object?, Object?> pigeonMap = message as Map<Object?, Object?>;
-    return SearchRequests()..requests = pigeonMap['requests'] as List<Object?>?;
+    return SearchRequests(
+      requests: pigeonMap['requests'] as List<Object?>?,
+    );
   }
 }
 
 class SearchReplies {
+  SearchReplies({
+    this.replies,
+  });
+
   List<Object?>? replies;
 
   Object encode() {
@@ -72,7 +94,9 @@
 
   static SearchReplies decode(Object message) {
     final Map<Object?, Object?> pigeonMap = message as Map<Object?, Object?>;
-    return SearchReplies()..replies = pigeonMap['replies'] as List<Object?>?;
+    return SearchReplies(
+      replies: pigeonMap['replies'] as List<Object?>?,
+    );
   }
 }
 
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 5259a06..780b412 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
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 //
-// Autogenerated from Pigeon (v1.0.8), do not edit directly.
+// Autogenerated from Pigeon (v1.0.15), 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
diff --git a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/test/non_null_fields_test.dart b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/test/non_null_fields_test.dart
new file mode 100644
index 0000000..b4ef08c
--- /dev/null
+++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/test/non_null_fields_test.dart
@@ -0,0 +1,13 @@
+// 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/non_null_fields.gen.dart';
+
+void main() {
+  test('test constructor', () {
+    final SearchRequest request = SearchRequest(query: 'what?');
+    expect(request.query, 'what?');
+  });
+}
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 6029534..0a81cb2 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
@@ -16,6 +16,8 @@
 		0DA5DFD626CC39D600D2354B /* multiple_arity.gen.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DA5DFD526CC39D600D2354B /* multiple_arity.gen.m */; };
 		0DA5DFD826CC3A2100D2354B /* MultipleArityTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DA5DFD726CC3A2100D2354B /* MultipleArityTest.m */; };
 		0DA5DFDB26CC3B3700D2354B /* HandlerBinaryMessenger.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DA5DFDA26CC3B3700D2354B /* HandlerBinaryMessenger.m */; };
+		0DBD8C3E279B73F700E4FDBA /* NonNullFieldsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DBD8C3D279B73F700E4FDBA /* NonNullFieldsTest.m */; };
+		0DBD8C41279B741800E4FDBA /* non_null_fields.gen.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DBD8C3F279B741800E4FDBA /* non_null_fields.gen.m */; };
 		0DD2E6BA2684031300A7D764 /* void_arg_host.gen.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DD2E6A62684031200A7D764 /* void_arg_host.gen.m */; };
 		0DD2E6BB2684031300A7D764 /* list.gen.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DD2E6A72684031200A7D764 /* list.gen.m */; };
 		0DD2E6BC2684031300A7D764 /* host2flutter.gen.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DD2E6AB2684031300A7D764 /* host2flutter.gen.m */; };
@@ -77,6 +79,9 @@
 		0DA5DFD726CC3A2100D2354B /* MultipleArityTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MultipleArityTest.m; sourceTree = "<group>"; };
 		0DA5DFD926CC3B3700D2354B /* HandlerBinaryMessenger.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HandlerBinaryMessenger.h; sourceTree = "<group>"; };
 		0DA5DFDA26CC3B3700D2354B /* HandlerBinaryMessenger.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HandlerBinaryMessenger.m; sourceTree = "<group>"; };
+		0DBD8C3D279B73F700E4FDBA /* NonNullFieldsTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NonNullFieldsTest.m; sourceTree = "<group>"; };
+		0DBD8C3F279B741800E4FDBA /* non_null_fields.gen.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = non_null_fields.gen.m; sourceTree = "<group>"; };
+		0DBD8C40279B741800E4FDBA /* non_null_fields.gen.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = non_null_fields.gen.h; sourceTree = "<group>"; };
 		0DD2E6A62684031200A7D764 /* void_arg_host.gen.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = void_arg_host.gen.m; sourceTree = "<group>"; };
 		0DD2E6A72684031200A7D764 /* list.gen.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = list.gen.m; sourceTree = "<group>"; };
 		0DD2E6A82684031200A7D764 /* host2flutter.gen.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = host2flutter.gen.h; sourceTree = "<group>"; };
@@ -150,6 +155,7 @@
 				0DA5DFD726CC3A2100D2354B /* MultipleArityTest.m */,
 				0DA5DFD926CC3B3700D2354B /* HandlerBinaryMessenger.h */,
 				0DA5DFDA26CC3B3700D2354B /* HandlerBinaryMessenger.m */,
+				0DBD8C3D279B73F700E4FDBA /* NonNullFieldsTest.m */,
 			);
 			path = RunnerTests;
 			sourceTree = "<group>";
@@ -187,6 +193,8 @@
 		97C146F01CF9000F007C117D /* Runner */ = {
 			isa = PBXGroup;
 			children = (
+				0DBD8C40279B741800E4FDBA /* non_null_fields.gen.h */,
+				0DBD8C3F279B741800E4FDBA /* non_null_fields.gen.m */,
 				0DA5DFD426CC39D600D2354B /* multiple_arity.gen.h */,
 				0DA5DFD526CC39D600D2354B /* multiple_arity.gen.m */,
 				0D6FD3C326A76D400046D8BD /* primitive.gen.h */,
@@ -371,6 +379,7 @@
 			buildActionMask = 2147483647;
 			files = (
 				0D50127523FF75B100CD5B95 /* RunnerTests.m in Sources */,
+				0DBD8C3E279B73F700E4FDBA /* NonNullFieldsTest.m in Sources */,
 				0DF4E5C5266ECF4A00AEA855 /* AllDatatypesTest.m in Sources */,
 				0DF4E5C8266ED80900AEA855 /* EchoMessenger.m in Sources */,
 				0DA5DFD826CC3A2100D2354B /* MultipleArityTest.m in Sources */,
@@ -386,6 +395,7 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				0DBD8C41279B741800E4FDBA /* non_null_fields.gen.m in Sources */,
 				0DD2E6BD2684031300A7D764 /* async_handlers.gen.m in Sources */,
 				0D7A910D268E5D700056B5E1 /* all_void.gen.m in Sources */,
 				0DD2E6C12684031300A7D764 /* voidhost.gen.m in Sources */,
diff --git a/packages/pigeon/platform_tests/ios_unit_tests/ios/RunnerTests/NonNullFieldsTest.m b/packages/pigeon/platform_tests/ios_unit_tests/ios/RunnerTests/NonNullFieldsTest.m
new file mode 100644
index 0000000..7c28a08
--- /dev/null
+++ b/packages/pigeon/platform_tests/ios_unit_tests/ios/RunnerTests/NonNullFieldsTest.m
@@ -0,0 +1,23 @@
+
+// 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 "non_null_fields.gen.h"
+
+///////////////////////////////////////////////////////////////////////////////////////////
+@interface NonNullFieldsTest : XCTestCase
+@end
+
+///////////////////////////////////////////////////////////////////////////////////////////
+@implementation NonNullFieldsTest
+
+- (void)testMake {
+  NNFSearchRequest* request = [NNFSearchRequest makeWithQuery:@"hello"];
+  XCTAssertEqualObjects(@"hello", request.query);
+}
+
+@end
diff --git a/packages/pigeon/pubspec.yaml b/packages/pigeon/pubspec.yaml
index 04578e3..594479f 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.15 # This must match the version in lib/generator_tools.dart
+version: 1.0.16 # 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 d688bee..7bc2c9b 100755
--- a/packages/pigeon/run_tests.sh
+++ b/packages/pigeon/run_tests.sh
@@ -240,6 +240,7 @@
   gen_ios_unittests_code ./pigeons/list.dart "LST"
   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/primitive.dart ""
   gen_ios_unittests_code ./pigeons/void_arg_flutter.dart "VAF"
   gen_ios_unittests_code ./pigeons/void_arg_host.dart "VAH"
@@ -296,6 +297,7 @@
   gen_android_unittests_code ./pigeons/list.dart PigeonList
   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/primitive.dart Primitive
   gen_android_unittests_code ./pigeons/void_arg_flutter.dart VoidArgFlutter
   gen_android_unittests_code ./pigeons/void_arg_host.dart VoidArgHost
diff --git a/packages/pigeon/test/dart_generator_test.dart b/packages/pigeon/test/dart_generator_test.dart
index 0733fad..8a9dc51 100644
--- a/packages/pigeon/test/dart_generator_test.dart
+++ b/packages/pigeon/test/dart_generator_test.dart
@@ -199,7 +199,7 @@
     expect(
       code.replaceAll('\n', ' ').replaceAll('  ', ''),
       contains(
-        '..nested = pigeonMap[\'nested\'] != null ? Input.decode(pigeonMap[\'nested\']) : null;',
+        'nested: pigeonMap[\'nested\'] != null ? Input.decode(pigeonMap[\'nested\']) : null',
       ),
     );
   });
diff --git a/packages/pigeon/test/java_generator_test.dart b/packages/pigeon/test/java_generator_test.dart
index ae815ef..f162d3c 100644
--- a/packages/pigeon/test/java_generator_test.dart
+++ b/packages/pigeon/test/java_generator_test.dart
@@ -31,7 +31,7 @@
     final String code = sink.toString();
     expect(code, contains('public class Messages'));
     expect(code, contains('public static class Foobar'));
-    expect(code, contains('private Long field1;'));
+    expect(code, contains('private @Nullable Long field1;'));
   });
 
   test('gen one enum', () {
@@ -200,14 +200,14 @@
     const JavaOptions javaOptions = JavaOptions(className: 'Messages');
     generateJava(javaOptions, root, sink);
     final String code = sink.toString();
-    expect(code, contains('private Boolean aBool;'));
-    expect(code, contains('private Long aInt;'));
-    expect(code, contains('private Double aDouble;'));
-    expect(code, contains('private String aString;'));
-    expect(code, contains('private byte[] aUint8List;'));
-    expect(code, contains('private int[] aInt32List;'));
-    expect(code, contains('private long[] aInt64List;'));
-    expect(code, contains('private double[] aFloat64List;'));
+    expect(code, contains('private @Nullable Boolean aBool;'));
+    expect(code, contains('private @Nullable Long aInt;'));
+    expect(code, contains('private @Nullable Double aDouble;'));
+    expect(code, contains('private @Nullable String aString;'));
+    expect(code, contains('private @Nullable byte[] aUint8List;'));
+    expect(code, contains('private @Nullable int[] aInt32List;'));
+    expect(code, contains('private @Nullable long[] aInt64List;'));
+    expect(code, contains('private @Nullable double[] aFloat64List;'));
   });
 
   test('gen one flutter api', () {
@@ -408,7 +408,7 @@
     generateJava(javaOptions, root, sink);
     final String code = sink.toString();
     expect(code, contains('public static class Foobar'));
-    expect(code, contains('private List<Object> field1;'));
+    expect(code, contains('private @Nullable List<Object> field1;'));
   });
 
   test('gen map', () {
@@ -428,7 +428,7 @@
     generateJava(javaOptions, root, sink);
     final String code = sink.toString();
     expect(code, contains('public static class Foobar'));
-    expect(code, contains('private Map<Object, Object> field1;'));
+    expect(code, contains('private @Nullable Map<Object, Object> field1;'));
   });
 
   test('gen nested', () {
@@ -468,8 +468,8 @@
     expect(code, contains('public class Messages'));
     expect(code, contains('public static class Outer'));
     expect(code, contains('public static class Nested'));
-    expect(code, contains('private Nested nested;'));
-    expect(code, contains('Nested.fromMap((Map)nested);'));
+    expect(code, contains('private @Nullable Nested nested;'));
+    expect(code, contains('Nested.fromMap((Map)nested)'));
     expect(code,
         contains('put("nested", (nested == null) ? null : nested.toMap());'));
   });
@@ -616,7 +616,7 @@
     expect(
         code,
         contains(
-            'fromMapResult.enum1 = enum1 == null ? null : Enum1.values()[(int)enum1];'));
+            'pigeonResult.setEnum1(enum1 == null ? null : Enum1.values()[(int)enum1])'));
   });
 
   Iterable<String> _makeIterable(String string) sync* {
diff --git a/packages/pigeon/test/objc_generator_test.dart b/packages/pigeon/test/objc_generator_test.dart
index d1ddbc3..6ff4cce 100644
--- a/packages/pigeon/test/objc_generator_test.dart
+++ b/packages/pigeon/test/objc_generator_test.dart
@@ -110,7 +110,8 @@
     final String code = sink.toString();
     expect(code, contains('#import "foo.h"'));
     expect(code, contains('@implementation Foobar'));
-    expect(code, contains('result.enum1 = [dict[@"enum1"] integerValue];'));
+    expect(
+        code, contains('pigeonResult.enum1 = [dict[@"enum1"] integerValue];'));
   });
 
   test('gen one class header with enum', () {
@@ -307,7 +308,7 @@
     generateObjcSource(const ObjcOptions(header: 'foo.h'), root, sink);
     final String code = sink.toString();
     expect(code, contains('@implementation Foobar'));
-    expect(code, contains('result.aBool = dict[@"aBool"];'));
+    expect(code, contains('pigeonResult.aBool = dict[@"aBool"];'));
   });
 
   test('nested class header', () {
@@ -350,7 +351,8 @@
     final StringBuffer sink = StringBuffer();
     generateObjcSource(const ObjcOptions(header: 'foo.h'), root, sink);
     final String code = sink.toString();
-    expect(code, contains('result.nested = [Input fromMap:dict[@"nested"]];'));
+    expect(code,
+        contains('pigeonResult.nested = [Input fromMap:dict[@"nested"]];'));
     expect(code, matches('[self.nested toMap].*@"nested"'));
   });
 
@@ -1559,4 +1561,20 @@
       expect(code, matches('divideValue:.*by:.*completion.*{'));
     }
   });
+
+  test('test non null field', () {
+    final Root root = Root(apis: <Api>[], classes: <Class>[
+      Class(name: 'Foobar', fields: <NamedType>[
+        NamedType(
+            type: const TypeDeclaration(baseName: 'String', isNullable: false),
+            name: 'field1',
+            offset: null)
+      ]),
+    ], enums: <Enum>[]);
+    final StringBuffer sink = StringBuffer();
+    generateObjcHeader(const ObjcOptions(), root, sink);
+    final String code = sink.toString();
+    expect(code, contains('@interface Foobar'));
+    expect(code, contains('@property(nonatomic, copy) NSString * field1'));
+  });
 }
diff --git a/packages/pigeon/test/pigeon_lib_test.dart b/packages/pigeon/test/pigeon_lib_test.dart
index 924087b..3ea099f 100644
--- a/packages/pigeon/test/pigeon_lib_test.dart
+++ b/packages/pigeon/test/pigeon_lib_test.dart
@@ -498,7 +498,23 @@
     const String code = '''
 class Foo {
   int? x;
-  Foo(this.x);
+  Foo({this.x});
+}
+
+@HostApi()
+abstract class Api {
+  Foo doit(Foo foo);
+}
+''';
+    final ParseResults results = _parseSource(code);
+    expect(results.errors.length, 0);
+  });
+
+  test('constructor body in data class', () {
+    const String code = '''
+class Foo {
+  int? x;
+  Foo({this.x}) { print('hi'); }
 }
 
 @HostApi()
@@ -512,6 +528,42 @@
     expect(results.errors[0].message, contains('Constructor'));
   });
 
+  test('constructor body in data class', () {
+    const String code = '''
+class Foo {
+  int? x;
+  Foo() : x = 0;
+}
+
+@HostApi()
+abstract class Api {
+  Foo doit(Foo foo);
+}
+''';
+    final ParseResults results = _parseSource(code);
+    expect(results.errors.length, 1);
+    expect(results.errors[0].lineNumber, 3);
+    expect(results.errors[0].message, contains('Constructor'));
+  });
+
+  test('constructor in api class', () {
+    const String code = '''
+class Foo {
+  int? x;
+}
+
+@HostApi()
+abstract class Api {
+  Api() { print('hi'); }
+  Foo doit(Foo foo);
+}
+''';
+    final ParseResults results = _parseSource(code);
+    expect(results.errors.length, 1);
+    expect(results.errors[0].lineNumber, 7);
+    expect(results.errors[0].message, contains('Constructor'));
+  });
+
   test('nullable api arguments', () {
     const String code = '''
 class Foo {