[pigeon]fix a crash when casting NSNull to an Array (#4289)

Fixes a crash introduced in https://github.com/flutter/packages/pull/3658: 

```
static func fromList(_ list: [Any?]) -> NativeAuthSession? {
  if let userPoolTokensList = list[2] as! [Any?]? {}
}
```
where `list[2]` is `NSNull`, which is not an Optional, hence can't be casted to `[Any?]?`. 

Recall that `nilOrValue` helper is created for this purpose. So the fix is simply: 

```
static func fromList(_ list: [Any?]) -> NativeAuthSession? {
  if let userPoolTokensList: [Any?] = nilOrValue(list[2]) {} // <- HERE
}
```

## Why didn't we catch this regression

Missing unit tests - we did not test `NSNull` fields to ensure `nilOrValue` works for them.  

## Why did it work in previous version? 

It's surprising that this worked fine before! The original code is: 
```
static func fromList(_ list: [Any]) -> NativeAuthSession? {
  if let userPoolTokensList = list[2] as! [Any]? {}
}
```
From [my previous PR](https://github.com/flutter/flutter/issues/129283), we know that list[2] is an implicit optional (that contains a NSNull value). I think Swift made an exception here when casting implicit optional NSNull (either intentionally or unintentionally). 

*List which issues are fixed by this PR. You must list at least one issue.*

Fixes https://github.com/flutter/flutter/issues/129283

*If you had to change anything in the [flutter/tests] repo, include a link to the migration guide as per the [breaking change policy].*
diff --git a/packages/pigeon/CHANGELOG.md b/packages/pigeon/CHANGELOG.md
index addd59f..4e941c0 100644
--- a/packages/pigeon/CHANGELOG.md
+++ b/packages/pigeon/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 10.1.2
+
+* [swift] Fixes a crash when passing `null` for nested nullable classes. 
+
 ## 10.1.1
 
 * Updates README to better reflect modern usage.
diff --git a/packages/pigeon/example/app/android/app/src/main/java/io/flutter/plugins/Messages.java b/packages/pigeon/example/app/android/app/src/main/java/io/flutter/plugins/Messages.java
index c1c3b7a..1f5aaeb 100644
--- a/packages/pigeon/example/app/android/app/src/main/java/io/flutter/plugins/Messages.java
+++ b/packages/pigeon/example/app/android/app/src/main/java/io/flutter/plugins/Messages.java
@@ -1,7 +1,7 @@
 // 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 (v10.1.1), do not edit directly.
+// Autogenerated from Pigeon (v10.1.2), do not edit directly.
 // See also: https://pub.dev/packages/pigeon
 
 import android.util.Log;
diff --git a/packages/pigeon/example/app/android/app/src/main/kotlin/dev/flutter/pigeon_example_app/Messages.g.kt b/packages/pigeon/example/app/android/app/src/main/kotlin/dev/flutter/pigeon_example_app/Messages.g.kt
index bc2dfa8..4aa5169 100644
--- a/packages/pigeon/example/app/android/app/src/main/kotlin/dev/flutter/pigeon_example_app/Messages.g.kt
+++ b/packages/pigeon/example/app/android/app/src/main/kotlin/dev/flutter/pigeon_example_app/Messages.g.kt
@@ -1,7 +1,7 @@
 // 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 (v10.1.1), do not edit directly.
+// Autogenerated from Pigeon (v10.1.2), do not edit directly.
 // See also: https://pub.dev/packages/pigeon
 
 
diff --git a/packages/pigeon/example/app/ios/Runner/Messages.g.swift b/packages/pigeon/example/app/ios/Runner/Messages.g.swift
index 8dd1a6b..3467edf 100644
--- a/packages/pigeon/example/app/ios/Runner/Messages.g.swift
+++ b/packages/pigeon/example/app/ios/Runner/Messages.g.swift
@@ -1,7 +1,7 @@
 // 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 (v10.1.1), do not edit directly.
+// Autogenerated from Pigeon (v10.1.2), do not edit directly.
 // See also: https://pub.dev/packages/pigeon
 
 import Foundation
diff --git a/packages/pigeon/example/app/lib/src/messages.g.dart b/packages/pigeon/example/app/lib/src/messages.g.dart
index 25e75e3..66ae5c5 100644
--- a/packages/pigeon/example/app/lib/src/messages.g.dart
+++ b/packages/pigeon/example/app/lib/src/messages.g.dart
@@ -1,7 +1,7 @@
 // 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 (v10.1.1), do not edit directly.
+// Autogenerated from Pigeon (v10.1.2), do not edit directly.
 // See also: https://pub.dev/packages/pigeon
 // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import
 
diff --git a/packages/pigeon/example/app/macos/Runner/messages.g.h b/packages/pigeon/example/app/macos/Runner/messages.g.h
index d789948..5fb709c 100644
--- a/packages/pigeon/example/app/macos/Runner/messages.g.h
+++ b/packages/pigeon/example/app/macos/Runner/messages.g.h
@@ -1,7 +1,7 @@
 // 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 (v10.1.1), do not edit directly.
+// Autogenerated from Pigeon (v10.1.2), do not edit directly.
 // See also: https://pub.dev/packages/pigeon
 
 #import <Foundation/Foundation.h>
diff --git a/packages/pigeon/example/app/macos/Runner/messages.g.m b/packages/pigeon/example/app/macos/Runner/messages.g.m
index 8aa8436..8a43bff 100644
--- a/packages/pigeon/example/app/macos/Runner/messages.g.m
+++ b/packages/pigeon/example/app/macos/Runner/messages.g.m
@@ -1,7 +1,7 @@
 // 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 (v10.1.1), do not edit directly.
+// Autogenerated from Pigeon (v10.1.2), do not edit directly.
 // See also: https://pub.dev/packages/pigeon
 
 #import "messages.g.h"
diff --git a/packages/pigeon/example/app/windows/runner/messages.g.cpp b/packages/pigeon/example/app/windows/runner/messages.g.cpp
index ba7c06b..50053cf 100644
--- a/packages/pigeon/example/app/windows/runner/messages.g.cpp
+++ b/packages/pigeon/example/app/windows/runner/messages.g.cpp
@@ -1,7 +1,7 @@
 // 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 (v10.1.1), do not edit directly.
+// Autogenerated from Pigeon (v10.1.2), do not edit directly.
 // See also: https://pub.dev/packages/pigeon
 
 #undef _HAS_EXCEPTIONS
diff --git a/packages/pigeon/example/app/windows/runner/messages.g.h b/packages/pigeon/example/app/windows/runner/messages.g.h
index 1bf5ddd..917de2d 100644
--- a/packages/pigeon/example/app/windows/runner/messages.g.h
+++ b/packages/pigeon/example/app/windows/runner/messages.g.h
@@ -1,7 +1,7 @@
 // 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 (v10.1.1), do not edit directly.
+// Autogenerated from Pigeon (v10.1.2), do not edit directly.
 // See also: https://pub.dev/packages/pigeon
 
 #ifndef PIGEON_MESSAGES_G_H_
diff --git a/packages/pigeon/lib/generator_tools.dart b/packages/pigeon/lib/generator_tools.dart
index 2b67bd5..f697cb4 100644
--- a/packages/pigeon/lib/generator_tools.dart
+++ b/packages/pigeon/lib/generator_tools.dart
@@ -11,7 +11,7 @@
 /// The current version of pigeon.
 ///
 /// This must match the version in pubspec.yaml.
-const String pigeonVersion = '10.1.1';
+const String pigeonVersion = '10.1.2';
 
 /// Read all the content from [stdin] to a String.
 String readStdin() {
diff --git a/packages/pigeon/lib/swift_generator.dart b/packages/pigeon/lib/swift_generator.dart
index a133a24..26547fb 100644
--- a/packages/pigeon/lib/swift_generator.dart
+++ b/packages/pigeon/lib/swift_generator.dart
@@ -627,7 +627,8 @@
       if (listEncodedClassNames != null &&
           listEncodedClassNames.contains(type.baseName)) {
         indent.writeln('var $variableName: $fieldType? = nil');
-        indent.write('if let ${variableName}List = $value as! [Any?]? ');
+        indent
+            .write('if let ${variableName}List: [Any?] = nilOrValue($value) ');
         indent.addScoped('{', '}', () {
           indent.writeln(
               '$variableName = $fieldType.fromList(${variableName}List)');
diff --git a/packages/pigeon/mock_handler_tester/test/message.dart b/packages/pigeon/mock_handler_tester/test/message.dart
index ac7d0bf..de2b7db 100644
--- a/packages/pigeon/mock_handler_tester/test/message.dart
+++ b/packages/pigeon/mock_handler_tester/test/message.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 (v9.2.5), do not edit directly.
+// Autogenerated from Pigeon (v10.1.1), do not edit directly.
 // See also: https://pub.dev/packages/pigeon
 // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import
 
diff --git a/packages/pigeon/mock_handler_tester/test/test.dart b/packages/pigeon/mock_handler_tester/test/test.dart
index 3114075..3af78ca 100644
--- a/packages/pigeon/mock_handler_tester/test/test.dart
+++ b/packages/pigeon/mock_handler_tester/test/test.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 (v9.2.5), do not edit directly.
+// Autogenerated from Pigeon (v10.1.1), 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, unnecessary_import
 // ignore_for_file: avoid_relative_lib_imports
diff --git a/packages/pigeon/pigeons/core_tests.dart b/packages/pigeon/pigeons/core_tests.dart
index 15de1ff..ceb1de2 100644
--- a/packages/pigeon/pigeons/core_tests.dart
+++ b/packages/pigeon/pigeons/core_tests.dart
@@ -10,7 +10,7 @@
   three,
 }
 
-// A class containing all supported types.
+/// A class containing all supported types.
 class AllTypes {
   AllTypes({
     this.aBool = false,
@@ -43,7 +43,7 @@
   String aString;
 }
 
-// A class containing all supported nullable types.
+/// A class containing all supported nullable types.
 class AllNullableTypes {
   AllNullableTypes(
     this.aNullableBool,
@@ -82,10 +82,15 @@
   String? aNullableString;
 }
 
-// A class for testing nested object handling.
-class AllNullableTypesWrapper {
-  AllNullableTypesWrapper(this.values);
-  AllNullableTypes values;
+/// A class for testing nested class handling.
+///
+/// This is needed to test nested nullable and non-nullable classes,
+/// `AllNullableTypes` is non-nullable here as it is easier to instantiate
+/// than `AllTypes` when testing doesn't require both (ie. testing null classes).
+class AllClassesWrapper {
+  AllClassesWrapper(this.allNullableTypes, this.allTypes);
+  AllNullableTypes allNullableTypes;
+  AllTypes? allTypes;
 }
 
 /// The core interface that each host language plugin must implement in
@@ -152,6 +157,11 @@
   @SwiftFunction('echo(_:)')
   Map<String?, Object?> echoMap(Map<String?, Object?> aMap);
 
+  /// Returns the passed map to test nested class serialization and deserialization.
+  @ObjCSelector('echoClassWrapper:')
+  @SwiftFunction('echo(_:)')
+  AllClassesWrapper echoClassWrapper(AllClassesWrapper wrapper);
+
   // ========== Synchronous nullable method tests ==========
 
   /// Returns the passed object, to test serialization and deserialization.
@@ -163,13 +173,13 @@
   /// sending of nested objects.
   @ObjCSelector('extractNestedNullableStringFrom:')
   @SwiftFunction('extractNestedNullableString(from:)')
-  String? extractNestedNullableString(AllNullableTypesWrapper wrapper);
+  String? extractNestedNullableString(AllClassesWrapper wrapper);
 
   /// Returns the inner `aString` value from the wrapped object, to test
   /// sending of nested objects.
   @ObjCSelector('createNestedObjectWithNullableString:')
   @SwiftFunction('createNestedObject(with:)')
-  AllNullableTypesWrapper createNestedNullableString(String? nullableString);
+  AllClassesWrapper createNestedNullableString(String? nullableString);
 
   /// Returns passed in arguments of multiple types.
   @ObjCSelector('sendMultipleNullableTypesABool:anInt:aString:')
diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/main/java/com/example/alternate_language_test_plugin/AlternateLanguageTestPlugin.java b/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/main/java/com/example/alternate_language_test_plugin/AlternateLanguageTestPlugin.java
index 6ca4c01..c9c4406 100644
--- a/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/main/java/com/example/alternate_language_test_plugin/AlternateLanguageTestPlugin.java
+++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/main/java/com/example/alternate_language_test_plugin/AlternateLanguageTestPlugin.java
@@ -6,8 +6,8 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import com.example.alternate_language_test_plugin.CoreTests.AllClassesWrapper;
 import com.example.alternate_language_test_plugin.CoreTests.AllNullableTypes;
-import com.example.alternate_language_test_plugin.CoreTests.AllNullableTypesWrapper;
 import com.example.alternate_language_test_plugin.CoreTests.AllTypes;
 import com.example.alternate_language_test_plugin.CoreTests.FlutterIntegrationCoreApi;
 import com.example.alternate_language_test_plugin.CoreTests.HostIntegrationCoreApi;
@@ -99,17 +99,21 @@
     return aMap;
   }
 
-  @Override
-  public @Nullable String extractNestedNullableString(@NonNull AllNullableTypesWrapper wrapper) {
-    return wrapper.getValues().getANullableString();
+  @NonNull
+  public AllClassesWrapper echoClassWrapper(@NonNull AllClassesWrapper wrapper) {
+    return wrapper;
   }
 
   @Override
-  public @NonNull AllNullableTypesWrapper createNestedNullableString(
-      @Nullable String nullableString) {
+  public @Nullable String extractNestedNullableString(@NonNull AllClassesWrapper wrapper) {
+    return wrapper.getAllNullableTypes().getANullableString();
+  }
+
+  @Override
+  public @NonNull AllClassesWrapper createNestedNullableString(@Nullable String nullableString) {
     AllNullableTypes innerObject =
         new AllNullableTypes.Builder().setANullableString(nullableString).build();
-    return new AllNullableTypesWrapper.Builder().setValues(innerObject).build();
+    return new AllClassesWrapper.Builder().setAllNullableTypes(innerObject).build();
   }
 
   @Override
diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/main/java/com/example/alternate_language_test_plugin/CoreTests.java b/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/main/java/com/example/alternate_language_test_plugin/CoreTests.java
index 8b8f6b5..aa53e50 100644
--- a/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/main/java/com/example/alternate_language_test_plugin/CoreTests.java
+++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/main/java/com/example/alternate_language_test_plugin/CoreTests.java
@@ -71,7 +71,11 @@
     }
   }
 
-  /** Generated class from Pigeon that represents data sent in messages. */
+  /**
+   * A class containing all supported types.
+   *
+   * <p>Generated class from Pigeon that represents data sent in messages.
+   */
   public static final class AllTypes {
     private @NonNull Boolean aBool;
 
@@ -388,7 +392,11 @@
     }
   }
 
-  /** Generated class from Pigeon that represents data sent in messages. */
+  /**
+   * A class containing all supported nullable types.
+   *
+   * <p>Generated class from Pigeon that represents data sent in messages.
+   */
   public static final class AllNullableTypes {
     private @Nullable Boolean aNullableBool;
 
@@ -735,52 +743,84 @@
     }
   }
 
-  /** Generated class from Pigeon that represents data sent in messages. */
-  public static final class AllNullableTypesWrapper {
-    private @NonNull AllNullableTypes values;
+  /**
+   * A class for testing nested class handling.
+   *
+   * <p>This is needed to test nested nullable and non-nullable classes, `AllNullableTypes` is
+   * non-nullable here as it is easier to instantiate than `AllTypes` when testing doesn't require
+   * both (ie. testing null classes).
+   *
+   * <p>Generated class from Pigeon that represents data sent in messages.
+   */
+  public static final class AllClassesWrapper {
+    private @NonNull AllNullableTypes allNullableTypes;
 
-    public @NonNull AllNullableTypes getValues() {
-      return values;
+    public @NonNull AllNullableTypes getAllNullableTypes() {
+      return allNullableTypes;
     }
 
-    public void setValues(@NonNull AllNullableTypes setterArg) {
+    public void setAllNullableTypes(@NonNull AllNullableTypes setterArg) {
       if (setterArg == null) {
-        throw new IllegalStateException("Nonnull field \"values\" is null.");
+        throw new IllegalStateException("Nonnull field \"allNullableTypes\" is null.");
       }
-      this.values = setterArg;
+      this.allNullableTypes = setterArg;
+    }
+
+    private @Nullable AllTypes allTypes;
+
+    public @Nullable AllTypes getAllTypes() {
+      return allTypes;
+    }
+
+    public void setAllTypes(@Nullable AllTypes setterArg) {
+      this.allTypes = setterArg;
     }
 
     /** Constructor is non-public to enforce null safety; use Builder. */
-    AllNullableTypesWrapper() {}
+    AllClassesWrapper() {}
 
     public static final class Builder {
 
-      private @Nullable AllNullableTypes values;
+      private @Nullable AllNullableTypes allNullableTypes;
 
-      public @NonNull Builder setValues(@NonNull AllNullableTypes setterArg) {
-        this.values = setterArg;
+      public @NonNull Builder setAllNullableTypes(@NonNull AllNullableTypes setterArg) {
+        this.allNullableTypes = setterArg;
         return this;
       }
 
-      public @NonNull AllNullableTypesWrapper build() {
-        AllNullableTypesWrapper pigeonReturn = new AllNullableTypesWrapper();
-        pigeonReturn.setValues(values);
+      private @Nullable AllTypes allTypes;
+
+      public @NonNull Builder setAllTypes(@Nullable AllTypes setterArg) {
+        this.allTypes = setterArg;
+        return this;
+      }
+
+      public @NonNull AllClassesWrapper build() {
+        AllClassesWrapper pigeonReturn = new AllClassesWrapper();
+        pigeonReturn.setAllNullableTypes(allNullableTypes);
+        pigeonReturn.setAllTypes(allTypes);
         return pigeonReturn;
       }
     }
 
     @NonNull
     ArrayList<Object> toList() {
-      ArrayList<Object> toListResult = new ArrayList<Object>(1);
-      toListResult.add((values == null) ? null : values.toList());
+      ArrayList<Object> toListResult = new ArrayList<Object>(2);
+      toListResult.add((allNullableTypes == null) ? null : allNullableTypes.toList());
+      toListResult.add((allTypes == null) ? null : allTypes.toList());
       return toListResult;
     }
 
-    static @NonNull AllNullableTypesWrapper fromList(@NonNull ArrayList<Object> list) {
-      AllNullableTypesWrapper pigeonResult = new AllNullableTypesWrapper();
-      Object values = list.get(0);
-      pigeonResult.setValues(
-          (values == null) ? null : AllNullableTypes.fromList((ArrayList<Object>) values));
+    static @NonNull AllClassesWrapper fromList(@NonNull ArrayList<Object> list) {
+      AllClassesWrapper pigeonResult = new AllClassesWrapper();
+      Object allNullableTypes = list.get(0);
+      pigeonResult.setAllNullableTypes(
+          (allNullableTypes == null)
+              ? null
+              : AllNullableTypes.fromList((ArrayList<Object>) allNullableTypes));
+      Object allTypes = list.get(1);
+      pigeonResult.setAllTypes(
+          (allTypes == null) ? null : AllTypes.fromList((ArrayList<Object>) allTypes));
       return pigeonResult;
     }
   }
@@ -848,9 +888,9 @@
     protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) {
       switch (type) {
         case (byte) 128:
-          return AllNullableTypes.fromList((ArrayList<Object>) readValue(buffer));
+          return AllClassesWrapper.fromList((ArrayList<Object>) readValue(buffer));
         case (byte) 129:
-          return AllNullableTypesWrapper.fromList((ArrayList<Object>) readValue(buffer));
+          return AllNullableTypes.fromList((ArrayList<Object>) readValue(buffer));
         case (byte) 130:
           return AllTypes.fromList((ArrayList<Object>) readValue(buffer));
         case (byte) 131:
@@ -862,12 +902,12 @@
 
     @Override
     protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) {
-      if (value instanceof AllNullableTypes) {
+      if (value instanceof AllClassesWrapper) {
         stream.write(128);
-        writeValue(stream, ((AllNullableTypes) value).toList());
-      } else if (value instanceof AllNullableTypesWrapper) {
+        writeValue(stream, ((AllClassesWrapper) value).toList());
+      } else if (value instanceof AllNullableTypes) {
         stream.write(129);
-        writeValue(stream, ((AllNullableTypesWrapper) value).toList());
+        writeValue(stream, ((AllNullableTypes) value).toList());
       } else if (value instanceof AllTypes) {
         stream.write(130);
         writeValue(stream, ((AllTypes) value).toList());
@@ -926,6 +966,9 @@
     /** Returns the passed map, to test serialization and deserialization. */
     @NonNull
     Map<String, Object> echoMap(@NonNull Map<String, Object> aMap);
+    /** Returns the passed map to test nested class serialization and deserialization. */
+    @NonNull
+    AllClassesWrapper echoClassWrapper(@NonNull AllClassesWrapper wrapper);
     /** Returns the passed object, to test serialization and deserialization. */
     @Nullable
     AllNullableTypes echoAllNullableTypes(@Nullable AllNullableTypes everything);
@@ -933,12 +976,12 @@
      * Returns the inner `aString` value from the wrapped object, to test sending of nested objects.
      */
     @Nullable
-    String extractNestedNullableString(@NonNull AllNullableTypesWrapper wrapper);
+    String extractNestedNullableString(@NonNull AllClassesWrapper wrapper);
     /**
      * Returns the inner `aString` value from the wrapped object, to test sending of nested objects.
      */
     @NonNull
-    AllNullableTypesWrapper createNestedNullableString(@Nullable String nullableString);
+    AllClassesWrapper createNestedNullableString(@Nullable String nullableString);
     /** Returns passed in arguments of multiple types. */
     @NonNull
     AllNullableTypes sendMultipleNullableTypes(
@@ -1386,6 +1429,31 @@
         BasicMessageChannel<Object> channel =
             new BasicMessageChannel<>(
                 binaryMessenger,
+                "dev.flutter.pigeon.HostIntegrationCoreApi.echoClassWrapper",
+                getCodec());
+        if (api != null) {
+          channel.setMessageHandler(
+              (message, reply) -> {
+                ArrayList<Object> wrapped = new ArrayList<Object>();
+                ArrayList<Object> args = (ArrayList<Object>) message;
+                AllClassesWrapper wrapperArg = (AllClassesWrapper) args.get(0);
+                try {
+                  AllClassesWrapper output = api.echoClassWrapper(wrapperArg);
+                  wrapped.add(0, output);
+                } catch (Throwable exception) {
+                  ArrayList<Object> wrappedError = wrapError(exception);
+                  wrapped = wrappedError;
+                }
+                reply.reply(wrapped);
+              });
+        } else {
+          channel.setMessageHandler(null);
+        }
+      }
+      {
+        BasicMessageChannel<Object> channel =
+            new BasicMessageChannel<>(
+                binaryMessenger,
                 "dev.flutter.pigeon.HostIntegrationCoreApi.echoAllNullableTypes",
                 getCodec());
         if (api != null) {
@@ -1418,7 +1486,7 @@
               (message, reply) -> {
                 ArrayList<Object> wrapped = new ArrayList<Object>();
                 ArrayList<Object> args = (ArrayList<Object>) message;
-                AllNullableTypesWrapper wrapperArg = (AllNullableTypesWrapper) args.get(0);
+                AllClassesWrapper wrapperArg = (AllClassesWrapper) args.get(0);
                 try {
                   String output = api.extractNestedNullableString(wrapperArg);
                   wrapped.add(0, output);
@@ -1445,8 +1513,7 @@
                 ArrayList<Object> args = (ArrayList<Object>) message;
                 String nullableStringArg = (String) args.get(0);
                 try {
-                  AllNullableTypesWrapper output =
-                      api.createNestedNullableString(nullableStringArg);
+                  AllClassesWrapper output = api.createNestedNullableString(nullableStringArg);
                   wrapped.add(0, output);
                 } catch (Throwable exception) {
                   ArrayList<Object> wrappedError = wrapError(exception);
@@ -2968,9 +3035,9 @@
     protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) {
       switch (type) {
         case (byte) 128:
-          return AllNullableTypes.fromList((ArrayList<Object>) readValue(buffer));
+          return AllClassesWrapper.fromList((ArrayList<Object>) readValue(buffer));
         case (byte) 129:
-          return AllNullableTypesWrapper.fromList((ArrayList<Object>) readValue(buffer));
+          return AllNullableTypes.fromList((ArrayList<Object>) readValue(buffer));
         case (byte) 130:
           return AllTypes.fromList((ArrayList<Object>) readValue(buffer));
         case (byte) 131:
@@ -2982,12 +3049,12 @@
 
     @Override
     protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) {
-      if (value instanceof AllNullableTypes) {
+      if (value instanceof AllClassesWrapper) {
         stream.write(128);
-        writeValue(stream, ((AllNullableTypes) value).toList());
-      } else if (value instanceof AllNullableTypesWrapper) {
+        writeValue(stream, ((AllClassesWrapper) value).toList());
+      } else if (value instanceof AllNullableTypes) {
         stream.write(129);
-        writeValue(stream, ((AllNullableTypesWrapper) value).toList());
+        writeValue(stream, ((AllNullableTypes) value).toList());
       } else if (value instanceof AllTypes) {
         stream.write(130);
         writeValue(stream, ((AllTypes) value).toList());
diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/test/java/com/example/alternate_language_test_plugin/AllDatatypesTest.java b/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/test/java/com/example/alternate_language_test_plugin/AllDatatypesTest.java
index e2d6414..cba21e7 100644
--- a/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/test/java/com/example/alternate_language_test_plugin/AllDatatypesTest.java
+++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/android/src/test/java/com/example/alternate_language_test_plugin/AllDatatypesTest.java
@@ -8,6 +8,7 @@
 import static org.mockito.Mockito.*;
 
 import com.example.alternate_language_test_plugin.CoreTests.AllNullableTypes;
+import com.example.alternate_language_test_plugin.CoreTests.AllTypes;
 import com.example.alternate_language_test_plugin.CoreTests.FlutterIntegrationCoreApi;
 import io.flutter.plugin.common.BinaryMessenger;
 import java.nio.ByteBuffer;
@@ -17,6 +18,58 @@
 import org.junit.Test;
 
 public class AllDatatypesTest {
+
+  void compareAllTypes(AllTypes firstTypes, AllTypes secondTypes) {
+    assertEquals(firstTypes == null, secondTypes == null);
+    if (firstTypes == null || secondTypes == null) {
+      return;
+    }
+    assertEquals(firstTypes.getABool(), secondTypes.getABool());
+    assertEquals(firstTypes.getAnInt(), secondTypes.getAnInt());
+    assertEquals(firstTypes.getAnInt64(), secondTypes.getAnInt64());
+
+    assertEquals(firstTypes.getADouble(), secondTypes.getADouble());
+    assertArrayEquals(firstTypes.getAByteArray(), secondTypes.getAByteArray());
+    assertArrayEquals(firstTypes.getA4ByteArray(), secondTypes.getA4ByteArray());
+    assertArrayEquals(firstTypes.getA8ByteArray(), secondTypes.getA8ByteArray());
+    assertTrue(floatArraysEqual(firstTypes.getAFloatArray(), secondTypes.getAFloatArray()));
+    assertArrayEquals(firstTypes.getAList().toArray(), secondTypes.getAList().toArray());
+    assertArrayEquals(
+        firstTypes.getAMap().keySet().toArray(), secondTypes.getAMap().keySet().toArray());
+    assertArrayEquals(
+        firstTypes.getAMap().values().toArray(), secondTypes.getAMap().values().toArray());
+    assertEquals(firstTypes.getAnEnum(), secondTypes.getAnEnum());
+    assertEquals(firstTypes.getAString(), secondTypes.getAString());
+  }
+
+  void compareAllNullableTypes(AllNullableTypes firstTypes, AllNullableTypes secondTypes) {
+    assertEquals(firstTypes == null, secondTypes == null);
+    if (firstTypes == null || secondTypes == null) {
+      return;
+    }
+    assertEquals(firstTypes.getANullableBool(), secondTypes.getANullableBool());
+    assertEquals(firstTypes.getANullableInt(), secondTypes.getANullableInt());
+    assertEquals(firstTypes.getANullableDouble(), secondTypes.getANullableDouble());
+    assertEquals(firstTypes.getANullableString(), secondTypes.getANullableString());
+    assertArrayEquals(firstTypes.getANullableByteArray(), secondTypes.getANullableByteArray());
+    assertArrayEquals(firstTypes.getANullable4ByteArray(), secondTypes.getANullable4ByteArray());
+    assertArrayEquals(firstTypes.getANullable8ByteArray(), secondTypes.getANullable8ByteArray());
+    assertTrue(
+        floatArraysEqual(
+            firstTypes.getANullableFloatArray(), secondTypes.getANullableFloatArray()));
+    assertArrayEquals(
+        firstTypes.getANullableList().toArray(), secondTypes.getANullableList().toArray());
+    assertArrayEquals(
+        firstTypes.getANullableMap().keySet().toArray(),
+        secondTypes.getANullableMap().keySet().toArray());
+    assertArrayEquals(
+        firstTypes.getANullableMap().values().toArray(),
+        secondTypes.getANullableMap().values().toArray());
+    assertArrayEquals(
+        firstTypes.getNullableMapWithObject().values().toArray(),
+        secondTypes.getNullableMapWithObject().values().toArray());
+  }
+
   @Test
   public void nullValues() {
     AllNullableTypes everything = new AllNullableTypes();
@@ -84,18 +137,37 @@
 
   @Test
   public void hasValues() {
-    AllNullableTypes everything = new AllNullableTypes();
-    everything.setANullableBool(false);
-    everything.setANullableInt(1234L);
-    everything.setANullableDouble(2.0);
-    everything.setANullableString("hello");
-    everything.setANullableByteArray(new byte[] {1, 2, 3, 4});
-    everything.setANullable4ByteArray(new int[] {1, 2, 3, 4});
-    everything.setANullable8ByteArray(new long[] {1, 2, 3, 4});
-    everything.setANullableFloatArray(new double[] {0.5, 0.25, 1.5, 1.25});
-    everything.setANullableList(Arrays.asList(new int[] {1, 2, 3}));
-    everything.setANullableMap(makeMap("hello", 1234));
-    everything.setNullableMapWithObject(makeStringMap("hello", 1234));
+    AllTypes allEverything =
+        new AllTypes.Builder()
+            .setABool(false)
+            .setAnInt(1234L)
+            .setAnInt64(4321L)
+            .setADouble(2.0)
+            .setAString("hello")
+            .setAByteArray(new byte[] {1, 2, 3, 4})
+            .setA4ByteArray(new int[] {1, 2, 3, 4})
+            .setA8ByteArray(new long[] {1, 2, 3, 4})
+            .setAFloatArray(new double[] {0.5, 0.25, 1.5, 1.25})
+            .setAList(Arrays.asList(new int[] {1, 2, 3}))
+            .setAMap(makeMap("hello", 1234))
+            .setAnEnum(CoreTests.AnEnum.ONE)
+            .build();
+
+    AllNullableTypes everything =
+        new AllNullableTypes.Builder()
+            .setANullableBool(false)
+            .setANullableInt(1234L)
+            .setANullableDouble(2.0)
+            .setANullableString("hello")
+            .setANullableByteArray(new byte[] {1, 2, 3, 4})
+            .setANullable4ByteArray(new int[] {1, 2, 3, 4})
+            .setANullable8ByteArray(new long[] {1, 2, 3, 4})
+            .setANullableFloatArray(new double[] {0.5, 0.25, 1.5, 1.25})
+            .setANullableList(Arrays.asList(new int[] {1, 2, 3}))
+            .setANullableMap(makeMap("hello", 1234))
+            .setNullableMapWithObject(makeStringMap("hello", 1234))
+            .build();
+
     BinaryMessenger binaryMessenger = mock(BinaryMessenger.class);
     doAnswer(
             invocation -> {
@@ -119,27 +191,7 @@
         everything,
         (result) -> {
           didCall[0] = true;
-          assertEquals(everything.getANullableBool(), result.getANullableBool());
-          assertEquals(everything.getANullableInt(), result.getANullableInt());
-          assertEquals(everything.getANullableDouble(), result.getANullableDouble());
-          assertEquals(everything.getANullableString(), result.getANullableString());
-          assertArrayEquals(everything.getANullableByteArray(), result.getANullableByteArray());
-          assertArrayEquals(everything.getANullable4ByteArray(), result.getANullable4ByteArray());
-          assertArrayEquals(everything.getANullable8ByteArray(), result.getANullable8ByteArray());
-          assertTrue(
-              floatArraysEqual(
-                  everything.getANullableFloatArray(), result.getANullableFloatArray()));
-          assertArrayEquals(
-              everything.getANullableList().toArray(), result.getANullableList().toArray());
-          assertArrayEquals(
-              everything.getANullableMap().keySet().toArray(),
-              result.getANullableMap().keySet().toArray());
-          assertArrayEquals(
-              everything.getANullableMap().values().toArray(),
-              result.getANullableMap().values().toArray());
-          assertArrayEquals(
-              everything.getNullableMapWithObject().values().toArray(),
-              result.getNullableMapWithObject().values().toArray());
+          compareAllNullableTypes(everything, result);
         });
     assertTrue(didCall[0]);
   }
diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/ios/Classes/AlternateLanguageTestPlugin.m b/packages/pigeon/platform_tests/alternate_language_test_plugin/ios/Classes/AlternateLanguageTestPlugin.m
index 843232a..1df7a34 100644
--- a/packages/pigeon/platform_tests/alternate_language_test_plugin/ios/Classes/AlternateLanguageTestPlugin.m
+++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/ios/Classes/AlternateLanguageTestPlugin.m
@@ -87,17 +87,22 @@
   return aMap;
 }
 
-- (nullable NSString *)extractNestedNullableStringFrom:(AllNullableTypesWrapper *)wrapper
-                                                 error:(FlutterError *_Nullable *_Nonnull)error {
-  return wrapper.values.aNullableString;
+- (nullable AllClassesWrapper *)echoClassWrapper:(AllClassesWrapper *)wrapper
+                                           error:(FlutterError *_Nullable *_Nonnull)error {
+  return wrapper;
 }
 
-- (nullable AllNullableTypesWrapper *)
+- (nullable NSString *)extractNestedNullableStringFrom:(AllClassesWrapper *)wrapper
+                                                 error:(FlutterError *_Nullable *_Nonnull)error {
+  return wrapper.allNullableTypes.aNullableString;
+}
+
+- (nullable AllClassesWrapper *)
     createNestedObjectWithNullableString:(nullable NSString *)nullableString
                                    error:(FlutterError *_Nullable *_Nonnull)error {
   AllNullableTypes *innerObject = [[AllNullableTypes alloc] init];
   innerObject.aNullableString = nullableString;
-  return [AllNullableTypesWrapper makeWithValues:innerObject];
+  return [AllClassesWrapper makeWithAllNullableTypes:innerObject allTypes:nil];
 }
 
 - (nullable AllNullableTypes *)sendMultipleNullableTypesABool:(nullable NSNumber *)aNullableBool
diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/ios/Classes/CoreTests.gen.h b/packages/pigeon/platform_tests/alternate_language_test_plugin/ios/Classes/CoreTests.gen.h
index cbe0a6a..185e866 100644
--- a/packages/pigeon/platform_tests/alternate_language_test_plugin/ios/Classes/CoreTests.gen.h
+++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/ios/Classes/CoreTests.gen.h
@@ -22,9 +22,10 @@
 
 @class AllTypes;
 @class AllNullableTypes;
-@class AllNullableTypesWrapper;
+@class AllClassesWrapper;
 @class TestMessage;
 
+/// A class containing all supported types.
 @interface AllTypes : NSObject
 /// `init` unavailable to enforce nonnull fields, see the `make` class method.
 - (instancetype)init NS_UNAVAILABLE;
@@ -54,6 +55,7 @@
 @property(nonatomic, copy) NSString *aString;
 @end
 
+/// A class containing all supported nullable types.
 @interface AllNullableTypes : NSObject
 + (instancetype)makeWithANullableBool:(nullable NSNumber *)aNullableBool
                          aNullableInt:(nullable NSNumber *)aNullableInt
@@ -89,11 +91,18 @@
 @property(nonatomic, copy, nullable) NSString *aNullableString;
 @end
 
-@interface AllNullableTypesWrapper : NSObject
+/// A class for testing nested class handling.
+///
+/// This is needed to test nested nullable and non-nullable classes,
+/// `AllNullableTypes` is non-nullable here as it is easier to instantiate
+/// than `AllTypes` when testing doesn't require both (ie. testing null classes).
+@interface AllClassesWrapper : NSObject
 /// `init` unavailable to enforce nonnull fields, see the `make` class method.
 - (instancetype)init NS_UNAVAILABLE;
-+ (instancetype)makeWithValues:(AllNullableTypes *)values;
-@property(nonatomic, strong) AllNullableTypes *values;
++ (instancetype)makeWithAllNullableTypes:(AllNullableTypes *)allNullableTypes
+                                allTypes:(nullable AllTypes *)allTypes;
+@property(nonatomic, strong) AllNullableTypes *allNullableTypes;
+@property(nonatomic, strong, nullable) AllTypes *allTypes;
 @end
 
 /// A data class containing a List, used in unit tests.
@@ -159,18 +168,23 @@
 /// @return `nil` only when `error != nil`.
 - (nullable NSDictionary<NSString *, id> *)echoMap:(NSDictionary<NSString *, id> *)aMap
                                              error:(FlutterError *_Nullable *_Nonnull)error;
+/// Returns the passed map to test nested class serialization and deserialization.
+///
+/// @return `nil` only when `error != nil`.
+- (nullable AllClassesWrapper *)echoClassWrapper:(AllClassesWrapper *)wrapper
+                                           error:(FlutterError *_Nullable *_Nonnull)error;
 /// Returns the passed object, to test serialization and deserialization.
 - (nullable AllNullableTypes *)echoAllNullableTypes:(nullable AllNullableTypes *)everything
                                               error:(FlutterError *_Nullable *_Nonnull)error;
 /// Returns the inner `aString` value from the wrapped object, to test
 /// sending of nested objects.
-- (nullable NSString *)extractNestedNullableStringFrom:(AllNullableTypesWrapper *)wrapper
+- (nullable NSString *)extractNestedNullableStringFrom:(AllClassesWrapper *)wrapper
                                                  error:(FlutterError *_Nullable *_Nonnull)error;
 /// Returns the inner `aString` value from the wrapped object, to test
 /// sending of nested objects.
 ///
 /// @return `nil` only when `error != nil`.
-- (nullable AllNullableTypesWrapper *)
+- (nullable AllClassesWrapper *)
     createNestedObjectWithNullableString:(nullable NSString *)nullableString
                                    error:(FlutterError *_Nullable *_Nonnull)error;
 /// Returns passed in arguments of multiple types.
diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/ios/Classes/CoreTests.gen.m b/packages/pigeon/platform_tests/alternate_language_test_plugin/ios/Classes/CoreTests.gen.m
index 34c07a7..510b9a8 100644
--- a/packages/pigeon/platform_tests/alternate_language_test_plugin/ios/Classes/CoreTests.gen.m
+++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/ios/Classes/CoreTests.gen.m
@@ -42,9 +42,9 @@
 - (NSArray *)toList;
 @end
 
-@interface AllNullableTypesWrapper ()
-+ (AllNullableTypesWrapper *)fromList:(NSArray *)list;
-+ (nullable AllNullableTypesWrapper *)nullableFromList:(NSArray *)list;
+@interface AllClassesWrapper ()
++ (AllClassesWrapper *)fromList:(NSArray *)list;
++ (nullable AllClassesWrapper *)nullableFromList:(NSArray *)list;
 - (NSArray *)toList;
 @end
 
@@ -208,24 +208,29 @@
 }
 @end
 
-@implementation AllNullableTypesWrapper
-+ (instancetype)makeWithValues:(AllNullableTypes *)values {
-  AllNullableTypesWrapper *pigeonResult = [[AllNullableTypesWrapper alloc] init];
-  pigeonResult.values = values;
+@implementation AllClassesWrapper
++ (instancetype)makeWithAllNullableTypes:(AllNullableTypes *)allNullableTypes
+                                allTypes:(nullable AllTypes *)allTypes {
+  AllClassesWrapper *pigeonResult = [[AllClassesWrapper alloc] init];
+  pigeonResult.allNullableTypes = allNullableTypes;
+  pigeonResult.allTypes = allTypes;
   return pigeonResult;
 }
-+ (AllNullableTypesWrapper *)fromList:(NSArray *)list {
-  AllNullableTypesWrapper *pigeonResult = [[AllNullableTypesWrapper alloc] init];
-  pigeonResult.values = [AllNullableTypes nullableFromList:(GetNullableObjectAtIndex(list, 0))];
-  NSAssert(pigeonResult.values != nil, @"");
++ (AllClassesWrapper *)fromList:(NSArray *)list {
+  AllClassesWrapper *pigeonResult = [[AllClassesWrapper alloc] init];
+  pigeonResult.allNullableTypes =
+      [AllNullableTypes nullableFromList:(GetNullableObjectAtIndex(list, 0))];
+  NSAssert(pigeonResult.allNullableTypes != nil, @"");
+  pigeonResult.allTypes = [AllTypes nullableFromList:(GetNullableObjectAtIndex(list, 1))];
   return pigeonResult;
 }
-+ (nullable AllNullableTypesWrapper *)nullableFromList:(NSArray *)list {
-  return (list) ? [AllNullableTypesWrapper fromList:list] : nil;
++ (nullable AllClassesWrapper *)nullableFromList:(NSArray *)list {
+  return (list) ? [AllClassesWrapper fromList:list] : nil;
 }
 - (NSArray *)toList {
   return @[
-    (self.values ? [self.values toList] : [NSNull null]),
+    (self.allNullableTypes ? [self.allNullableTypes toList] : [NSNull null]),
+    (self.allTypes ? [self.allTypes toList] : [NSNull null]),
   ];
 }
 @end
@@ -257,9 +262,9 @@
 - (nullable id)readValueOfType:(UInt8)type {
   switch (type) {
     case 128:
-      return [AllNullableTypes fromList:[self readValue]];
+      return [AllClassesWrapper fromList:[self readValue]];
     case 129:
-      return [AllNullableTypesWrapper fromList:[self readValue]];
+      return [AllNullableTypes fromList:[self readValue]];
     case 130:
       return [AllTypes fromList:[self readValue]];
     case 131:
@@ -274,10 +279,10 @@
 @end
 @implementation HostIntegrationCoreApiCodecWriter
 - (void)writeValue:(id)value {
-  if ([value isKindOfClass:[AllNullableTypes class]]) {
+  if ([value isKindOfClass:[AllClassesWrapper class]]) {
     [self writeByte:128];
     [self writeValue:[value toList]];
-  } else if ([value isKindOfClass:[AllNullableTypesWrapper class]]) {
+  } else if ([value isKindOfClass:[AllNullableTypes class]]) {
     [self writeByte:129];
     [self writeValue:[value toList]];
   } else if ([value isKindOfClass:[AllTypes class]]) {
@@ -587,6 +592,28 @@
       [channel setMessageHandler:nil];
     }
   }
+  /// Returns the passed map to test nested class serialization and deserialization.
+  {
+    FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc]
+           initWithName:@"dev.flutter.pigeon.HostIntegrationCoreApi.echoClassWrapper"
+        binaryMessenger:binaryMessenger
+                  codec:HostIntegrationCoreApiGetCodec()];
+    if (api) {
+      NSCAssert(
+          [api respondsToSelector:@selector(echoClassWrapper:error:)],
+          @"HostIntegrationCoreApi api (%@) doesn't respond to @selector(echoClassWrapper:error:)",
+          api);
+      [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) {
+        NSArray *args = message;
+        AllClassesWrapper *arg_wrapper = GetNullableObjectAtIndex(args, 0);
+        FlutterError *error;
+        AllClassesWrapper *output = [api echoClassWrapper:arg_wrapper error:&error];
+        callback(wrapResult(output, error));
+      }];
+    } else {
+      [channel setMessageHandler:nil];
+    }
+  }
   /// Returns the passed object, to test serialization and deserialization.
   {
     FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc]
@@ -623,7 +650,7 @@
                 api);
       [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) {
         NSArray *args = message;
-        AllNullableTypesWrapper *arg_wrapper = GetNullableObjectAtIndex(args, 0);
+        AllClassesWrapper *arg_wrapper = GetNullableObjectAtIndex(args, 0);
         FlutterError *error;
         NSString *output = [api extractNestedNullableStringFrom:arg_wrapper error:&error];
         callback(wrapResult(output, error));
@@ -648,8 +675,8 @@
         NSArray *args = message;
         NSString *arg_nullableString = GetNullableObjectAtIndex(args, 0);
         FlutterError *error;
-        AllNullableTypesWrapper *output =
-            [api createNestedObjectWithNullableString:arg_nullableString error:&error];
+        AllClassesWrapper *output = [api createNestedObjectWithNullableString:arg_nullableString
+                                                                        error:&error];
         callback(wrapResult(output, error));
       }];
     } else {
@@ -1797,9 +1824,9 @@
 - (nullable id)readValueOfType:(UInt8)type {
   switch (type) {
     case 128:
-      return [AllNullableTypes fromList:[self readValue]];
+      return [AllClassesWrapper fromList:[self readValue]];
     case 129:
-      return [AllNullableTypesWrapper fromList:[self readValue]];
+      return [AllNullableTypes fromList:[self readValue]];
     case 130:
       return [AllTypes fromList:[self readValue]];
     case 131:
@@ -1814,10 +1841,10 @@
 @end
 @implementation FlutterIntegrationCoreApiCodecWriter
 - (void)writeValue:(id)value {
-  if ([value isKindOfClass:[AllNullableTypes class]]) {
+  if ([value isKindOfClass:[AllClassesWrapper class]]) {
     [self writeByte:128];
     [self writeValue:[value toList]];
-  } else if ([value isKindOfClass:[AllNullableTypesWrapper class]]) {
+  } else if ([value isKindOfClass:[AllNullableTypes class]]) {
     [self writeByte:129];
     [self writeValue:[value toList]];
   } else if ([value isKindOfClass:[AllTypes class]]) {
diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/lib/main.dart b/packages/pigeon/platform_tests/alternate_language_test_plugin/lib/main.dart
new file mode 100644
index 0000000..84de3a4
--- /dev/null
+++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/lib/main.dart
@@ -0,0 +1,7 @@
+// 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.
+
+// There is intentionally no code here; tests use generated Pigeon APIs
+// directly, as wrapping them in a plugin would just add maintenance burden
+// when changing tests.
diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/macos/Classes/AlternateLanguageTestPlugin.m b/packages/pigeon/platform_tests/alternate_language_test_plugin/macos/Classes/AlternateLanguageTestPlugin.m
index 276b9a0..488ff0d 100644
--- a/packages/pigeon/platform_tests/alternate_language_test_plugin/macos/Classes/AlternateLanguageTestPlugin.m
+++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/macos/Classes/AlternateLanguageTestPlugin.m
@@ -87,17 +87,22 @@
   return aMap;
 }
 
-- (nullable NSString *)extractNestedNullableStringFrom:(AllNullableTypesWrapper *)wrapper
-                                                 error:(FlutterError *_Nullable *_Nonnull)error {
-  return wrapper.values.aNullableString;
+- (nullable AllClassesWrapper *)echoClassWrapper:(AllClassesWrapper *)wrapper
+                                           error:(FlutterError *_Nullable *_Nonnull)error {
+  return wrapper;
 }
 
-- (nullable AllNullableTypesWrapper *)
+- (nullable NSString *)extractNestedNullableStringFrom:(AllClassesWrapper *)wrapper
+                                                 error:(FlutterError *_Nullable *_Nonnull)error {
+  return wrapper.allNullableTypes.aNullableString;
+}
+
+- (nullable AllClassesWrapper *)
     createNestedObjectWithNullableString:(nullable NSString *)nullableString
                                    error:(FlutterError *_Nullable *_Nonnull)error {
   AllNullableTypes *innerObject = [[AllNullableTypes alloc] init];
   innerObject.aNullableString = nullableString;
-  return [AllNullableTypesWrapper makeWithValues:innerObject];
+  return [AllClassesWrapper makeWithAllNullableTypes:innerObject allTypes:nil];
 }
 
 - (nullable AllNullableTypes *)sendMultipleNullableTypesABool:(nullable NSNumber *)aNullableBool
diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/macos/Classes/CoreTests.gen.h b/packages/pigeon/platform_tests/alternate_language_test_plugin/macos/Classes/CoreTests.gen.h
index cbe0a6a..185e866 100644
--- a/packages/pigeon/platform_tests/alternate_language_test_plugin/macos/Classes/CoreTests.gen.h
+++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/macos/Classes/CoreTests.gen.h
@@ -22,9 +22,10 @@
 
 @class AllTypes;
 @class AllNullableTypes;
-@class AllNullableTypesWrapper;
+@class AllClassesWrapper;
 @class TestMessage;
 
+/// A class containing all supported types.
 @interface AllTypes : NSObject
 /// `init` unavailable to enforce nonnull fields, see the `make` class method.
 - (instancetype)init NS_UNAVAILABLE;
@@ -54,6 +55,7 @@
 @property(nonatomic, copy) NSString *aString;
 @end
 
+/// A class containing all supported nullable types.
 @interface AllNullableTypes : NSObject
 + (instancetype)makeWithANullableBool:(nullable NSNumber *)aNullableBool
                          aNullableInt:(nullable NSNumber *)aNullableInt
@@ -89,11 +91,18 @@
 @property(nonatomic, copy, nullable) NSString *aNullableString;
 @end
 
-@interface AllNullableTypesWrapper : NSObject
+/// A class for testing nested class handling.
+///
+/// This is needed to test nested nullable and non-nullable classes,
+/// `AllNullableTypes` is non-nullable here as it is easier to instantiate
+/// than `AllTypes` when testing doesn't require both (ie. testing null classes).
+@interface AllClassesWrapper : NSObject
 /// `init` unavailable to enforce nonnull fields, see the `make` class method.
 - (instancetype)init NS_UNAVAILABLE;
-+ (instancetype)makeWithValues:(AllNullableTypes *)values;
-@property(nonatomic, strong) AllNullableTypes *values;
++ (instancetype)makeWithAllNullableTypes:(AllNullableTypes *)allNullableTypes
+                                allTypes:(nullable AllTypes *)allTypes;
+@property(nonatomic, strong) AllNullableTypes *allNullableTypes;
+@property(nonatomic, strong, nullable) AllTypes *allTypes;
 @end
 
 /// A data class containing a List, used in unit tests.
@@ -159,18 +168,23 @@
 /// @return `nil` only when `error != nil`.
 - (nullable NSDictionary<NSString *, id> *)echoMap:(NSDictionary<NSString *, id> *)aMap
                                              error:(FlutterError *_Nullable *_Nonnull)error;
+/// Returns the passed map to test nested class serialization and deserialization.
+///
+/// @return `nil` only when `error != nil`.
+- (nullable AllClassesWrapper *)echoClassWrapper:(AllClassesWrapper *)wrapper
+                                           error:(FlutterError *_Nullable *_Nonnull)error;
 /// Returns the passed object, to test serialization and deserialization.
 - (nullable AllNullableTypes *)echoAllNullableTypes:(nullable AllNullableTypes *)everything
                                               error:(FlutterError *_Nullable *_Nonnull)error;
 /// Returns the inner `aString` value from the wrapped object, to test
 /// sending of nested objects.
-- (nullable NSString *)extractNestedNullableStringFrom:(AllNullableTypesWrapper *)wrapper
+- (nullable NSString *)extractNestedNullableStringFrom:(AllClassesWrapper *)wrapper
                                                  error:(FlutterError *_Nullable *_Nonnull)error;
 /// Returns the inner `aString` value from the wrapped object, to test
 /// sending of nested objects.
 ///
 /// @return `nil` only when `error != nil`.
-- (nullable AllNullableTypesWrapper *)
+- (nullable AllClassesWrapper *)
     createNestedObjectWithNullableString:(nullable NSString *)nullableString
                                    error:(FlutterError *_Nullable *_Nonnull)error;
 /// Returns passed in arguments of multiple types.
diff --git a/packages/pigeon/platform_tests/alternate_language_test_plugin/macos/Classes/CoreTests.gen.m b/packages/pigeon/platform_tests/alternate_language_test_plugin/macos/Classes/CoreTests.gen.m
index 34c07a7..510b9a8 100644
--- a/packages/pigeon/platform_tests/alternate_language_test_plugin/macos/Classes/CoreTests.gen.m
+++ b/packages/pigeon/platform_tests/alternate_language_test_plugin/macos/Classes/CoreTests.gen.m
@@ -42,9 +42,9 @@
 - (NSArray *)toList;
 @end
 
-@interface AllNullableTypesWrapper ()
-+ (AllNullableTypesWrapper *)fromList:(NSArray *)list;
-+ (nullable AllNullableTypesWrapper *)nullableFromList:(NSArray *)list;
+@interface AllClassesWrapper ()
++ (AllClassesWrapper *)fromList:(NSArray *)list;
++ (nullable AllClassesWrapper *)nullableFromList:(NSArray *)list;
 - (NSArray *)toList;
 @end
 
@@ -208,24 +208,29 @@
 }
 @end
 
-@implementation AllNullableTypesWrapper
-+ (instancetype)makeWithValues:(AllNullableTypes *)values {
-  AllNullableTypesWrapper *pigeonResult = [[AllNullableTypesWrapper alloc] init];
-  pigeonResult.values = values;
+@implementation AllClassesWrapper
++ (instancetype)makeWithAllNullableTypes:(AllNullableTypes *)allNullableTypes
+                                allTypes:(nullable AllTypes *)allTypes {
+  AllClassesWrapper *pigeonResult = [[AllClassesWrapper alloc] init];
+  pigeonResult.allNullableTypes = allNullableTypes;
+  pigeonResult.allTypes = allTypes;
   return pigeonResult;
 }
-+ (AllNullableTypesWrapper *)fromList:(NSArray *)list {
-  AllNullableTypesWrapper *pigeonResult = [[AllNullableTypesWrapper alloc] init];
-  pigeonResult.values = [AllNullableTypes nullableFromList:(GetNullableObjectAtIndex(list, 0))];
-  NSAssert(pigeonResult.values != nil, @"");
++ (AllClassesWrapper *)fromList:(NSArray *)list {
+  AllClassesWrapper *pigeonResult = [[AllClassesWrapper alloc] init];
+  pigeonResult.allNullableTypes =
+      [AllNullableTypes nullableFromList:(GetNullableObjectAtIndex(list, 0))];
+  NSAssert(pigeonResult.allNullableTypes != nil, @"");
+  pigeonResult.allTypes = [AllTypes nullableFromList:(GetNullableObjectAtIndex(list, 1))];
   return pigeonResult;
 }
-+ (nullable AllNullableTypesWrapper *)nullableFromList:(NSArray *)list {
-  return (list) ? [AllNullableTypesWrapper fromList:list] : nil;
++ (nullable AllClassesWrapper *)nullableFromList:(NSArray *)list {
+  return (list) ? [AllClassesWrapper fromList:list] : nil;
 }
 - (NSArray *)toList {
   return @[
-    (self.values ? [self.values toList] : [NSNull null]),
+    (self.allNullableTypes ? [self.allNullableTypes toList] : [NSNull null]),
+    (self.allTypes ? [self.allTypes toList] : [NSNull null]),
   ];
 }
 @end
@@ -257,9 +262,9 @@
 - (nullable id)readValueOfType:(UInt8)type {
   switch (type) {
     case 128:
-      return [AllNullableTypes fromList:[self readValue]];
+      return [AllClassesWrapper fromList:[self readValue]];
     case 129:
-      return [AllNullableTypesWrapper fromList:[self readValue]];
+      return [AllNullableTypes fromList:[self readValue]];
     case 130:
       return [AllTypes fromList:[self readValue]];
     case 131:
@@ -274,10 +279,10 @@
 @end
 @implementation HostIntegrationCoreApiCodecWriter
 - (void)writeValue:(id)value {
-  if ([value isKindOfClass:[AllNullableTypes class]]) {
+  if ([value isKindOfClass:[AllClassesWrapper class]]) {
     [self writeByte:128];
     [self writeValue:[value toList]];
-  } else if ([value isKindOfClass:[AllNullableTypesWrapper class]]) {
+  } else if ([value isKindOfClass:[AllNullableTypes class]]) {
     [self writeByte:129];
     [self writeValue:[value toList]];
   } else if ([value isKindOfClass:[AllTypes class]]) {
@@ -587,6 +592,28 @@
       [channel setMessageHandler:nil];
     }
   }
+  /// Returns the passed map to test nested class serialization and deserialization.
+  {
+    FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc]
+           initWithName:@"dev.flutter.pigeon.HostIntegrationCoreApi.echoClassWrapper"
+        binaryMessenger:binaryMessenger
+                  codec:HostIntegrationCoreApiGetCodec()];
+    if (api) {
+      NSCAssert(
+          [api respondsToSelector:@selector(echoClassWrapper:error:)],
+          @"HostIntegrationCoreApi api (%@) doesn't respond to @selector(echoClassWrapper:error:)",
+          api);
+      [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) {
+        NSArray *args = message;
+        AllClassesWrapper *arg_wrapper = GetNullableObjectAtIndex(args, 0);
+        FlutterError *error;
+        AllClassesWrapper *output = [api echoClassWrapper:arg_wrapper error:&error];
+        callback(wrapResult(output, error));
+      }];
+    } else {
+      [channel setMessageHandler:nil];
+    }
+  }
   /// Returns the passed object, to test serialization and deserialization.
   {
     FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc]
@@ -623,7 +650,7 @@
                 api);
       [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) {
         NSArray *args = message;
-        AllNullableTypesWrapper *arg_wrapper = GetNullableObjectAtIndex(args, 0);
+        AllClassesWrapper *arg_wrapper = GetNullableObjectAtIndex(args, 0);
         FlutterError *error;
         NSString *output = [api extractNestedNullableStringFrom:arg_wrapper error:&error];
         callback(wrapResult(output, error));
@@ -648,8 +675,8 @@
         NSArray *args = message;
         NSString *arg_nullableString = GetNullableObjectAtIndex(args, 0);
         FlutterError *error;
-        AllNullableTypesWrapper *output =
-            [api createNestedObjectWithNullableString:arg_nullableString error:&error];
+        AllClassesWrapper *output = [api createNestedObjectWithNullableString:arg_nullableString
+                                                                        error:&error];
         callback(wrapResult(output, error));
       }];
     } else {
@@ -1797,9 +1824,9 @@
 - (nullable id)readValueOfType:(UInt8)type {
   switch (type) {
     case 128:
-      return [AllNullableTypes fromList:[self readValue]];
+      return [AllClassesWrapper fromList:[self readValue]];
     case 129:
-      return [AllNullableTypesWrapper fromList:[self readValue]];
+      return [AllNullableTypes fromList:[self readValue]];
     case 130:
       return [AllTypes fromList:[self readValue]];
     case 131:
@@ -1814,10 +1841,10 @@
 @end
 @implementation FlutterIntegrationCoreApiCodecWriter
 - (void)writeValue:(id)value {
-  if ([value isKindOfClass:[AllNullableTypes class]]) {
+  if ([value isKindOfClass:[AllClassesWrapper class]]) {
     [self writeByte:128];
     [self writeValue:[value toList]];
-  } else if ([value isKindOfClass:[AllNullableTypesWrapper class]]) {
+  } else if ([value isKindOfClass:[AllNullableTypes class]]) {
     [self writeByte:129];
     [self writeValue:[value toList]];
   } else if ([value isKindOfClass:[AllTypes class]]) {
diff --git a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/core_tests.gen.dart b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/core_tests.gen.dart
index 8cd3960..a66b1c5 100644
--- a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/core_tests.gen.dart
+++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/core_tests.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 (v9.2.5), do not edit directly.
+// Autogenerated from Pigeon (v10.1.1), do not edit directly.
 // See also: https://pub.dev/packages/pigeon
 // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import
 
@@ -188,23 +188,30 @@
   }
 }
 
-class AllNullableTypesWrapper {
-  AllNullableTypesWrapper({
-    required this.values,
+class AllClassesWrapper {
+  AllClassesWrapper({
+    required this.allNullableTypes,
+    this.allTypes,
   });
 
-  AllNullableTypes values;
+  AllNullableTypes allNullableTypes;
+
+  AllTypes? allTypes;
 
   Object encode() {
     return <Object?>[
-      values.encode(),
+      allNullableTypes.encode(),
+      allTypes?.encode(),
     ];
   }
 
-  static AllNullableTypesWrapper decode(Object result) {
+  static AllClassesWrapper decode(Object result) {
     result as List<Object?>;
-    return AllNullableTypesWrapper(
-      values: AllNullableTypes.decode(result[0]! as List<Object?>),
+    return AllClassesWrapper(
+      allNullableTypes: AllNullableTypes.decode(result[0]! as List<Object?>),
+      allTypes: result[1] != null
+          ? AllTypes.decode(result[1]! as List<Object?>)
+          : null,
     );
   }
 }
@@ -235,10 +242,10 @@
   const _HostIntegrationCoreApiCodec();
   @override
   void writeValue(WriteBuffer buffer, Object? value) {
-    if (value is AllNullableTypes) {
+    if (value is AllClassesWrapper) {
       buffer.putUint8(128);
       writeValue(buffer, value.encode());
-    } else if (value is AllNullableTypesWrapper) {
+    } else if (value is AllNullableTypes) {
       buffer.putUint8(129);
       writeValue(buffer, value.encode());
     } else if (value is AllTypes) {
@@ -256,9 +263,9 @@
   Object? readValueOfType(int type, ReadBuffer buffer) {
     switch (type) {
       case 128:
-        return AllNullableTypes.decode(readValue(buffer)!);
+        return AllClassesWrapper.decode(readValue(buffer)!);
       case 129:
-        return AllNullableTypesWrapper.decode(readValue(buffer)!);
+        return AllNullableTypes.decode(readValue(buffer)!);
       case 130:
         return AllTypes.decode(readValue(buffer)!);
       case 131:
@@ -622,6 +629,35 @@
     }
   }
 
+  /// Returns the passed map to test nested class serialization and deserialization.
+  Future<AllClassesWrapper> echoClassWrapper(
+      AllClassesWrapper arg_wrapper) async {
+    final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+        'dev.flutter.pigeon.HostIntegrationCoreApi.echoClassWrapper', codec,
+        binaryMessenger: _binaryMessenger);
+    final List<Object?>? replyList =
+        await channel.send(<Object?>[arg_wrapper]) as List<Object?>?;
+    if (replyList == null) {
+      throw PlatformException(
+        code: 'channel-error',
+        message: 'Unable to establish connection on channel.',
+      );
+    } else if (replyList.length > 1) {
+      throw PlatformException(
+        code: replyList[0]! as String,
+        message: replyList[1] as String?,
+        details: replyList[2],
+      );
+    } else if (replyList[0] == null) {
+      throw PlatformException(
+        code: 'null-error',
+        message: 'Host platform returned null value for non-null return value.',
+      );
+    } else {
+      return (replyList[0] as AllClassesWrapper?)!;
+    }
+  }
+
   /// Returns the passed object, to test serialization and deserialization.
   Future<AllNullableTypes?> echoAllNullableTypes(
       AllNullableTypes? arg_everything) async {
@@ -649,7 +685,7 @@
   /// Returns the inner `aString` value from the wrapped object, to test
   /// sending of nested objects.
   Future<String?> extractNestedNullableString(
-      AllNullableTypesWrapper arg_wrapper) async {
+      AllClassesWrapper arg_wrapper) async {
     final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
         'dev.flutter.pigeon.HostIntegrationCoreApi.extractNestedNullableString',
         codec,
@@ -674,7 +710,7 @@
 
   /// Returns the inner `aString` value from the wrapped object, to test
   /// sending of nested objects.
-  Future<AllNullableTypesWrapper> createNestedNullableString(
+  Future<AllClassesWrapper> createNestedNullableString(
       String? arg_nullableString) async {
     final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
         'dev.flutter.pigeon.HostIntegrationCoreApi.createNestedNullableString',
@@ -699,7 +735,7 @@
         message: 'Host platform returned null value for non-null return value.',
       );
     } else {
-      return (replyList[0] as AllNullableTypesWrapper?)!;
+      return (replyList[0] as AllClassesWrapper?)!;
     }
   }
 
@@ -1970,10 +2006,10 @@
   const _FlutterIntegrationCoreApiCodec();
   @override
   void writeValue(WriteBuffer buffer, Object? value) {
-    if (value is AllNullableTypes) {
+    if (value is AllClassesWrapper) {
       buffer.putUint8(128);
       writeValue(buffer, value.encode());
-    } else if (value is AllNullableTypesWrapper) {
+    } else if (value is AllNullableTypes) {
       buffer.putUint8(129);
       writeValue(buffer, value.encode());
     } else if (value is AllTypes) {
@@ -1991,9 +2027,9 @@
   Object? readValueOfType(int type, ReadBuffer buffer) {
     switch (type) {
       case 128:
-        return AllNullableTypes.decode(readValue(buffer)!);
+        return AllClassesWrapper.decode(readValue(buffer)!);
       case 129:
-        return AllNullableTypesWrapper.decode(readValue(buffer)!);
+        return AllNullableTypes.decode(readValue(buffer)!);
       case 130:
         return AllTypes.decode(readValue(buffer)!);
       case 131:
diff --git a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/flutter_unittests.gen.dart b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/flutter_unittests.gen.dart
index f8e8b21..fc3f96d 100644
--- a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/flutter_unittests.gen.dart
+++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/flutter_unittests.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 (v9.2.5), do not edit directly.
+// Autogenerated from Pigeon (v10.1.1), do not edit directly.
 // See also: https://pub.dev/packages/pigeon
 // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import
 
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 6cfd7a3..9790d32 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 (v9.2.5), do not edit directly.
+// Autogenerated from Pigeon (v10.1.1), do not edit directly.
 // See also: https://pub.dev/packages/pigeon
 // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import
 
diff --git a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/non_null_fields.gen.dart b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/non_null_fields.gen.dart
index 6fa2ee5..8c344ea 100644
--- a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/non_null_fields.gen.dart
+++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/non_null_fields.gen.dart
@@ -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 (v9.2.5), do not edit directly.
+// Autogenerated from Pigeon (v10.1.1), do not edit directly.
 // See also: https://pub.dev/packages/pigeon
 // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import
 
diff --git a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/null_fields.gen.dart b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/null_fields.gen.dart
index 379e137..77a0e1e 100644
--- a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/null_fields.gen.dart
+++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/null_fields.gen.dart
@@ -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 (v9.2.5), do not edit directly.
+// Autogenerated from Pigeon (v10.1.1), do not edit directly.
 // See also: https://pub.dev/packages/pigeon
 // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import
 
diff --git a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/nullable_returns.gen.dart b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/nullable_returns.gen.dart
index 50ae58f..f001b42 100644
--- a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/nullable_returns.gen.dart
+++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/nullable_returns.gen.dart
@@ -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 (v9.2.5), do not edit directly.
+// Autogenerated from Pigeon (v10.1.1), do not edit directly.
 // See also: https://pub.dev/packages/pigeon
 // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import
 
diff --git a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/primitive.gen.dart b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/primitive.gen.dart
index c5981a9..1590e7c 100644
--- a/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/primitive.gen.dart
+++ b/packages/pigeon/platform_tests/flutter_null_safe_unit_tests/lib/primitive.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 (v9.2.5), do not edit directly.
+// Autogenerated from Pigeon (v10.1.1), do not edit directly.
 // See also: https://pub.dev/packages/pigeon
 // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import
 
diff --git a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/integration_tests.dart b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/integration_tests.dart
index 730dedf..0a22cbe 100644
--- a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/integration_tests.dart
+++ b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/integration_tests.dart
@@ -39,6 +39,92 @@
 void runPigeonIntegrationTests(TargetGenerator targetGenerator) {
   IntegrationTestWidgetsFlutterBinding.ensureInitialized();
 
+  void compareAllTypes(AllTypes? allTypesOne, AllTypes? allTypesTwo) {
+    expect(allTypesOne == null, allTypesTwo == null);
+    if (allTypesOne == null || allTypesTwo == null) {
+      return;
+    }
+    expect(allTypesOne.aBool, allTypesTwo.aBool);
+    expect(allTypesOne.anInt, allTypesTwo.anInt);
+    expect(allTypesOne.anInt64, allTypesTwo.anInt64);
+    expect(allTypesOne.aDouble, allTypesTwo.aDouble);
+    expect(allTypesOne.aString, allTypesTwo.aString);
+    expect(allTypesOne.aByteArray, allTypesTwo.aByteArray);
+    expect(allTypesOne.a4ByteArray, allTypesTwo.a4ByteArray);
+    expect(allTypesOne.a8ByteArray, allTypesTwo.a8ByteArray);
+    expect(allTypesOne.aFloatArray, allTypesTwo.aFloatArray);
+    expect(listEquals(allTypesOne.aList, allTypesTwo.aList), true);
+    expect(mapEquals(allTypesOne.aMap, allTypesTwo.aMap), true);
+    expect(allTypesOne.anEnum, allTypesTwo.anEnum);
+  }
+
+  void compareAllNullableTypes(AllNullableTypes? allNullableTypesOne,
+      AllNullableTypes? allNullableTypesTwo) {
+    expect(allNullableTypesOne == null, allNullableTypesTwo == null);
+    if (allNullableTypesOne == null || allNullableTypesTwo == null) {
+      return;
+    }
+    expect(
+        allNullableTypesOne.aNullableBool, allNullableTypesTwo.aNullableBool);
+    expect(allNullableTypesOne.aNullableInt, allNullableTypesTwo.aNullableInt);
+    expect(
+        allNullableTypesOne.aNullableInt64, allNullableTypesTwo.aNullableInt64);
+    expect(allNullableTypesOne.aNullableDouble,
+        allNullableTypesTwo.aNullableDouble);
+    expect(allNullableTypesOne.aNullableString,
+        allNullableTypesTwo.aNullableString);
+    expect(allNullableTypesOne.aNullableByteArray,
+        allNullableTypesTwo.aNullableByteArray);
+    expect(allNullableTypesOne.aNullable4ByteArray,
+        allNullableTypesTwo.aNullable4ByteArray);
+    expect(allNullableTypesOne.aNullable8ByteArray,
+        allNullableTypesTwo.aNullable8ByteArray);
+    expect(allNullableTypesOne.aNullableFloatArray,
+        allNullableTypesTwo.aNullableFloatArray);
+    expect(
+        listEquals(allNullableTypesOne.aNullableList,
+            allNullableTypesTwo.aNullableList),
+        true);
+    expect(
+        mapEquals(
+            allNullableTypesOne.aNullableMap, allNullableTypesTwo.aNullableMap),
+        true);
+    expect(allNullableTypesOne.nullableNestedList?.length,
+        allNullableTypesTwo.nullableNestedList?.length);
+    // TODO(stuartmorgan): Enable this once the Dart types are fixed; see
+    // https://github.com/flutter/flutter/issues/116117
+    //for (int i = 0; i < allNullableTypesOne.nullableNestedList!.length; i++) {
+    //  expect(listEquals(allNullableTypesOne.nullableNestedList![i], allNullableTypesTwo.nullableNestedList![i]),
+    //      true);
+    //}
+    expect(
+        mapEquals(allNullableTypesOne.nullableMapWithAnnotations,
+            allNullableTypesTwo.nullableMapWithAnnotations),
+        true);
+    expect(
+        mapEquals(allNullableTypesOne.nullableMapWithObject,
+            allNullableTypesTwo.nullableMapWithObject),
+        true);
+    // TODO(stuartmorgan): Fix and re-enable.
+    // See https://github.com/flutter/flutter/issues/118733
+    if (targetGenerator != TargetGenerator.objc) {
+      expect(
+          allNullableTypesOne.aNullableEnum, allNullableTypesTwo.aNullableEnum);
+    }
+  }
+
+  void compareAllClassesWrapper(
+      AllClassesWrapper? wrapperOne, AllClassesWrapper? wrapperTwo) {
+    expect(wrapperOne == null, wrapperTwo == null);
+    if (wrapperOne == null || wrapperTwo == null) {
+      return;
+    }
+
+    compareAllNullableTypes(
+        wrapperOne.allNullableTypes, wrapperTwo.allNullableTypes);
+    compareAllTypes(wrapperOne.allTypes, wrapperTwo.allTypes);
+  }
+
   final AllTypes genericAllTypes = AllTypes(
     aBool: true,
     anInt: _regularInt,
@@ -49,8 +135,14 @@
     a4ByteArray: Int32List.fromList(<int>[4, 5, 6]),
     a8ByteArray: Int64List.fromList(<int>[7, 8, 9]),
     aFloatArray: Float64List.fromList(<double>[2.71828, _doublePi]),
-    aList: <Object?>['Thing 1', 2, true, 3.14],
-    aMap: <Object?, Object?>{'a': 1, 'b': 2.0, 'c': 'three', 'd': false},
+    aList: <Object?>['Thing 1', 2, true, 3.14, null],
+    aMap: <Object?, Object?>{
+      'a': 1,
+      'b': 2.0,
+      'c': 'three',
+      'd': false,
+      'e': null
+    },
     anEnum: AnEnum.two,
   );
 
@@ -64,12 +156,13 @@
     aNullable4ByteArray: Int32List.fromList(<int>[4, 5, 6]),
     aNullable8ByteArray: Int64List.fromList(<int>[7, 8, 9]),
     aNullableFloatArray: Float64List.fromList(<double>[2.71828, _doublePi]),
-    aNullableList: <Object?>['Thing 1', 2, true, 3.14],
+    aNullableList: <Object?>['Thing 1', 2, true, 3.14, null],
     aNullableMap: <Object?, Object?>{
       'a': 1,
       'b': 2.0,
       'c': 'three',
-      'd': false
+      'd': false,
+      'e': null
     },
     nullableNestedList: <List<bool>>[
       <bool>[true, false],
@@ -92,19 +185,7 @@
       final HostIntegrationCoreApi api = HostIntegrationCoreApi();
 
       final AllTypes echoObject = await api.echoAllTypes(genericAllTypes);
-
-      expect(echoObject.aBool, genericAllTypes.aBool);
-      expect(echoObject.anInt, genericAllTypes.anInt);
-      expect(echoObject.anInt64, genericAllTypes.anInt64);
-      expect(echoObject.aDouble, genericAllTypes.aDouble);
-      expect(echoObject.aString, genericAllTypes.aString);
-      expect(echoObject.aByteArray, genericAllTypes.aByteArray);
-      expect(echoObject.a4ByteArray, genericAllTypes.a4ByteArray);
-      expect(echoObject.a8ByteArray, genericAllTypes.a8ByteArray);
-      expect(echoObject.aFloatArray, genericAllTypes.aFloatArray);
-      expect(listEquals(echoObject.aList, genericAllTypes.aList), true);
-      expect(mapEquals(echoObject.aMap, genericAllTypes.aMap), true);
-      expect(echoObject.anEnum, genericAllTypes.anEnum);
+      compareAllTypes(echoObject, genericAllTypes);
     });
 
     testWidgets('all nullable datatypes serialize and deserialize correctly',
@@ -113,47 +194,8 @@
 
       final AllNullableTypes? echoObject =
           await api.echoAllNullableTypes(genericAllNullableTypes);
-      expect(echoObject?.aNullableBool, genericAllNullableTypes.aNullableBool);
-      expect(echoObject?.aNullableInt, genericAllNullableTypes.aNullableInt);
-      expect(
-          echoObject?.aNullableInt64, genericAllNullableTypes.aNullableInt64);
-      expect(
-          echoObject?.aNullableDouble, genericAllNullableTypes.aNullableDouble);
-      expect(
-          echoObject?.aNullableString, genericAllNullableTypes.aNullableString);
-      expect(echoObject?.aNullableByteArray,
-          genericAllNullableTypes.aNullableByteArray);
-      expect(echoObject?.aNullable4ByteArray,
-          genericAllNullableTypes.aNullable4ByteArray);
-      expect(echoObject?.aNullable8ByteArray,
-          genericAllNullableTypes.aNullable8ByteArray);
-      expect(echoObject?.aNullableFloatArray,
-          genericAllNullableTypes.aNullableFloatArray);
-      expect(
-          listEquals(
-              echoObject?.aNullableList, genericAllNullableTypes.aNullableList),
-          true);
-      expect(
-          mapEquals(
-              echoObject?.aNullableMap, genericAllNullableTypes.aNullableMap),
-          true);
-      expect(echoObject?.nullableNestedList?.length,
-          genericAllNullableTypes.nullableNestedList?.length);
-      // TODO(stuartmorgan): Enable this once the Dart types are fixed; see
-      // https://github.com/flutter/flutter/issues/116117
-      //for (int i = 0; i < echoObject?.nullableNestedList!.length; i++) {
-      //  expect(listEquals(echoObject?.nullableNestedList![i], genericAllNullableTypes.nullableNestedList![i]),
-      //      true);
-      //}
-      expect(
-          mapEquals(echoObject?.nullableMapWithAnnotations,
-              genericAllNullableTypes.nullableMapWithAnnotations),
-          true);
-      expect(
-          mapEquals(echoObject?.nullableMapWithObject,
-              genericAllNullableTypes.nullableMapWithObject),
-          true);
-      expect(echoObject?.aNullableEnum, genericAllNullableTypes.aNullableEnum);
+
+      compareAllNullableTypes(echoObject, genericAllNullableTypes);
     });
 
     testWidgets('all null datatypes serialize and deserialize correctly',
@@ -162,80 +204,36 @@
 
       final AllNullableTypes allTypesNull = AllNullableTypes();
 
-      final AllNullableTypes? echoNullFilledObject =
+      final AllNullableTypes? echoNullFilledClass =
           await api.echoAllNullableTypes(allTypesNull);
+      compareAllNullableTypes(allTypesNull, echoNullFilledClass);
+    });
 
-      expect(echoNullFilledObject?.aNullableBool, allTypesNull.aNullableBool);
-      expect(echoNullFilledObject?.aNullableBool, null);
+    testWidgets('Classes with list of null serialize and deserialize correctly',
+        (WidgetTester _) async {
+      final HostIntegrationCoreApi api = HostIntegrationCoreApi();
 
-      expect(echoNullFilledObject?.aNullableInt, allTypesNull.aNullableInt);
-      expect(echoNullFilledObject?.aNullableInt, null);
+      final AllNullableTypes nullableListTypes =
+          AllNullableTypes(aNullableList: <String?>['String', null]);
 
-      expect(echoNullFilledObject?.aNullableInt64, allTypesNull.aNullableInt64);
-      expect(echoNullFilledObject?.aNullableInt64, null);
+      final AllNullableTypes? echoNullFilledClass =
+          await api.echoAllNullableTypes(nullableListTypes);
 
-      expect(
-          echoNullFilledObject?.aNullableDouble, allTypesNull.aNullableDouble);
-      expect(echoNullFilledObject?.aNullableDouble, null);
+      compareAllNullableTypes(nullableListTypes, echoNullFilledClass);
+    });
 
-      expect(
-          echoNullFilledObject?.aNullableString, allTypesNull.aNullableString);
-      expect(echoNullFilledObject?.aNullableString, null);
+    testWidgets('Classes with map of null serialize and deserialize correctly',
+        (WidgetTester _) async {
+      final HostIntegrationCoreApi api = HostIntegrationCoreApi();
 
-      expect(echoNullFilledObject?.aNullableByteArray,
-          allTypesNull.aNullableByteArray);
-      expect(echoNullFilledObject?.aNullableByteArray, null);
+      final AllNullableTypes nullableListTypes = AllNullableTypes(
+          aNullableMap: <String?, String?>{'String': 'string', 'null': null});
 
-      expect(echoNullFilledObject?.aNullable4ByteArray,
-          allTypesNull.aNullable4ByteArray);
-      expect(echoNullFilledObject?.aNullable4ByteArray, null);
+      final AllNullableTypes? echoNullFilledClass =
+          await api.echoAllNullableTypes(nullableListTypes);
 
-      expect(echoNullFilledObject?.aNullable8ByteArray,
-          allTypesNull.aNullable8ByteArray);
-      expect(echoNullFilledObject?.aNullable8ByteArray, null);
-
-      expect(echoNullFilledObject?.aNullableFloatArray,
-          allTypesNull.aNullableFloatArray);
-      expect(echoNullFilledObject?.aNullableFloatArray, null);
-
-      expect(
-          listEquals(
-              echoNullFilledObject?.aNullableList, allTypesNull.aNullableList),
-          true);
-      expect(echoNullFilledObject?.aNullableList, null);
-
-      expect(
-          mapEquals(
-              echoNullFilledObject?.aNullableMap, allTypesNull.aNullableMap),
-          true);
-      expect(echoNullFilledObject?.aNullableMap, null);
-
-      // TODO(stuartmorgan): Enable this once the Dart types are fixed; see
-      // https://github.com/flutter/flutter/issues/116117
-      //for (int i = 0; i < echoNullFilledObject?.nullableNestedList!.length; i++) {
-      //  expect(listEquals(echoNullFilledObject?.nullableNestedList![i], allTypesNull.nullableNestedList![i]),
-      //      true);
-      //}
-      expect(echoNullFilledObject?.nullableNestedList, null);
-
-      expect(
-          mapEquals(echoNullFilledObject?.nullableMapWithAnnotations,
-              allTypesNull.nullableMapWithAnnotations),
-          true);
-      expect(echoNullFilledObject?.nullableMapWithAnnotations, null);
-
-      expect(
-          mapEquals(echoNullFilledObject?.nullableMapWithObject,
-              allTypesNull.nullableMapWithObject),
-          true);
-      expect(echoNullFilledObject?.nullableMapWithObject, null);
-
-      expect(echoNullFilledObject?.aNullableEnum, allTypesNull.aNullableEnum);
-      expect(echoNullFilledObject?.aNullableEnum, null);
-    },
-        // TODO(stuartmorgan): Fix and re-enable.
-        // See https://github.com/flutter/flutter/issues/118733
-        skip: targetGenerator == TargetGenerator.objc);
+      compareAllNullableTypes(nullableListTypes, echoNullFilledClass);
+    });
 
     testWidgets('errors are returned correctly', (WidgetTester _) async {
       final HostIntegrationCoreApi api = HostIntegrationCoreApi();
@@ -270,12 +268,12 @@
     testWidgets('nested objects can be sent correctly', (WidgetTester _) async {
       final HostIntegrationCoreApi api = HostIntegrationCoreApi();
 
-      final AllNullableTypesWrapper sentObject =
-          AllNullableTypesWrapper(values: genericAllNullableTypes);
+      final AllClassesWrapper sentObject = AllClassesWrapper(
+          allNullableTypes: genericAllNullableTypes, allTypes: genericAllTypes);
 
       final String? receivedString =
           await api.extractNestedNullableString(sentObject);
-      expect(receivedString, sentObject.values.aNullableString);
+      expect(receivedString, sentObject.allNullableTypes.aNullableString);
     });
 
     testWidgets('nested objects can be received correctly',
@@ -283,9 +281,33 @@
       final HostIntegrationCoreApi api = HostIntegrationCoreApi();
 
       const String sentString = 'Some string';
-      final AllNullableTypesWrapper receivedObject =
+      final AllClassesWrapper receivedObject =
           await api.createNestedNullableString(sentString);
-      expect(receivedObject.values.aNullableString, sentString);
+      expect(receivedObject.allNullableTypes.aNullableString, sentString);
+    });
+
+    testWidgets('nested classes can serialize and deserialize correctly',
+        (WidgetTester _) async {
+      final HostIntegrationCoreApi api = HostIntegrationCoreApi();
+
+      final AllClassesWrapper sentWrapper = AllClassesWrapper(
+          allNullableTypes: AllNullableTypes(), allTypes: genericAllTypes);
+
+      final AllClassesWrapper receivedClassWrapper =
+          await api.echoClassWrapper(sentWrapper);
+      compareAllClassesWrapper(sentWrapper, receivedClassWrapper);
+    });
+
+    testWidgets('nested null classes can serialize and deserialize correctly',
+        (WidgetTester _) async {
+      final HostIntegrationCoreApi api = HostIntegrationCoreApi();
+
+      final AllClassesWrapper sentWrapper =
+          AllClassesWrapper(allNullableTypes: AllNullableTypes());
+
+      final AllClassesWrapper receivedClassWrapper =
+          await api.echoClassWrapper(sentWrapper);
+      compareAllClassesWrapper(sentWrapper, receivedClassWrapper);
     });
 
     testWidgets(
@@ -308,11 +330,11 @@
         (WidgetTester _) async {
       final HostIntegrationCoreApi api = HostIntegrationCoreApi();
 
-      final AllNullableTypes echoNullFilledObject =
+      final AllNullableTypes echoNullFilledClass =
           await api.sendMultipleNullableTypes(null, null, null);
-      expect(echoNullFilledObject.aNullableInt, null);
-      expect(echoNullFilledObject.aNullableBool, null);
-      expect(echoNullFilledObject.aNullableString, null);
+      expect(echoNullFilledClass.aNullableInt, null);
+      expect(echoNullFilledClass.aNullableBool, null);
+      expect(echoNullFilledClass.aNullableString, null);
     });
 
     testWidgets('Int serialize and deserialize correctly',
@@ -549,7 +571,7 @@
         (WidgetTester _) async {
       final HostIntegrationCoreApi api = HostIntegrationCoreApi();
 
-      const List<Object?> sentObject = <Object>[7, 'Hello Dart!'];
+      const List<Object?> sentObject = <Object?>[7, 'Hello Dart!', null];
       final List<Object?>? echoObject = await api.echoNullableList(sentObject);
       expect(listEquals(echoObject, sentObject), true);
     });
@@ -562,6 +584,7 @@
         'a': 1,
         'b': 2.3,
         'c': 'four',
+        'd': null,
       };
       final Map<String?, Object?>? echoObject =
           await api.echoNullableMap(sentObject);
@@ -630,18 +653,7 @@
 
       final AllTypes echoObject = await api.echoAsyncAllTypes(genericAllTypes);
 
-      expect(echoObject.aBool, genericAllTypes.aBool);
-      expect(echoObject.anInt, genericAllTypes.anInt);
-      expect(echoObject.anInt64, genericAllTypes.anInt64);
-      expect(echoObject.aDouble, genericAllTypes.aDouble);
-      expect(echoObject.aString, genericAllTypes.aString);
-      expect(echoObject.aByteArray, genericAllTypes.aByteArray);
-      expect(echoObject.a4ByteArray, genericAllTypes.a4ByteArray);
-      expect(echoObject.a8ByteArray, genericAllTypes.a8ByteArray);
-      expect(echoObject.aFloatArray, genericAllTypes.aFloatArray);
-      expect(listEquals(echoObject.aList, genericAllTypes.aList), true);
-      expect(mapEquals(echoObject.aMap, genericAllTypes.aMap), true);
-      expect(echoObject.anEnum, genericAllTypes.anEnum);
+      compareAllTypes(echoObject, genericAllTypes);
     });
 
     testWidgets(
@@ -651,47 +663,8 @@
 
       final AllNullableTypes? echoObject =
           await api.echoAsyncNullableAllNullableTypes(genericAllNullableTypes);
-      expect(echoObject?.aNullableBool, genericAllNullableTypes.aNullableBool);
-      expect(echoObject?.aNullableInt, genericAllNullableTypes.aNullableInt);
-      expect(
-          echoObject?.aNullableInt64, genericAllNullableTypes.aNullableInt64);
-      expect(
-          echoObject?.aNullableDouble, genericAllNullableTypes.aNullableDouble);
-      expect(
-          echoObject?.aNullableString, genericAllNullableTypes.aNullableString);
-      expect(echoObject?.aNullableByteArray,
-          genericAllNullableTypes.aNullableByteArray);
-      expect(echoObject?.aNullable4ByteArray,
-          genericAllNullableTypes.aNullable4ByteArray);
-      expect(echoObject?.aNullable8ByteArray,
-          genericAllNullableTypes.aNullable8ByteArray);
-      expect(echoObject?.aNullableFloatArray,
-          genericAllNullableTypes.aNullableFloatArray);
-      expect(
-          listEquals(
-              echoObject?.aNullableList, genericAllNullableTypes.aNullableList),
-          true);
-      expect(
-          mapEquals(
-              echoObject?.aNullableMap, genericAllNullableTypes.aNullableMap),
-          true);
-      expect(echoObject?.nullableNestedList?.length,
-          genericAllNullableTypes.nullableNestedList?.length);
-      // TODO(stuartmorgan): Enable this once the Dart types are fixed; see
-      // https://github.com/flutter/flutter/issues/116117
-      //for (int i = 0; i < echoObject?.nullableNestedList!.length; i++) {
-      //  expect(listEquals(echoObject?.nullableNestedList![i], genericAllNullableTypes.nullableNestedList![i]),
-      //      true);
-      //}
-      expect(
-          mapEquals(echoObject?.nullableMapWithAnnotations,
-              genericAllNullableTypes.nullableMapWithAnnotations),
-          true);
-      expect(
-          mapEquals(echoObject?.nullableMapWithObject,
-              genericAllNullableTypes.nullableMapWithObject),
-          true);
-      expect(echoObject?.aNullableEnum, genericAllNullableTypes.aNullableEnum);
+
+      compareAllNullableTypes(echoObject, genericAllNullableTypes);
     });
 
     testWidgets('all null datatypes async serialize and deserialize correctly',
@@ -700,80 +673,10 @@
 
       final AllNullableTypes allTypesNull = AllNullableTypes();
 
-      final AllNullableTypes? echoNullFilledObject =
+      final AllNullableTypes? echoNullFilledClass =
           await api.echoAsyncNullableAllNullableTypes(allTypesNull);
-
-      expect(echoNullFilledObject?.aNullableBool, allTypesNull.aNullableBool);
-      expect(echoNullFilledObject?.aNullableBool, null);
-
-      expect(echoNullFilledObject?.aNullableInt, allTypesNull.aNullableInt);
-      expect(echoNullFilledObject?.aNullableInt, null);
-
-      expect(echoNullFilledObject?.aNullableInt64, allTypesNull.aNullableInt64);
-      expect(echoNullFilledObject?.aNullableInt64, null);
-
-      expect(
-          echoNullFilledObject?.aNullableDouble, allTypesNull.aNullableDouble);
-      expect(echoNullFilledObject?.aNullableDouble, null);
-
-      expect(
-          echoNullFilledObject?.aNullableString, allTypesNull.aNullableString);
-      expect(echoNullFilledObject?.aNullableString, null);
-
-      expect(echoNullFilledObject?.aNullableByteArray,
-          allTypesNull.aNullableByteArray);
-      expect(echoNullFilledObject?.aNullableByteArray, null);
-
-      expect(echoNullFilledObject?.aNullable4ByteArray,
-          allTypesNull.aNullable4ByteArray);
-      expect(echoNullFilledObject?.aNullable4ByteArray, null);
-
-      expect(echoNullFilledObject?.aNullable8ByteArray,
-          allTypesNull.aNullable8ByteArray);
-      expect(echoNullFilledObject?.aNullable8ByteArray, null);
-
-      expect(echoNullFilledObject?.aNullableFloatArray,
-          allTypesNull.aNullableFloatArray);
-      expect(echoNullFilledObject?.aNullableFloatArray, null);
-
-      expect(
-          listEquals(
-              echoNullFilledObject?.aNullableList, allTypesNull.aNullableList),
-          true);
-      expect(echoNullFilledObject?.aNullableList, null);
-
-      expect(
-          mapEquals(
-              echoNullFilledObject?.aNullableMap, allTypesNull.aNullableMap),
-          true);
-      expect(echoNullFilledObject?.aNullableMap, null);
-
-      // TODO(stuartmorgan): Enable this once the Dart types are fixed; see
-      // https://github.com/flutter/flutter/issues/116117
-      //for (int i = 0; i < echoNullFilledObject?.nullableNestedList!.length; i++) {
-      //  expect(listEquals(echoNullFilledObject?.nullableNestedList![i], allTypesNull.nullableNestedList![i]),
-      //      true);
-      //}
-      expect(echoNullFilledObject?.nullableNestedList, null);
-
-      expect(
-          mapEquals(echoNullFilledObject?.nullableMapWithAnnotations,
-              allTypesNull.nullableMapWithAnnotations),
-          true);
-      expect(echoNullFilledObject?.nullableMapWithAnnotations, null);
-
-      expect(
-          mapEquals(echoNullFilledObject?.nullableMapWithObject,
-              allTypesNull.nullableMapWithObject),
-          true);
-      expect(echoNullFilledObject?.nullableMapWithObject, null);
-
-      expect(echoNullFilledObject?.aNullableEnum, allTypesNull.aNullableEnum);
-      expect(echoNullFilledObject?.aNullableEnum, null);
-    },
-        // TODO(stuartmorgan): Fix and re-enable.
-        // See https://github.com/flutter/flutter/issues/118733
-        skip: targetGenerator == TargetGenerator.objc);
+      compareAllNullableTypes(echoNullFilledClass, allTypesNull);
+    });
 
     testWidgets('Int async serialize and deserialize correctly',
         (WidgetTester _) async {
@@ -1100,18 +1003,7 @@
       final AllTypes echoObject =
           await api.callFlutterEchoAllTypes(genericAllTypes);
 
-      expect(echoObject.aBool, genericAllTypes.aBool);
-      expect(echoObject.anInt, genericAllTypes.anInt);
-      expect(echoObject.anInt64, genericAllTypes.anInt64);
-      expect(echoObject.aDouble, genericAllTypes.aDouble);
-      expect(echoObject.aString, genericAllTypes.aString);
-      expect(echoObject.aByteArray, genericAllTypes.aByteArray);
-      expect(echoObject.a4ByteArray, genericAllTypes.a4ByteArray);
-      expect(echoObject.a8ByteArray, genericAllTypes.a8ByteArray);
-      expect(echoObject.aFloatArray, genericAllTypes.aFloatArray);
-      expect(listEquals(echoObject.aList, genericAllTypes.aList), true);
-      expect(mapEquals(echoObject.aMap, genericAllTypes.aMap), true);
-      expect(echoObject.anEnum, genericAllTypes.anEnum);
+      compareAllTypes(echoObject, genericAllTypes);
     });
 
     testWidgets(
diff --git a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/core_tests.gen.dart b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/core_tests.gen.dart
index d687814..3920cb3 100644
--- a/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/core_tests.gen.dart
+++ b/packages/pigeon/platform_tests/shared_test_plugin_code/lib/src/generated/core_tests.gen.dart
@@ -18,6 +18,7 @@
   three,
 }
 
+/// A class containing all supported types.
 class AllTypes {
   AllTypes({
     required this.aBool,
@@ -94,6 +95,7 @@
   }
 }
 
+/// A class containing all supported nullable types.
 class AllNullableTypes {
   AllNullableTypes({
     this.aNullableBool,
@@ -188,23 +190,35 @@
   }
 }
 
-class AllNullableTypesWrapper {
-  AllNullableTypesWrapper({
-    required this.values,
+/// A class for testing nested class handling.
+///
+/// This is needed to test nested nullable and non-nullable classes,
+/// `AllNullableTypes` is non-nullable here as it is easier to instantiate
+/// than `AllTypes` when testing doesn't require both (ie. testing null classes).
+class AllClassesWrapper {
+  AllClassesWrapper({
+    required this.allNullableTypes,
+    this.allTypes,
   });
 
-  AllNullableTypes values;
+  AllNullableTypes allNullableTypes;
+
+  AllTypes? allTypes;
 
   Object encode() {
     return <Object?>[
-      values.encode(),
+      allNullableTypes.encode(),
+      allTypes?.encode(),
     ];
   }
 
-  static AllNullableTypesWrapper decode(Object result) {
+  static AllClassesWrapper decode(Object result) {
     result as List<Object?>;
-    return AllNullableTypesWrapper(
-      values: AllNullableTypes.decode(result[0]! as List<Object?>),
+    return AllClassesWrapper(
+      allNullableTypes: AllNullableTypes.decode(result[0]! as List<Object?>),
+      allTypes: result[1] != null
+          ? AllTypes.decode(result[1]! as List<Object?>)
+          : null,
     );
   }
 }
@@ -235,10 +249,10 @@
   const _HostIntegrationCoreApiCodec();
   @override
   void writeValue(WriteBuffer buffer, Object? value) {
-    if (value is AllNullableTypes) {
+    if (value is AllClassesWrapper) {
       buffer.putUint8(128);
       writeValue(buffer, value.encode());
-    } else if (value is AllNullableTypesWrapper) {
+    } else if (value is AllNullableTypes) {
       buffer.putUint8(129);
       writeValue(buffer, value.encode());
     } else if (value is AllTypes) {
@@ -256,9 +270,9 @@
   Object? readValueOfType(int type, ReadBuffer buffer) {
     switch (type) {
       case 128:
-        return AllNullableTypes.decode(readValue(buffer)!);
+        return AllClassesWrapper.decode(readValue(buffer)!);
       case 129:
-        return AllNullableTypesWrapper.decode(readValue(buffer)!);
+        return AllNullableTypes.decode(readValue(buffer)!);
       case 130:
         return AllTypes.decode(readValue(buffer)!);
       case 131:
@@ -622,6 +636,35 @@
     }
   }
 
+  /// Returns the passed map to test nested class serialization and deserialization.
+  Future<AllClassesWrapper> echoClassWrapper(
+      AllClassesWrapper arg_wrapper) async {
+    final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+        'dev.flutter.pigeon.HostIntegrationCoreApi.echoClassWrapper', codec,
+        binaryMessenger: _binaryMessenger);
+    final List<Object?>? replyList =
+        await channel.send(<Object?>[arg_wrapper]) as List<Object?>?;
+    if (replyList == null) {
+      throw PlatformException(
+        code: 'channel-error',
+        message: 'Unable to establish connection on channel.',
+      );
+    } else if (replyList.length > 1) {
+      throw PlatformException(
+        code: replyList[0]! as String,
+        message: replyList[1] as String?,
+        details: replyList[2],
+      );
+    } else if (replyList[0] == null) {
+      throw PlatformException(
+        code: 'null-error',
+        message: 'Host platform returned null value for non-null return value.',
+      );
+    } else {
+      return (replyList[0] as AllClassesWrapper?)!;
+    }
+  }
+
   /// Returns the passed object, to test serialization and deserialization.
   Future<AllNullableTypes?> echoAllNullableTypes(
       AllNullableTypes? arg_everything) async {
@@ -649,7 +692,7 @@
   /// Returns the inner `aString` value from the wrapped object, to test
   /// sending of nested objects.
   Future<String?> extractNestedNullableString(
-      AllNullableTypesWrapper arg_wrapper) async {
+      AllClassesWrapper arg_wrapper) async {
     final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
         'dev.flutter.pigeon.HostIntegrationCoreApi.extractNestedNullableString',
         codec,
@@ -674,7 +717,7 @@
 
   /// Returns the inner `aString` value from the wrapped object, to test
   /// sending of nested objects.
-  Future<AllNullableTypesWrapper> createNestedNullableString(
+  Future<AllClassesWrapper> createNestedNullableString(
       String? arg_nullableString) async {
     final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
         'dev.flutter.pigeon.HostIntegrationCoreApi.createNestedNullableString',
@@ -699,7 +742,7 @@
         message: 'Host platform returned null value for non-null return value.',
       );
     } else {
-      return (replyList[0] as AllNullableTypesWrapper?)!;
+      return (replyList[0] as AllClassesWrapper?)!;
     }
   }
 
@@ -1970,10 +2013,10 @@
   const _FlutterIntegrationCoreApiCodec();
   @override
   void writeValue(WriteBuffer buffer, Object? value) {
-    if (value is AllNullableTypes) {
+    if (value is AllClassesWrapper) {
       buffer.putUint8(128);
       writeValue(buffer, value.encode());
-    } else if (value is AllNullableTypesWrapper) {
+    } else if (value is AllNullableTypes) {
       buffer.putUint8(129);
       writeValue(buffer, value.encode());
     } else if (value is AllTypes) {
@@ -1991,9 +2034,9 @@
   Object? readValueOfType(int type, ReadBuffer buffer) {
     switch (type) {
       case 128:
-        return AllNullableTypes.decode(readValue(buffer)!);
+        return AllClassesWrapper.decode(readValue(buffer)!);
       case 129:
-        return AllNullableTypesWrapper.decode(readValue(buffer)!);
+        return AllNullableTypes.decode(readValue(buffer)!);
       case 130:
         return AllTypes.decode(readValue(buffer)!);
       case 131:
diff --git a/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/CoreTests.gen.kt b/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/CoreTests.gen.kt
index eaf7359..0404656 100644
--- a/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/CoreTests.gen.kt
+++ b/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/CoreTests.gen.kt
@@ -59,7 +59,11 @@
   }
 }
 
-/** Generated class from Pigeon that represents data sent in messages. */
+/**
+ * A class containing all supported types.
+ *
+ * Generated class from Pigeon that represents data sent in messages.
+ */
 data class AllTypes (
   val aBool: Boolean,
   val anInt: Long,
@@ -111,7 +115,11 @@
   }
 }
 
-/** Generated class from Pigeon that represents data sent in messages. */
+/**
+ * A class containing all supported nullable types.
+ *
+ * Generated class from Pigeon that represents data sent in messages.
+ */
 data class AllNullableTypes (
   val aNullableBool: Boolean? = null,
   val aNullableInt: Long? = null,
@@ -174,21 +182,34 @@
   }
 }
 
-/** Generated class from Pigeon that represents data sent in messages. */
-data class AllNullableTypesWrapper (
-  val values: AllNullableTypes
+/**
+ * A class for testing nested class handling.
+ *
+ * This is needed to test nested nullable and non-nullable classes,
+ * `AllNullableTypes` is non-nullable here as it is easier to instantiate
+ * than `AllTypes` when testing doesn't require both (ie. testing null classes).
+ *
+ * Generated class from Pigeon that represents data sent in messages.
+ */
+data class AllClassesWrapper (
+  val allNullableTypes: AllNullableTypes,
+  val allTypes: AllTypes? = null
 
 ) {
   companion object {
     @Suppress("UNCHECKED_CAST")
-    fun fromList(list: List<Any?>): AllNullableTypesWrapper {
-      val values = AllNullableTypes.fromList(list[0] as List<Any?>)
-      return AllNullableTypesWrapper(values)
+    fun fromList(list: List<Any?>): AllClassesWrapper {
+      val allNullableTypes = AllNullableTypes.fromList(list[0] as List<Any?>)
+      val allTypes: AllTypes? = (list[1] as List<Any?>?)?.let {
+        AllTypes.fromList(it)
+      }
+      return AllClassesWrapper(allNullableTypes, allTypes)
     }
   }
   fun toList(): List<Any?> {
     return listOf<Any?>(
-      values.toList(),
+      allNullableTypes.toList(),
+      allTypes?.toList(),
     )
   }
 }
@@ -222,12 +243,12 @@
     return when (type) {
       128.toByte() -> {
         return (readValue(buffer) as? List<Any?>)?.let {
-          AllNullableTypes.fromList(it)
+          AllClassesWrapper.fromList(it)
         }
       }
       129.toByte() -> {
         return (readValue(buffer) as? List<Any?>)?.let {
-          AllNullableTypesWrapper.fromList(it)
+          AllNullableTypes.fromList(it)
         }
       }
       130.toByte() -> {
@@ -245,11 +266,11 @@
   }
   override fun writeValue(stream: ByteArrayOutputStream, value: Any?)   {
     when (value) {
-      is AllNullableTypes -> {
+      is AllClassesWrapper -> {
         stream.write(128)
         writeValue(stream, value.toList())
       }
-      is AllNullableTypesWrapper -> {
+      is AllNullableTypes -> {
         stream.write(129)
         writeValue(stream, value.toList())
       }
@@ -302,18 +323,20 @@
   fun echoList(aList: List<Any?>): List<Any?>
   /** Returns the passed map, to test serialization and deserialization. */
   fun echoMap(aMap: Map<String?, Any?>): Map<String?, Any?>
+  /** Returns the passed map to test nested class serialization and deserialization. */
+  fun echoClassWrapper(wrapper: AllClassesWrapper): AllClassesWrapper
   /** Returns the passed object, to test serialization and deserialization. */
   fun echoAllNullableTypes(everything: AllNullableTypes?): AllNullableTypes?
   /**
    * Returns the inner `aString` value from the wrapped object, to test
    * sending of nested objects.
    */
-  fun extractNestedNullableString(wrapper: AllNullableTypesWrapper): String?
+  fun extractNestedNullableString(wrapper: AllClassesWrapper): String?
   /**
    * Returns the inner `aString` value from the wrapped object, to test
    * sending of nested objects.
    */
-  fun createNestedNullableString(nullableString: String?): AllNullableTypesWrapper
+  fun createNestedNullableString(nullableString: String?): AllClassesWrapper
   /** Returns passed in arguments of multiple types. */
   fun sendMultipleNullableTypes(aNullableBool: Boolean?, aNullableInt: Long?, aNullableString: String?): AllNullableTypes
   /** Returns passed in int. */
@@ -636,6 +659,24 @@
         }
       }
       run {
+        val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.HostIntegrationCoreApi.echoClassWrapper", codec)
+        if (api != null) {
+          channel.setMessageHandler { message, reply ->
+            val args = message as List<Any?>
+            val wrapperArg = args[0] as AllClassesWrapper
+            var wrapped: List<Any?>
+            try {
+              wrapped = listOf<Any?>(api.echoClassWrapper(wrapperArg))
+            } catch (exception: Throwable) {
+              wrapped = wrapError(exception)
+            }
+            reply.reply(wrapped)
+          }
+        } else {
+          channel.setMessageHandler(null)
+        }
+      }
+      run {
         val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.HostIntegrationCoreApi.echoAllNullableTypes", codec)
         if (api != null) {
           channel.setMessageHandler { message, reply ->
@@ -658,7 +699,7 @@
         if (api != null) {
           channel.setMessageHandler { message, reply ->
             val args = message as List<Any?>
-            val wrapperArg = args[0] as AllNullableTypesWrapper
+            val wrapperArg = args[0] as AllClassesWrapper
             var wrapped: List<Any?>
             try {
               wrapped = listOf<Any?>(api.extractNestedNullableString(wrapperArg))
@@ -1666,12 +1707,12 @@
     return when (type) {
       128.toByte() -> {
         return (readValue(buffer) as? List<Any?>)?.let {
-          AllNullableTypes.fromList(it)
+          AllClassesWrapper.fromList(it)
         }
       }
       129.toByte() -> {
         return (readValue(buffer) as? List<Any?>)?.let {
-          AllNullableTypesWrapper.fromList(it)
+          AllNullableTypes.fromList(it)
         }
       }
       130.toByte() -> {
@@ -1689,11 +1730,11 @@
   }
   override fun writeValue(stream: ByteArrayOutputStream, value: Any?)   {
     when (value) {
-      is AllNullableTypes -> {
+      is AllClassesWrapper -> {
         stream.write(128)
         writeValue(stream, value.toList())
       }
-      is AllNullableTypesWrapper -> {
+      is AllNullableTypes -> {
         stream.write(129)
         writeValue(stream, value.toList())
       }
diff --git a/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/TestPlugin.kt b/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/TestPlugin.kt
index 1c2b68d..93a6615 100644
--- a/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/TestPlugin.kt
+++ b/packages/pigeon/platform_tests/test_plugin/android/src/main/kotlin/com/example/test_plugin/TestPlugin.kt
@@ -83,12 +83,16 @@
     return aMap
   }
 
-  override fun extractNestedNullableString(wrapper: AllNullableTypesWrapper): String? {
-    return wrapper.values.aNullableString
+  override fun echoClassWrapper(wrapper: AllClassesWrapper): AllClassesWrapper {
+    return wrapper
   }
 
-  override fun createNestedNullableString(nullableString: String?): AllNullableTypesWrapper {
-    return AllNullableTypesWrapper(AllNullableTypes(aNullableString = nullableString))
+  override fun extractNestedNullableString(wrapper: AllClassesWrapper): String? {
+    return wrapper.allNullableTypes.aNullableString
+  }
+
+  override fun createNestedNullableString(nullableString: String?): AllClassesWrapper {
+    return AllClassesWrapper(AllNullableTypes(aNullableString = nullableString))
   }
 
   override fun sendMultipleNullableTypes(aNullableBool: Boolean?, aNullableInt: Long?, aNullableString: String?): AllNullableTypes {
diff --git a/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/AllDatatypesTest.kt b/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/AllDatatypesTest.kt
index 62833ef..09cedf4 100644
--- a/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/AllDatatypesTest.kt
+++ b/packages/pigeon/platform_tests/test_plugin/android/src/test/kotlin/com/example/test_plugin/AllDatatypesTest.kt
@@ -14,6 +14,43 @@
 
 
 internal class AllDatatypesTest: TestCase() {
+    fun compareAllTypes(firstTypes: AllTypes?, secondTypes: AllTypes?) {
+        assertEquals(firstTypes == null, secondTypes == null)
+        if (firstTypes == null || secondTypes == null) {
+            return
+        }
+        assertEquals(firstTypes.aBool, secondTypes.aBool)
+        assertEquals(firstTypes.anInt, secondTypes.anInt)
+        assertEquals(firstTypes.anInt64, secondTypes.anInt64)
+        assertEquals(firstTypes.aDouble, secondTypes.aDouble)
+        assertEquals(firstTypes.aString, secondTypes.aString)
+        assertTrue(firstTypes.aByteArray.contentEquals(secondTypes.aByteArray))
+        assertTrue(firstTypes.a4ByteArray.contentEquals(secondTypes.a4ByteArray))
+        assertTrue(firstTypes.a8ByteArray.contentEquals(secondTypes.a8ByteArray))
+        assertTrue(firstTypes.aFloatArray.contentEquals(secondTypes.aFloatArray))
+        assertEquals(firstTypes.aList, secondTypes.aList)
+        assertEquals(firstTypes.aMap, secondTypes.aMap)
+        assertEquals(firstTypes.anEnum, secondTypes.anEnum)
+    }
+
+    fun compareAllNullableTypes(firstTypes: AllNullableTypes?, secondTypes: AllNullableTypes?) {
+        assertEquals(firstTypes == null, secondTypes == null)
+        if (firstTypes == null || secondTypes == null) {
+            return
+        }
+        assertEquals(firstTypes.aNullableBool, secondTypes.aNullableBool)
+        assertEquals(firstTypes.aNullableInt, secondTypes.aNullableInt)
+        assertEquals(firstTypes.aNullableDouble, secondTypes.aNullableDouble)
+        assertEquals(firstTypes.aNullableString, secondTypes.aNullableString)
+        assertTrue(firstTypes.aNullableByteArray.contentEquals(secondTypes.aNullableByteArray))
+        assertTrue(firstTypes.aNullable4ByteArray.contentEquals(secondTypes.aNullable4ByteArray))
+        assertTrue(firstTypes.aNullable8ByteArray.contentEquals(secondTypes.aNullable8ByteArray))
+        assertTrue(firstTypes.aNullableFloatArray.contentEquals(secondTypes.aNullableFloatArray))
+        assertEquals(firstTypes.aNullableList, secondTypes.aNullableList)
+        assertEquals(firstTypes.aNullableMap, secondTypes.aNullableMap)
+        assertEquals(firstTypes.nullableMapWithObject, secondTypes.nullableMapWithObject)
+    }
+
     @Test
     fun testNullValues() {
         val everything = AllNullableTypes()
@@ -63,7 +100,7 @@
             aNullableFloatArray = doubleArrayOf(0.5, 0.25, 1.5, 1.25),
             aNullableList = listOf(1, 2, 3),
             aNullableMap = mapOf("hello" to 1234),
-            nullableMapWithObject = mapOf("hello" to 1234)
+            nullableMapWithObject = mapOf("hello" to 1234),
         )
         val binaryMessenger = mockk<BinaryMessenger>()
         val api = FlutterIntegrationCoreApi(binaryMessenger)
@@ -82,17 +119,7 @@
         var didCall = false
         api.echoAllNullableTypes(everything) {
             didCall = true
-            assertEquals(everything.aNullableBool, it.aNullableBool)
-            assertEquals(everything.aNullableInt, it.aNullableInt)
-            assertEquals(everything.aNullableDouble, it.aNullableDouble)
-            assertEquals(everything.aNullableString, it.aNullableString)
-            assertTrue(everything.aNullableByteArray.contentEquals(it.aNullableByteArray))
-            assertTrue(everything.aNullable4ByteArray.contentEquals(it.aNullable4ByteArray))
-            assertTrue(everything.aNullable8ByteArray.contentEquals(it.aNullable8ByteArray))
-            assertTrue(everything.aNullableFloatArray.contentEquals(it.aNullableFloatArray))
-            assertEquals(everything.aNullableList, it.aNullableList)
-            assertEquals(everything.aNullableMap, it.aNullableMap)
-            assertEquals(everything.nullableMapWithObject, it.nullableMapWithObject)
+            compareAllNullableTypes(everything, it)
         }
 
         assertTrue(didCall)
diff --git a/packages/pigeon/platform_tests/test_plugin/example/ios/Runner.xcodeproj/project.pbxproj b/packages/pigeon/platform_tests/test_plugin/example/ios/Runner.xcodeproj/project.pbxproj
index eb200b2..969f970 100644
--- a/packages/pigeon/platform_tests/test_plugin/example/ios/Runner.xcodeproj/project.pbxproj
+++ b/packages/pigeon/platform_tests/test_plugin/example/ios/Runner.xcodeproj/project.pbxproj
@@ -28,6 +28,7 @@
 		97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
 		97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
 		97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
+		E04641FA2A46270400661C9E /* NSNullFieldTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E04641F92A46270400661C9E /* NSNullFieldTests.swift */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
@@ -88,6 +89,7 @@
 		9808B6775522250A40D7D452 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
 		BC37C4E8AE005B445F208C02 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		BF5B776B52F984FB430C15A3 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
+		E04641F92A46270400661C9E /* NSNullFieldTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSNullFieldTests.swift; sourceTree = "<group>"; };
 		EB04430DB6D43CCC08FA526B /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
@@ -126,6 +128,7 @@
 				33A341C6291ECDFD00D34E0F /* NullableReturnsTests.swift */,
 				33A341C7291ECDFD00D34E0F /* PrimitiveTests.swift */,
 				33A341B7291ECCA100D34E0F /* RunnerTests.swift */,
+				E04641F92A46270400661C9E /* NSNullFieldTests.swift */,
 				33A341CA291ECDFD00D34E0F /* Utils.swift */,
 			);
 			path = RunnerTests;
@@ -413,6 +416,7 @@
 				33A341D5291ECDFD00D34E0F /* AsyncHandlersTest.swift in Sources */,
 				33A341CE291ECDFD00D34E0F /* EnumTests.swift in Sources */,
 				33A341CD291ECDFD00D34E0F /* ListTests.swift in Sources */,
+				E04641FA2A46270400661C9E /* NSNullFieldTests.swift in Sources */,
 				33A341D1291ECDFD00D34E0F /* MockBinaryMessenger.swift in Sources */,
 				33A341CF291ECDFD00D34E0F /* NonNullFieldsTest.swift in Sources */,
 				33A341CB291ECDFD00D34E0F /* MultipleArityTests.swift in Sources */,
diff --git a/packages/pigeon/platform_tests/test_plugin/example/ios/RunnerTests/NSNullFieldTests.swift b/packages/pigeon/platform_tests/test_plugin/example/ios/RunnerTests/NSNullFieldTests.swift
new file mode 100644
index 0000000..036608f
--- /dev/null
+++ b/packages/pigeon/platform_tests/test_plugin/example/ios/RunnerTests/NSNullFieldTests.swift
@@ -0,0 +1,61 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import XCTest
+@testable import test_plugin
+
+/// Tests NSNull is correctly handled by `nilOrValue` helper, by manually setting nullable fields to NSNull.
+final class NSNullFieldTests: XCTestCase {
+
+  func testNSNull_nullListToCustomStructField() throws {
+    let reply = NullFieldsSearchReply(
+      result: nil,
+      error: nil,
+      indices: nil,
+      request: nil,
+      type: nil)
+    var list = reply.toList()
+    // request field
+    list[3] = NSNull()
+    let copy = NullFieldsSearchReply.fromList(list)
+    XCTAssertNotNil(copy)
+    XCTAssertNil(copy!.request)
+  }
+
+  func testNSNull_nullListField() {
+    let reply = NullFieldsSearchReply(
+      result: nil,
+      error: nil,
+      indices: nil,
+      request: nil,
+      type: nil)
+    var list = reply.toList()
+    // indices field
+    list[2] = NSNull()
+    let copy = NullFieldsSearchReply.fromList(list)
+    XCTAssertNotNil(copy)
+    XCTAssertNil(copy!.indices)
+  }
+
+  func testNSNull_nullBasicFields() throws {
+    let reply = NullFieldsSearchReply(
+      result: nil,
+      error: nil,
+      indices: nil,
+      request: nil,
+      type: nil)
+    var list = reply.toList()
+    // result field
+    list[0] = NSNull()
+    // error field
+    list[1] = NSNull()
+    // type field
+    list[4] = NSNull()
+    let copy = NullFieldsSearchReply.fromList(list)
+    XCTAssertNotNil(copy)
+    XCTAssertNil(copy!.result)
+    XCTAssertNil(copy!.error)
+    XCTAssertNil(copy!.type)
+  }
+}
diff --git a/packages/pigeon/platform_tests/test_plugin/ios/Classes/CoreTests.gen.swift b/packages/pigeon/platform_tests/test_plugin/ios/Classes/CoreTests.gen.swift
index 4c1dfa0..a2dd43f 100644
--- a/packages/pigeon/platform_tests/test_plugin/ios/Classes/CoreTests.gen.swift
+++ b/packages/pigeon/platform_tests/test_plugin/ios/Classes/CoreTests.gen.swift
@@ -44,6 +44,8 @@
   case three = 2
 }
 
+/// A class containing all supported types.
+///
 /// Generated class from Pigeon that represents data sent in messages.
 struct AllTypes {
   var aBool: Bool
@@ -106,6 +108,8 @@
   }
 }
 
+/// A class containing all supported nullable types.
+///
 /// Generated class from Pigeon that represents data sent in messages.
 struct AllNullableTypes {
   var aNullableBool: Bool? = nil
@@ -184,20 +188,33 @@
   }
 }
 
+/// A class for testing nested class handling.
+///
+/// This is needed to test nested nullable and non-nullable classes,
+/// `AllNullableTypes` is non-nullable here as it is easier to instantiate
+/// than `AllTypes` when testing doesn't require both (ie. testing null classes).
+///
 /// Generated class from Pigeon that represents data sent in messages.
-struct AllNullableTypesWrapper {
-  var values: AllNullableTypes
+struct AllClassesWrapper {
+  var allNullableTypes: AllNullableTypes
+  var allTypes: AllTypes? = nil
 
-  static func fromList(_ list: [Any?]) -> AllNullableTypesWrapper? {
-    let values = AllNullableTypes.fromList(list[0] as! [Any?])!
+  static func fromList(_ list: [Any?]) -> AllClassesWrapper? {
+    let allNullableTypes = AllNullableTypes.fromList(list[0] as! [Any?])!
+    var allTypes: AllTypes? = nil
+    if let allTypesList: [Any?] = nilOrValue(list[1]) {
+      allTypes = AllTypes.fromList(allTypesList)
+    }
 
-    return AllNullableTypesWrapper(
-      values: values
+    return AllClassesWrapper(
+      allNullableTypes: allNullableTypes,
+      allTypes: allTypes
     )
   }
   func toList() -> [Any?] {
     return [
-      values.toList(),
+      allNullableTypes.toList(),
+      allTypes?.toList(),
     ]
   }
 }
@@ -226,9 +243,9 @@
   override func readValue(ofType type: UInt8) -> Any? {
     switch type {
       case 128:
-        return AllNullableTypes.fromList(self.readValue() as! [Any?])
+        return AllClassesWrapper.fromList(self.readValue() as! [Any?])
       case 129:
-        return AllNullableTypesWrapper.fromList(self.readValue() as! [Any?])
+        return AllNullableTypes.fromList(self.readValue() as! [Any?])
       case 130:
         return AllTypes.fromList(self.readValue() as! [Any?])
       case 131:
@@ -241,10 +258,10 @@
 
 private class HostIntegrationCoreApiCodecWriter: FlutterStandardWriter {
   override func writeValue(_ value: Any) {
-    if let value = value as? AllNullableTypes {
+    if let value = value as? AllClassesWrapper {
       super.writeByte(128)
       super.writeValue(value.toList())
-    } else if let value = value as? AllNullableTypesWrapper {
+    } else if let value = value as? AllNullableTypes {
       super.writeByte(129)
       super.writeValue(value.toList())
     } else if let value = value as? AllTypes {
@@ -305,14 +322,16 @@
   func echo(_ aList: [Any?]) throws -> [Any?]
   /// Returns the passed map, to test serialization and deserialization.
   func echo(_ aMap: [String?: Any?]) throws -> [String?: Any?]
+  /// Returns the passed map to test nested class serialization and deserialization.
+  func echo(_ wrapper: AllClassesWrapper) throws -> AllClassesWrapper
   /// Returns the passed object, to test serialization and deserialization.
   func echo(_ everything: AllNullableTypes?) throws -> AllNullableTypes?
   /// Returns the inner `aString` value from the wrapped object, to test
   /// sending of nested objects.
-  func extractNestedNullableString(from wrapper: AllNullableTypesWrapper) throws -> String?
+  func extractNestedNullableString(from wrapper: AllClassesWrapper) throws -> String?
   /// Returns the inner `aString` value from the wrapped object, to test
   /// sending of nested objects.
-  func createNestedObject(with nullableString: String?) throws -> AllNullableTypesWrapper
+  func createNestedObject(with nullableString: String?) throws -> AllClassesWrapper
   /// Returns passed in arguments of multiple types.
   func sendMultipleNullableTypes(aBool aNullableBool: Bool?, anInt aNullableInt: Int64?, aString aNullableString: String?) throws -> AllNullableTypes
   /// Returns passed in int.
@@ -604,6 +623,22 @@
     } else {
       echoMapChannel.setMessageHandler(nil)
     }
+    /// Returns the passed map to test nested class serialization and deserialization.
+    let echoClassWrapperChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.HostIntegrationCoreApi.echoClassWrapper", binaryMessenger: binaryMessenger, codec: codec)
+    if let api = api {
+      echoClassWrapperChannel.setMessageHandler { message, reply in
+        let args = message as! [Any?]
+        let wrapperArg = args[0] as! AllClassesWrapper
+        do {
+          let result = try api.echo(wrapperArg)
+          reply(wrapResult(result))
+        } catch {
+          reply(wrapError(error))
+        }
+      }
+    } else {
+      echoClassWrapperChannel.setMessageHandler(nil)
+    }
     /// Returns the passed object, to test serialization and deserialization.
     let echoAllNullableTypesChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.HostIntegrationCoreApi.echoAllNullableTypes", binaryMessenger: binaryMessenger, codec: codec)
     if let api = api {
@@ -626,7 +661,7 @@
     if let api = api {
       extractNestedNullableStringChannel.setMessageHandler { message, reply in
         let args = message as! [Any?]
-        let wrapperArg = args[0] as! AllNullableTypesWrapper
+        let wrapperArg = args[0] as! AllClassesWrapper
         do {
           let result = try api.extractNestedNullableString(from: wrapperArg)
           reply(wrapResult(result))
@@ -1514,9 +1549,9 @@
   override func readValue(ofType type: UInt8) -> Any? {
     switch type {
       case 128:
-        return AllNullableTypes.fromList(self.readValue() as! [Any?])
+        return AllClassesWrapper.fromList(self.readValue() as! [Any?])
       case 129:
-        return AllNullableTypesWrapper.fromList(self.readValue() as! [Any?])
+        return AllNullableTypes.fromList(self.readValue() as! [Any?])
       case 130:
         return AllTypes.fromList(self.readValue() as! [Any?])
       case 131:
@@ -1529,10 +1564,10 @@
 
 private class FlutterIntegrationCoreApiCodecWriter: FlutterStandardWriter {
   override func writeValue(_ value: Any) {
-    if let value = value as? AllNullableTypes {
+    if let value = value as? AllClassesWrapper {
       super.writeByte(128)
       super.writeValue(value.toList())
-    } else if let value = value as? AllNullableTypesWrapper {
+    } else if let value = value as? AllNullableTypes {
       super.writeByte(129)
       super.writeValue(value.toList())
     } else if let value = value as? AllTypes {
diff --git a/packages/pigeon/platform_tests/test_plugin/ios/Classes/TestPlugin.swift b/packages/pigeon/platform_tests/test_plugin/ios/Classes/TestPlugin.swift
index 368dbde..315c86c 100644
--- a/packages/pigeon/platform_tests/test_plugin/ios/Classes/TestPlugin.swift
+++ b/packages/pigeon/platform_tests/test_plugin/ios/Classes/TestPlugin.swift
@@ -81,12 +81,16 @@
     return aMap
   }
 
-  func extractNestedNullableString(from wrapper: AllNullableTypesWrapper) -> String? {
-    return wrapper.values.aNullableString;
+   func echo(_ wrapper: AllClassesWrapper) throws -> AllClassesWrapper {
+    return wrapper
+   }
+
+  func extractNestedNullableString(from wrapper: AllClassesWrapper) -> String? {
+    return wrapper.allNullableTypes.aNullableString;
   }
 
-  func createNestedObject(with nullableString: String?) -> AllNullableTypesWrapper {
-    return AllNullableTypesWrapper(values: AllNullableTypes(aNullableString: nullableString))
+  func createNestedObject(with nullableString: String?) -> AllClassesWrapper {
+    return AllClassesWrapper(allNullableTypes: AllNullableTypes(aNullableString: nullableString))
   }
 
   func sendMultipleNullableTypes(aBool aNullableBool: Bool?, anInt aNullableInt: Int64?, aString aNullableString: String?) -> AllNullableTypes {
diff --git a/packages/pigeon/platform_tests/test_plugin/macos/Classes/CoreTests.gen.swift b/packages/pigeon/platform_tests/test_plugin/macos/Classes/CoreTests.gen.swift
index 4c1dfa0..a2dd43f 100644
--- a/packages/pigeon/platform_tests/test_plugin/macos/Classes/CoreTests.gen.swift
+++ b/packages/pigeon/platform_tests/test_plugin/macos/Classes/CoreTests.gen.swift
@@ -44,6 +44,8 @@
   case three = 2
 }
 
+/// A class containing all supported types.
+///
 /// Generated class from Pigeon that represents data sent in messages.
 struct AllTypes {
   var aBool: Bool
@@ -106,6 +108,8 @@
   }
 }
 
+/// A class containing all supported nullable types.
+///
 /// Generated class from Pigeon that represents data sent in messages.
 struct AllNullableTypes {
   var aNullableBool: Bool? = nil
@@ -184,20 +188,33 @@
   }
 }
 
+/// A class for testing nested class handling.
+///
+/// This is needed to test nested nullable and non-nullable classes,
+/// `AllNullableTypes` is non-nullable here as it is easier to instantiate
+/// than `AllTypes` when testing doesn't require both (ie. testing null classes).
+///
 /// Generated class from Pigeon that represents data sent in messages.
-struct AllNullableTypesWrapper {
-  var values: AllNullableTypes
+struct AllClassesWrapper {
+  var allNullableTypes: AllNullableTypes
+  var allTypes: AllTypes? = nil
 
-  static func fromList(_ list: [Any?]) -> AllNullableTypesWrapper? {
-    let values = AllNullableTypes.fromList(list[0] as! [Any?])!
+  static func fromList(_ list: [Any?]) -> AllClassesWrapper? {
+    let allNullableTypes = AllNullableTypes.fromList(list[0] as! [Any?])!
+    var allTypes: AllTypes? = nil
+    if let allTypesList: [Any?] = nilOrValue(list[1]) {
+      allTypes = AllTypes.fromList(allTypesList)
+    }
 
-    return AllNullableTypesWrapper(
-      values: values
+    return AllClassesWrapper(
+      allNullableTypes: allNullableTypes,
+      allTypes: allTypes
     )
   }
   func toList() -> [Any?] {
     return [
-      values.toList(),
+      allNullableTypes.toList(),
+      allTypes?.toList(),
     ]
   }
 }
@@ -226,9 +243,9 @@
   override func readValue(ofType type: UInt8) -> Any? {
     switch type {
       case 128:
-        return AllNullableTypes.fromList(self.readValue() as! [Any?])
+        return AllClassesWrapper.fromList(self.readValue() as! [Any?])
       case 129:
-        return AllNullableTypesWrapper.fromList(self.readValue() as! [Any?])
+        return AllNullableTypes.fromList(self.readValue() as! [Any?])
       case 130:
         return AllTypes.fromList(self.readValue() as! [Any?])
       case 131:
@@ -241,10 +258,10 @@
 
 private class HostIntegrationCoreApiCodecWriter: FlutterStandardWriter {
   override func writeValue(_ value: Any) {
-    if let value = value as? AllNullableTypes {
+    if let value = value as? AllClassesWrapper {
       super.writeByte(128)
       super.writeValue(value.toList())
-    } else if let value = value as? AllNullableTypesWrapper {
+    } else if let value = value as? AllNullableTypes {
       super.writeByte(129)
       super.writeValue(value.toList())
     } else if let value = value as? AllTypes {
@@ -305,14 +322,16 @@
   func echo(_ aList: [Any?]) throws -> [Any?]
   /// Returns the passed map, to test serialization and deserialization.
   func echo(_ aMap: [String?: Any?]) throws -> [String?: Any?]
+  /// Returns the passed map to test nested class serialization and deserialization.
+  func echo(_ wrapper: AllClassesWrapper) throws -> AllClassesWrapper
   /// Returns the passed object, to test serialization and deserialization.
   func echo(_ everything: AllNullableTypes?) throws -> AllNullableTypes?
   /// Returns the inner `aString` value from the wrapped object, to test
   /// sending of nested objects.
-  func extractNestedNullableString(from wrapper: AllNullableTypesWrapper) throws -> String?
+  func extractNestedNullableString(from wrapper: AllClassesWrapper) throws -> String?
   /// Returns the inner `aString` value from the wrapped object, to test
   /// sending of nested objects.
-  func createNestedObject(with nullableString: String?) throws -> AllNullableTypesWrapper
+  func createNestedObject(with nullableString: String?) throws -> AllClassesWrapper
   /// Returns passed in arguments of multiple types.
   func sendMultipleNullableTypes(aBool aNullableBool: Bool?, anInt aNullableInt: Int64?, aString aNullableString: String?) throws -> AllNullableTypes
   /// Returns passed in int.
@@ -604,6 +623,22 @@
     } else {
       echoMapChannel.setMessageHandler(nil)
     }
+    /// Returns the passed map to test nested class serialization and deserialization.
+    let echoClassWrapperChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.HostIntegrationCoreApi.echoClassWrapper", binaryMessenger: binaryMessenger, codec: codec)
+    if let api = api {
+      echoClassWrapperChannel.setMessageHandler { message, reply in
+        let args = message as! [Any?]
+        let wrapperArg = args[0] as! AllClassesWrapper
+        do {
+          let result = try api.echo(wrapperArg)
+          reply(wrapResult(result))
+        } catch {
+          reply(wrapError(error))
+        }
+      }
+    } else {
+      echoClassWrapperChannel.setMessageHandler(nil)
+    }
     /// Returns the passed object, to test serialization and deserialization.
     let echoAllNullableTypesChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.HostIntegrationCoreApi.echoAllNullableTypes", binaryMessenger: binaryMessenger, codec: codec)
     if let api = api {
@@ -626,7 +661,7 @@
     if let api = api {
       extractNestedNullableStringChannel.setMessageHandler { message, reply in
         let args = message as! [Any?]
-        let wrapperArg = args[0] as! AllNullableTypesWrapper
+        let wrapperArg = args[0] as! AllClassesWrapper
         do {
           let result = try api.extractNestedNullableString(from: wrapperArg)
           reply(wrapResult(result))
@@ -1514,9 +1549,9 @@
   override func readValue(ofType type: UInt8) -> Any? {
     switch type {
       case 128:
-        return AllNullableTypes.fromList(self.readValue() as! [Any?])
+        return AllClassesWrapper.fromList(self.readValue() as! [Any?])
       case 129:
-        return AllNullableTypesWrapper.fromList(self.readValue() as! [Any?])
+        return AllNullableTypes.fromList(self.readValue() as! [Any?])
       case 130:
         return AllTypes.fromList(self.readValue() as! [Any?])
       case 131:
@@ -1529,10 +1564,10 @@
 
 private class FlutterIntegrationCoreApiCodecWriter: FlutterStandardWriter {
   override func writeValue(_ value: Any) {
-    if let value = value as? AllNullableTypes {
+    if let value = value as? AllClassesWrapper {
       super.writeByte(128)
       super.writeValue(value.toList())
-    } else if let value = value as? AllNullableTypesWrapper {
+    } else if let value = value as? AllNullableTypes {
       super.writeByte(129)
       super.writeValue(value.toList())
     } else if let value = value as? AllTypes {
diff --git a/packages/pigeon/platform_tests/test_plugin/macos/Classes/TestPlugin.swift b/packages/pigeon/platform_tests/test_plugin/macos/Classes/TestPlugin.swift
index 51a68c3..d5f27f6 100644
--- a/packages/pigeon/platform_tests/test_plugin/macos/Classes/TestPlugin.swift
+++ b/packages/pigeon/platform_tests/test_plugin/macos/Classes/TestPlugin.swift
@@ -80,12 +80,16 @@
     return aMap
   }
 
-  func extractNestedNullableString(from wrapper: AllNullableTypesWrapper) -> String? {
-    return wrapper.values.aNullableString;
+   func echo(_ wrapper: AllClassesWrapper) throws -> AllClassesWrapper {
+    return wrapper
+   }
+
+  func extractNestedNullableString(from wrapper: AllClassesWrapper) -> String? {
+    return wrapper.allNullableTypes.aNullableString;
   }
 
-  func createNestedObject(with nullableString: String?) -> AllNullableTypesWrapper {
-    return AllNullableTypesWrapper(values: AllNullableTypes(aNullableString: nullableString))
+  func createNestedObject(with nullableString: String?) -> AllClassesWrapper {
+    return AllClassesWrapper(allNullableTypes: AllNullableTypes(aNullableString: nullableString))
   }
 
   func sendMultipleNullableTypes(aBool aNullableBool: Bool?, anInt aNullableInt: Int64?, aString aNullableString: String?) -> AllNullableTypes {
diff --git a/packages/pigeon/platform_tests/test_plugin/windows/pigeon/core_tests.gen.cpp b/packages/pigeon/platform_tests/test_plugin/windows/pigeon/core_tests.gen.cpp
index a4a17f9..af8e5a2 100644
--- a/packages/pigeon/platform_tests/test_plugin/windows/pigeon/core_tests.gen.cpp
+++ b/packages/pigeon/platform_tests/test_plugin/windows/pigeon/core_tests.gen.cpp
@@ -543,30 +543,56 @@
   return decoded;
 }
 
-// AllNullableTypesWrapper
+// AllClassesWrapper
 
-AllNullableTypesWrapper::AllNullableTypesWrapper(const AllNullableTypes& values)
-    : values_(values) {}
+AllClassesWrapper::AllClassesWrapper(const AllNullableTypes& all_nullable_types)
+    : all_nullable_types_(all_nullable_types) {}
 
-const AllNullableTypes& AllNullableTypesWrapper::values() const {
-  return values_;
+AllClassesWrapper::AllClassesWrapper(const AllNullableTypes& all_nullable_types,
+                                     const AllTypes* all_types)
+    : all_nullable_types_(all_nullable_types),
+      all_types_(all_types ? std::optional<AllTypes>(*all_types)
+                           : std::nullopt) {}
+
+const AllNullableTypes& AllClassesWrapper::all_nullable_types() const {
+  return all_nullable_types_;
 }
 
-void AllNullableTypesWrapper::set_values(const AllNullableTypes& value_arg) {
-  values_ = value_arg;
+void AllClassesWrapper::set_all_nullable_types(
+    const AllNullableTypes& value_arg) {
+  all_nullable_types_ = value_arg;
 }
 
-EncodableList AllNullableTypesWrapper::ToEncodableList() const {
+const AllTypes* AllClassesWrapper::all_types() const {
+  return all_types_ ? &(*all_types_) : nullptr;
+}
+
+void AllClassesWrapper::set_all_types(const AllTypes* value_arg) {
+  all_types_ = value_arg ? std::optional<AllTypes>(*value_arg) : std::nullopt;
+}
+
+void AllClassesWrapper::set_all_types(const AllTypes& value_arg) {
+  all_types_ = value_arg;
+}
+
+EncodableList AllClassesWrapper::ToEncodableList() const {
   EncodableList list;
-  list.reserve(1);
-  list.push_back(EncodableValue(values_.ToEncodableList()));
+  list.reserve(2);
+  list.push_back(EncodableValue(all_nullable_types_.ToEncodableList()));
+  list.push_back(all_types_ ? EncodableValue(all_types_->ToEncodableList())
+                            : EncodableValue());
   return list;
 }
 
-AllNullableTypesWrapper AllNullableTypesWrapper::FromEncodableList(
+AllClassesWrapper AllClassesWrapper::FromEncodableList(
     const EncodableList& list) {
-  AllNullableTypesWrapper decoded(
+  AllClassesWrapper decoded(
       AllNullableTypes::FromEncodableList(std::get<EncodableList>(list[0])));
+  auto& encodable_all_types = list[1];
+  if (!encodable_all_types.IsNull()) {
+    decoded.set_all_types(AllTypes::FromEncodableList(
+        std::get<EncodableList>(encodable_all_types)));
+  }
   return decoded;
 }
 
@@ -614,10 +640,10 @@
     uint8_t type, flutter::ByteStreamReader* stream) const {
   switch (type) {
     case 128:
-      return CustomEncodableValue(AllNullableTypes::FromEncodableList(
+      return CustomEncodableValue(AllClassesWrapper::FromEncodableList(
           std::get<EncodableList>(ReadValue(stream))));
     case 129:
-      return CustomEncodableValue(AllNullableTypesWrapper::FromEncodableList(
+      return CustomEncodableValue(AllNullableTypes::FromEncodableList(
           std::get<EncodableList>(ReadValue(stream))));
     case 130:
       return CustomEncodableValue(AllTypes::FromEncodableList(
@@ -634,22 +660,21 @@
     const EncodableValue& value, flutter::ByteStreamWriter* stream) const {
   if (const CustomEncodableValue* custom_value =
           std::get_if<CustomEncodableValue>(&value)) {
-    if (custom_value->type() == typeid(AllNullableTypes)) {
+    if (custom_value->type() == typeid(AllClassesWrapper)) {
       stream->WriteByte(128);
+      WriteValue(EncodableValue(std::any_cast<AllClassesWrapper>(*custom_value)
+                                    .ToEncodableList()),
+                 stream);
+      return;
+    }
+    if (custom_value->type() == typeid(AllNullableTypes)) {
+      stream->WriteByte(129);
       WriteValue(
           EncodableValue(
               std::any_cast<AllNullableTypes>(*custom_value).ToEncodableList()),
           stream);
       return;
     }
-    if (custom_value->type() == typeid(AllNullableTypesWrapper)) {
-      stream->WriteByte(129);
-      WriteValue(
-          EncodableValue(std::any_cast<AllNullableTypesWrapper>(*custom_value)
-                             .ToEncodableList()),
-          stream);
-      return;
-    }
     if (custom_value->type() == typeid(AllTypes)) {
       stream->WriteByte(130);
       WriteValue(EncodableValue(
@@ -1093,6 +1118,42 @@
   {
     auto channel = std::make_unique<BasicMessageChannel<>>(
         binary_messenger,
+        "dev.flutter.pigeon.HostIntegrationCoreApi.echoClassWrapper",
+        &GetCodec());
+    if (api != nullptr) {
+      channel->SetMessageHandler(
+          [api](const EncodableValue& message,
+                const flutter::MessageReply<EncodableValue>& reply) {
+            try {
+              const auto& args = std::get<EncodableList>(message);
+              const auto& encodable_wrapper_arg = args.at(0);
+              if (encodable_wrapper_arg.IsNull()) {
+                reply(WrapError("wrapper_arg unexpectedly null."));
+                return;
+              }
+              const auto& wrapper_arg = std::any_cast<const AllClassesWrapper&>(
+                  std::get<CustomEncodableValue>(encodable_wrapper_arg));
+              ErrorOr<AllClassesWrapper> output =
+                  api->EchoClassWrapper(wrapper_arg);
+              if (output.has_error()) {
+                reply(WrapError(output.error()));
+                return;
+              }
+              EncodableList wrapped;
+              wrapped.push_back(
+                  CustomEncodableValue(std::move(output).TakeValue()));
+              reply(EncodableValue(std::move(wrapped)));
+            } catch (const std::exception& exception) {
+              reply(WrapError(exception.what()));
+            }
+          });
+    } else {
+      channel->SetMessageHandler(nullptr);
+    }
+  }
+  {
+    auto channel = std::make_unique<BasicMessageChannel<>>(
+        binary_messenger,
         "dev.flutter.pigeon.HostIntegrationCoreApi.echoAllNullableTypes",
         &GetCodec());
     if (api != nullptr) {
@@ -1145,9 +1206,8 @@
                 reply(WrapError("wrapper_arg unexpectedly null."));
                 return;
               }
-              const auto& wrapper_arg =
-                  std::any_cast<const AllNullableTypesWrapper&>(
-                      std::get<CustomEncodableValue>(encodable_wrapper_arg));
+              const auto& wrapper_arg = std::any_cast<const AllClassesWrapper&>(
+                  std::get<CustomEncodableValue>(encodable_wrapper_arg));
               ErrorOr<std::optional<std::string>> output =
                   api->ExtractNestedNullableString(wrapper_arg);
               if (output.has_error()) {
@@ -1185,7 +1245,7 @@
               const auto& encodable_nullable_string_arg = args.at(0);
               const auto* nullable_string_arg =
                   std::get_if<std::string>(&encodable_nullable_string_arg);
-              ErrorOr<AllNullableTypesWrapper> output =
+              ErrorOr<AllClassesWrapper> output =
                   api->CreateNestedNullableString(nullable_string_arg);
               if (output.has_error()) {
                 reply(WrapError(output.error()));
@@ -3088,10 +3148,10 @@
     uint8_t type, flutter::ByteStreamReader* stream) const {
   switch (type) {
     case 128:
-      return CustomEncodableValue(AllNullableTypes::FromEncodableList(
+      return CustomEncodableValue(AllClassesWrapper::FromEncodableList(
           std::get<EncodableList>(ReadValue(stream))));
     case 129:
-      return CustomEncodableValue(AllNullableTypesWrapper::FromEncodableList(
+      return CustomEncodableValue(AllNullableTypes::FromEncodableList(
           std::get<EncodableList>(ReadValue(stream))));
     case 130:
       return CustomEncodableValue(AllTypes::FromEncodableList(
@@ -3108,22 +3168,21 @@
     const EncodableValue& value, flutter::ByteStreamWriter* stream) const {
   if (const CustomEncodableValue* custom_value =
           std::get_if<CustomEncodableValue>(&value)) {
-    if (custom_value->type() == typeid(AllNullableTypes)) {
+    if (custom_value->type() == typeid(AllClassesWrapper)) {
       stream->WriteByte(128);
+      WriteValue(EncodableValue(std::any_cast<AllClassesWrapper>(*custom_value)
+                                    .ToEncodableList()),
+                 stream);
+      return;
+    }
+    if (custom_value->type() == typeid(AllNullableTypes)) {
+      stream->WriteByte(129);
       WriteValue(
           EncodableValue(
               std::any_cast<AllNullableTypes>(*custom_value).ToEncodableList()),
           stream);
       return;
     }
-    if (custom_value->type() == typeid(AllNullableTypesWrapper)) {
-      stream->WriteByte(129);
-      WriteValue(
-          EncodableValue(std::any_cast<AllNullableTypesWrapper>(*custom_value)
-                             .ToEncodableList()),
-          stream);
-      return;
-    }
     if (custom_value->type() == typeid(AllTypes)) {
       stream->WriteByte(130);
       WriteValue(EncodableValue(
diff --git a/packages/pigeon/platform_tests/test_plugin/windows/pigeon/core_tests.gen.h b/packages/pigeon/platform_tests/test_plugin/windows/pigeon/core_tests.gen.h
index 413c19d..67b0302 100644
--- a/packages/pigeon/platform_tests/test_plugin/windows/pigeon/core_tests.gen.h
+++ b/packages/pigeon/platform_tests/test_plugin/windows/pigeon/core_tests.gen.h
@@ -67,6 +67,8 @@
 
 enum class AnEnum { one = 0, two = 1, three = 2 };
 
+// A class containing all supported types.
+//
 // Generated class from Pigeon that represents data sent in messages.
 class AllTypes {
  public:
@@ -119,6 +121,7 @@
  private:
   static AllTypes FromEncodableList(const flutter::EncodableList& list);
   flutter::EncodableList ToEncodableList() const;
+  friend class AllClassesWrapper;
   friend class HostIntegrationCoreApi;
   friend class HostIntegrationCoreApiCodecSerializer;
   friend class FlutterIntegrationCoreApi;
@@ -144,6 +147,8 @@
   std::string a_string_;
 };
 
+// A class containing all supported nullable types.
+//
 // Generated class from Pigeon that represents data sent in messages.
 class AllNullableTypes {
  public:
@@ -230,7 +235,7 @@
  private:
   static AllNullableTypes FromEncodableList(const flutter::EncodableList& list);
   flutter::EncodableList ToEncodableList() const;
-  friend class AllNullableTypesWrapper;
+  friend class AllClassesWrapper;
   friend class HostIntegrationCoreApi;
   friend class HostIntegrationCoreApiCodecSerializer;
   friend class FlutterIntegrationCoreApi;
@@ -259,17 +264,31 @@
   std::optional<std::string> a_nullable_string_;
 };
 
+// A class for testing nested class handling.
+//
+// This is needed to test nested nullable and non-nullable classes,
+// `AllNullableTypes` is non-nullable here as it is easier to instantiate
+// than `AllTypes` when testing doesn't require both (ie. testing null classes).
+//
 // Generated class from Pigeon that represents data sent in messages.
-class AllNullableTypesWrapper {
+class AllClassesWrapper {
  public:
-  // Constructs an object setting all fields.
-  explicit AllNullableTypesWrapper(const AllNullableTypes& values);
+  // Constructs an object setting all non-nullable fields.
+  explicit AllClassesWrapper(const AllNullableTypes& all_nullable_types);
 
-  const AllNullableTypes& values() const;
-  void set_values(const AllNullableTypes& value_arg);
+  // Constructs an object setting all fields.
+  explicit AllClassesWrapper(const AllNullableTypes& all_nullable_types,
+                             const AllTypes* all_types);
+
+  const AllNullableTypes& all_nullable_types() const;
+  void set_all_nullable_types(const AllNullableTypes& value_arg);
+
+  const AllTypes* all_types() const;
+  void set_all_types(const AllTypes* value_arg);
+  void set_all_types(const AllTypes& value_arg);
 
  private:
-  static AllNullableTypesWrapper FromEncodableList(
+  static AllClassesWrapper FromEncodableList(
       const flutter::EncodableList& list);
   flutter::EncodableList ToEncodableList() const;
   friend class HostIntegrationCoreApi;
@@ -283,7 +302,8 @@
   friend class FlutterSmallApi;
   friend class FlutterSmallApiCodecSerializer;
   friend class CoreTestsTest;
-  AllNullableTypes values_;
+  AllNullableTypes all_nullable_types_;
+  std::optional<AllTypes> all_types_;
 };
 
 // A data class containing a List, used in unit tests.
@@ -377,16 +397,20 @@
   // Returns the passed map, to test serialization and deserialization.
   virtual ErrorOr<flutter::EncodableMap> EchoMap(
       const flutter::EncodableMap& a_map) = 0;
+  // Returns the passed map to test nested class serialization and
+  // deserialization.
+  virtual ErrorOr<AllClassesWrapper> EchoClassWrapper(
+      const AllClassesWrapper& wrapper) = 0;
   // Returns the passed object, to test serialization and deserialization.
   virtual ErrorOr<std::optional<AllNullableTypes>> EchoAllNullableTypes(
       const AllNullableTypes* everything) = 0;
   // Returns the inner `aString` value from the wrapped object, to test
   // sending of nested objects.
   virtual ErrorOr<std::optional<std::string>> ExtractNestedNullableString(
-      const AllNullableTypesWrapper& wrapper) = 0;
+      const AllClassesWrapper& wrapper) = 0;
   // Returns the inner `aString` value from the wrapped object, to test
   // sending of nested objects.
-  virtual ErrorOr<AllNullableTypesWrapper> CreateNestedNullableString(
+  virtual ErrorOr<AllClassesWrapper> CreateNestedNullableString(
       const std::string* nullable_string) = 0;
   // Returns passed in arguments of multiple types.
   virtual ErrorOr<AllNullableTypes> SendMultipleNullableTypes(
diff --git a/packages/pigeon/platform_tests/test_plugin/windows/test_plugin.cpp b/packages/pigeon/platform_tests/test_plugin/windows/test_plugin.cpp
index 713f17b..1257710 100644
--- a/packages/pigeon/platform_tests/test_plugin/windows/test_plugin.cpp
+++ b/packages/pigeon/platform_tests/test_plugin/windows/test_plugin.cpp
@@ -16,8 +16,8 @@
 
 namespace test_plugin {
 
+using core_tests_pigeontest::AllClassesWrapper;
 using core_tests_pigeontest::AllNullableTypes;
-using core_tests_pigeontest::AllNullableTypesWrapper;
 using core_tests_pigeontest::AllTypes;
 using core_tests_pigeontest::ErrorOr;
 using core_tests_pigeontest::FlutterError;
@@ -98,14 +98,20 @@
   return a_map;
 }
 
+ErrorOr<AllClassesWrapper> TestPlugin::EchoClassWrapper(
+    const AllClassesWrapper& wrapper) {
+  return wrapper;
+}
+
 ErrorOr<std::optional<std::string>> TestPlugin::ExtractNestedNullableString(
-    const AllNullableTypesWrapper& wrapper) {
-  const std::string* inner_string = wrapper.values().a_nullable_string();
+    const AllClassesWrapper& wrapper) {
+  const std::string* inner_string =
+      wrapper.all_nullable_types().a_nullable_string();
   return inner_string ? std::optional<std::string>(*inner_string)
                       : std::nullopt;
 }
 
-ErrorOr<AllNullableTypesWrapper> TestPlugin::CreateNestedNullableString(
+ErrorOr<AllClassesWrapper> TestPlugin::CreateNestedNullableString(
     const std::string* nullable_string) {
   AllNullableTypes inner_object;
   // The string pointer can't be passed through directly since the setter for
@@ -116,7 +122,7 @@
   } else {
     inner_object.set_a_nullable_string(nullptr);
   }
-  AllNullableTypesWrapper wrapper(inner_object);
+  AllClassesWrapper wrapper(inner_object);
   return wrapper;
 }
 
diff --git a/packages/pigeon/platform_tests/test_plugin/windows/test_plugin.h b/packages/pigeon/platform_tests/test_plugin/windows/test_plugin.h
index c65931d..649d20f 100644
--- a/packages/pigeon/platform_tests/test_plugin/windows/test_plugin.h
+++ b/packages/pigeon/platform_tests/test_plugin/windows/test_plugin.h
@@ -58,10 +58,13 @@
       const flutter::EncodableList& a_list) override;
   core_tests_pigeontest::ErrorOr<flutter::EncodableMap> EchoMap(
       const flutter::EncodableMap& a_map) override;
+  core_tests_pigeontest::ErrorOr<core_tests_pigeontest::AllClassesWrapper>
+  EchoClassWrapper(
+      const core_tests_pigeontest::AllClassesWrapper& wrapper) override;
   core_tests_pigeontest::ErrorOr<std::optional<std::string>>
   ExtractNestedNullableString(
-      const core_tests_pigeontest::AllNullableTypesWrapper& wrapper) override;
-  core_tests_pigeontest::ErrorOr<core_tests_pigeontest::AllNullableTypesWrapper>
+      const core_tests_pigeontest::AllClassesWrapper& wrapper) override;
+  core_tests_pigeontest::ErrorOr<core_tests_pigeontest::AllClassesWrapper>
   CreateNestedNullableString(const std::string* nullable_string) override;
   core_tests_pigeontest::ErrorOr<core_tests_pigeontest::AllNullableTypes>
   SendMultipleNullableTypes(const bool* a_nullable_bool,
diff --git a/packages/pigeon/pubspec.yaml b/packages/pigeon/pubspec.yaml
index 82531c4..3d2d6a6 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: 10.1.1 # This must match the version in lib/generator_tools.dart
+version: 10.1.2 # This must match the version in lib/generator_tools.dart
 
 environment:
   sdk: ">=2.19.0 <4.0.0"