[jnigen] Clean up interface implementation using auxiliary generated classes (https://github.com/dart-lang/jnigen/issues/349)

* Use impl classes for implementing interfaces
diff --git a/pkgs/jnigen/lib/src/bindings/dart_generator.dart b/pkgs/jnigen/lib/src/bindings/dart_generator.dart
index c78a3b2..ab710f6 100644
--- a/pkgs/jnigen/lib/src/bindings/dart_generator.dart
+++ b/pkgs/jnigen/lib/src/bindings/dart_generator.dart
@@ -15,11 +15,11 @@
 import 'resolver.dart';
 import 'visitor.dart';
 
-// Import prefixes
+// Import prefixes.
 const _jni = 'jni';
 const _ffi = 'ffi';
 
-// package:jni types
+// package:jni types.
 const _jType = '$_jni.JObjType';
 const _jPointer = '$_jni.JObjectPtr';
 const _jArray = '$_jni.JArray';
@@ -27,10 +27,10 @@
 const _jResult = '$_jni.JniResult';
 const _jCallType = '$_jni.JniCallType';
 
-// package:ffi types
+// package:ffi types.
 const _voidPointer = '$_ffi.Pointer<$_ffi.Void>';
 
-// Prefixes and suffixes
+// Prefixes and suffixes.
 const _typeParamPrefix = '\$';
 
 // Misc.
@@ -41,7 +41,7 @@
 const _lookup = 'jniLookup';
 const _selfPointer = 'reference';
 
-// Docs
+// Docs.
 const _deleteInstruction =
     '  /// The returned object must be deleted after use, '
     'by calling the `delete` method.';
@@ -282,7 +282,7 @@
   }
 }
 
-/// Generates the Dart class definition, type class, and the array extension
+/// Generates the Dart class definition, type class, and the array extension.
 class _ClassGenerator extends Visitor<ClassDecl, void> {
   final Config config;
   final StringSink s;
@@ -302,13 +302,14 @@
     final isDartOnly =
         config.outputConfig.bindingsType == BindingsType.dartOnly;
 
-    // Docs
+    // Docs.
     s.write('/// from: ${node.binaryName}\n');
     node.javadoc?.accept(_DocGenerator(s, depth: 0));
 
-    // Class definition
+    // Class definition.
     final name = node.finalName;
     final superName = node.superclass!.accept(_TypeGenerator(resolver));
+    final implClassName = '\$${name}Impl';
     final typeParamsDef = _encloseIfNotEmpty(
       '<',
       node.allTypeParams
@@ -328,7 +329,7 @@
       typeParams.join(', '),
       ')',
     );
-    final typeClassDefinitions = typeParams
+    final typeClassesDef = typeParams
         .map((typeParam) =>
             'final $_jType<$_typeParamPrefix$typeParam> $typeParam;')
         .join(_newLine(depth: 1));
@@ -347,7 +348,7 @@
   @override
   late final $_jType<$name$typeParamsCall> $instanceTypeGetter = $staticTypeGetter$staticTypeGetterCallArgs;
 
-  $typeClassDefinitions
+  $typeClassesDef
 
   $name.fromRef(
     $ctorTypeClassesDef
@@ -367,7 +368,7 @@
 ''');
     }
 
-    // Static TypeClass getter
+    // Static TypeClass getter.
     s.writeln(
         '  /// The type which includes information such as the signature of this class.');
     final typeClassName = node.typeClassName;
@@ -402,29 +403,24 @@
       method.accept(methodGenerator);
     }
 
-    // Experimental: Interface implementation
+    // Experimental: Interface implementation.
     if (node.declKind == DeclKind.interfaceKind &&
         (config.experiments?.contains(Experiment.interfaceImplementation) ??
             false)) {
       s.write('''
-  /// Maps a specific port to the implemented methods.
-  static final Map<int, Map<String, Function>> _\$methods = {};
-
-  /// Maps a specific port to the type parameters.
-  static final Map<int, Map<String, $_jType>> _\$types = {};
+  /// Maps a specific port to the implemented interface.
+  static final Map<int, $implClassName> _\$impls = {};
 
   ReceivePort? _\$p;
 
   static final Finalizer<ReceivePort> _\$finalizer = Finalizer((\$p) {
-    _\$methods.remove(\$p.sendPort.nativePort);
-    _\$types.remove(\$p.sendPort.nativePort);
+    _\$impls.remove(\$p.sendPort.nativePort);
     \$p.close();
   });
 
   @override
   void delete() {
-    _\$methods.remove(_\$p?.sendPort.nativePort);
-    _\$types.remove(_\$p?.sendPort.nativePort);
+    _\$impls.remove(_\$p?.sendPort.nativePort);
     _\$p?.close();
     _\$finalizer.detach(this);
     super.delete();
@@ -467,21 +463,13 @@
   }
 
   factory $name.implement(
+    $implClassName$typeParamsCall \$impl,
+  ) {
 ''');
-      final typeClassesCall =
-          typeParams.map((typeParam) => '$typeParam,').join(_newLine(depth: 3));
-      s.write(_encloseIfNotEmpty(
-        '{',
-        [
-          ...typeParams
-              .map((typeParam) => 'required $_jType<\$$typeParam> $typeParam,'),
-          ...node.methods.accept(_InterfaceImplementArg(resolver))
-        ].join(_newLine(depth: 2)),
-        '}',
-      ));
-
+      final typeClassesCall = typeParams
+          .map((typeParam) => '\$impl.$typeParam,')
+          .join(_newLine(depth: 3));
       s.write('''
-      ) {
     final \$p = ReceivePort();
     final \$x = $name.fromRef(
       $typeClassesCall
@@ -492,17 +480,8 @@
       ),
     ).._\$p = \$p;
     final \$a = \$p.sendPort.nativePort; 
-    _\$types[\$a] = {};
-    _\$methods[\$a] = {};
+    _\$impls[\$a] = \$impl;
 ''');
-      final typeFiller = _InterfaceTypesFiller(s);
-      for (final typeParam in node.allTypeParams) {
-        typeParam.accept(typeFiller);
-      }
-      final methodFiller = _InterfaceMethodsFiller(s);
-      for (final method in node.methods) {
-        method.accept(methodFiller);
-      }
       s.write('''
     _\$finalizer.attach(\$x, \$p, detach: \$x);
     \$p.listen((\$m) {
@@ -515,10 +494,87 @@
   ''');
     }
 
-    // End of Class definition
+    // End of Class definition.
     s.writeln('}');
 
-    // TypeClass definition
+    // Experimental: Interface implementation
+    // Abstract and concrete Impl class definition.
+    // Used for interface implementation.
+    if (node.declKind == DeclKind.interfaceKind &&
+        (config.experiments?.contains(Experiment.interfaceImplementation) ??
+            false)) {
+      // Abstract Impl class.
+      final typeClassGetters = typeParams
+          .map((typeParam) =>
+              '$_jType<$_typeParamPrefix$typeParam> get $typeParam;')
+          .join(_newLine(depth: 1));
+      final abstractFactoryArgs = _encloseIfNotEmpty(
+        '{',
+        [
+          ...typeParams
+              .map((typeParam) => 'required $_jType<\$$typeParam> $typeParam,'),
+          ...node.methods.accept(_ConcreteImplClosureCtorArg(resolver)),
+        ].join(_newLine(depth: 2)),
+        '}',
+      );
+      s.write('''
+abstract class $implClassName$typeParamsDef {
+  factory $implClassName(
+    $abstractFactoryArgs
+  ) = _$implClassName;
+
+  $typeClassGetters
+
+''');
+      final abstractImplMethod = _AbstractImplMethod(resolver, s);
+      for (final method in node.methods) {
+        method.accept(abstractImplMethod);
+      }
+      s.writeln('}');
+
+      // Concrete Impl class.
+      // This is for passing closures instead of implementing the class.
+      final concreteCtorArgs = _encloseIfNotEmpty(
+        '{',
+        [
+          ...typeParams.map((typeParam) => 'required this.$typeParam,'),
+          ...node.methods.accept(_ConcreteImplClosureCtorArg(resolver)),
+        ].join(_newLine(depth: 2)),
+        '}',
+      );
+      final setClosures = _encloseIfNotEmpty(
+        ' :  ',
+        node.methods
+            .map((method) => '_${method.finalName} = ${method.finalName}')
+            .join(', '),
+        '',
+      );
+      final typeClassesDef = typeParams.map((typeParam) => '''
+@override
+final $_jType<\$$typeParam> $typeParam;
+''').join(_newLine(depth: 1));
+      s.write('''
+
+class _$implClassName$typeParamsDef implements $implClassName$typeParamsCall {
+  _$implClassName(
+    $concreteCtorArgs
+  )$setClosures;
+
+  $typeClassesDef
+
+''');
+      final concreteClosureDef = _ConcreteImplClosureDef(resolver, s);
+      for (final method in node.methods) {
+        method.accept(concreteClosureDef);
+      }
+      s.writeln();
+      final concreteMethodDef = _ConcreteImplMethod(resolver, s);
+      for (final method in node.methods) {
+        method.accept(concreteMethodDef);
+      }
+      s.writeln('}');
+    }
+    // TypeClass definition.
     final typeClassesCall =
         typeParams.map((typeParam) => '$typeParam,').join(_newLine(depth: 2));
     final signature = node.signature;
@@ -532,7 +588,7 @@
         : 'Object.hash($typeClassName, $hashCodeTypeClasses)';
     s.write('''
 class $typeClassName$typeParamsDef extends $_jType<$name$typeParamsCall> {
-  $typeClassDefinitions
+  $typeClassesDef
 
   const $typeClassName(
     $ctorTypeClassesDef
@@ -704,7 +760,7 @@
     final innerTypeClass = node.type.accept(_TypeClassGenerator(
       resolver,
       isConst: false,
-      boxPrimitives: boxPrimitives,
+      boxPrimitives: false,
       typeVarFromMap: typeVarFromMap,
     ));
     final ifConst = innerTypeClass.canBeConst && isConst ? 'const ' : '';
@@ -724,7 +780,7 @@
     final definedTypeClasses = node.params.accept(_TypeClassGenerator(
       resolver,
       isConst: false,
-      boxPrimitives: boxPrimitives,
+      boxPrimitives: false,
       typeVarFromMap: typeVarFromMap,
     ));
 
@@ -773,7 +829,7 @@
   @override
   _TypeClass visitTypeVar(TypeVar node) {
     if (typeVarFromMap) {
-      return _TypeClass('_\$types[\$p]!["${node.name}"]!', false);
+      return _TypeClass('_\$impls[\$p]!.${node.name}', false);
     }
     return _TypeClass(node.name, false);
   }
@@ -1026,10 +1082,10 @@
       }
     }
 
-    // Accessors
+    // Accessors.
     (isCBased ? writeCAccessor : writeDartOnlyAccessor)(node);
 
-    // Getter docs
+    // Getter docs.
     writeDocs(node, writeDeleteInstructions: true);
 
     final name = node.finalName;
@@ -1041,7 +1097,7 @@
     ));
     s.writeln(';\n');
     if (!node.isFinal) {
-      // Setter docs
+      // Setter docs.
       writeDocs(node, writeDeleteInstructions: true);
 
       s.write('${ifStatic}set $name($type value) => ');
@@ -1510,11 +1566,44 @@
   }
 }
 
-/// The argument for .implement method of interfaces.
-class _InterfaceImplementArg extends Visitor<Method, String> {
+/// Method defintion for Impl abstract class used for interface implementation.
+class _AbstractImplMethod extends Visitor<Method, void> {
+  final Resolver resolver;
+  final StringSink s;
+
+  _AbstractImplMethod(this.resolver, this.s);
+
+  @override
+  void visit(Method node) {
+    final returnType = node.returnType.accept(_TypeGenerator(resolver));
+    final name = node.finalName;
+    final args = node.params.accept(_ParamDef(resolver)).join(', ');
+    s.writeln('  $returnType $name($args);');
+  }
+}
+
+/// Closure defintion for concrete Impl class used for interface implementation.
+class _ConcreteImplClosureDef extends Visitor<Method, void> {
+  final Resolver resolver;
+  final StringSink s;
+
+  _ConcreteImplClosureDef(this.resolver, this.s);
+
+  @override
+  void visit(Method node) {
+    final returnType = node.returnType.accept(_TypeGenerator(resolver));
+    final name = node.finalName;
+    final args = node.params.accept(_ParamDef(resolver)).join(', ');
+    s.writeln('  final $returnType Function($args) _$name;');
+  }
+}
+
+/// Closure argument for concrete Impl class constructor.
+/// Used for interface implementation.
+class _ConcreteImplClosureCtorArg extends Visitor<Method, String> {
   final Resolver resolver;
 
-  _InterfaceImplementArg(this.resolver);
+  _ConcreteImplClosureCtorArg(this.resolver);
 
   @override
   String visit(Method node) {
@@ -1525,6 +1614,26 @@
   }
 }
 
+/// Method defintion for concrete Impl class used for interface implementation.
+class _ConcreteImplMethod extends Visitor<Method, void> {
+  final Resolver resolver;
+  final StringSink s;
+
+  _ConcreteImplMethod(this.resolver, this.s);
+
+  @override
+  void visit(Method node) {
+    final returnType = node.returnType.accept(_TypeGenerator(resolver));
+    final name = node.finalName;
+    final argsDef = node.params.accept(_ParamDef(resolver)).join(', ');
+    final argsCall = node.params.map((param) => param.finalName).join(', ');
+    s.write('''
+  $returnType $name($argsDef) {
+    return _$name($argsCall);
+  }''');
+  }
+}
+
 /// The if statement to check which method has been called from the proxy class.
 class _InterfaceMethodIf extends Visitor<Method, void> {
   final Resolver resolver;
@@ -1537,9 +1646,10 @@
     final isVoid = node.returnType.name == 'void';
     final signature = node.javaSig;
     final saveResult = isVoid ? '' : 'final \$r = ';
+    final name = node.finalName;
     s.write('''
       if (\$d == r"$signature") {
-        ${saveResult}_\$methods[\$p]![\$d]!(
+        ${saveResult}_\$impls[\$p]!.$name(
 ''');
     for (var i = 0; i < node.params.length; ++i) {
       node.params[i].accept(_InterfaceParamCast(resolver, s, paramIndex: i));
@@ -1618,33 +1728,3 @@
     return '($_jni.J${node.boxedName}(\$r)..setAsDeleted()).reference';
   }
 }
-
-/// Fills the static _$types map with the correct type classes for the given
-/// port.
-class _InterfaceTypesFiller extends Visitor<TypeParam, void> {
-  final StringSink s;
-
-  _InterfaceTypesFiller(this.s);
-
-  @override
-  void visit(TypeParam node) {
-    s.write('''
-    _\$types[\$a]!["${node.name}"] = ${node.name};
-''');
-  }
-}
-
-/// Fills the static _$method map with the correct callbacks for the given
-/// port.
-class _InterfaceMethodsFiller extends Visitor<Method, void> {
-  final StringSink s;
-
-  _InterfaceMethodsFiller(this.s);
-
-  @override
-  void visit(Method node) {
-    s.write('''
-    _\$methods[\$a]![r"${node.javaSig}"] = ${node.finalName};
-''');
-  }
-}
diff --git a/pkgs/jnigen/test/simple_package_test/c_based/dart_bindings/simple_package.dart b/pkgs/jnigen/test/simple_package_test/c_based/dart_bindings/simple_package.dart
index 11e873e..27f3962 100644
--- a/pkgs/jnigen/test/simple_package_test/c_based/dart_bindings/simple_package.dart
+++ b/pkgs/jnigen/test/simple_package_test/c_based/dart_bindings/simple_package.dart
@@ -2919,24 +2919,19 @@
     return _manyPrimitives(reference, a, b ? 1 : 0, c, d).long;
   }
 
-  /// Maps a specific port to the implemented methods.
-  static final Map<int, Map<String, Function>> _$methods = {};
-
-  /// Maps a specific port to the type parameters.
-  static final Map<int, Map<String, jni.JObjType>> _$types = {};
+  /// Maps a specific port to the implemented interface.
+  static final Map<int, $MyInterfaceImpl> _$impls = {};
 
   ReceivePort? _$p;
 
   static final Finalizer<ReceivePort> _$finalizer = Finalizer(($p) {
-    _$methods.remove($p.sendPort.nativePort);
-    _$types.remove($p.sendPort.nativePort);
+    _$impls.remove($p.sendPort.nativePort);
     $p.close();
   });
 
   @override
   void delete() {
-    _$methods.remove(_$p?.sendPort.nativePort);
-    _$types.remove(_$p?.sendPort.nativePort);
+    _$impls.remove(_$p?.sendPort.nativePort);
     _$p?.close();
     _$finalizer.detach(this);
     super.delete();
@@ -2970,13 +2965,13 @@
     final $d = $i.methodDescriptor.toDartString(deleteOriginal: true);
     final $a = $i.args;
     if ($d == r"voidCallback(Ljava/lang/String;)V") {
-      _$methods[$p]![$d]!(
+      _$impls[$p]!.voidCallback(
         $a[0].castTo(const jni.JStringType(), deleteOriginal: true),
       );
       return jni.nullptr;
     }
     if ($d == r"stringCallback(Ljava/lang/String;)Ljava/lang/String;") {
-      final $r = _$methods[$p]![$d]!(
+      final $r = _$impls[$p]!.stringCallback(
         $a[0].castTo(const jni.JStringType(), deleteOriginal: true),
       );
       return (($r as jni.JObject).castTo(const jni.JObjectType())
@@ -2984,15 +2979,15 @@
           .reference;
     }
     if ($d == r"varCallback(Ljava/lang/Object;)Ljava/lang/Object;") {
-      final $r = _$methods[$p]![$d]!(
-        $a[0].castTo(_$types[$p]!["T"]!, deleteOriginal: true),
+      final $r = _$impls[$p]!.varCallback(
+        $a[0].castTo(_$impls[$p]!.T, deleteOriginal: true),
       );
       return (($r as jni.JObject).castTo(const jni.JObjectType())
             ..setAsDeleted())
           .reference;
     }
     if ($d == r"manyPrimitives(IZCD)J") {
-      final $r = _$methods[$p]![$d]!(
+      final $r = _$impls[$p]!.manyPrimitives(
         $a[0]
             .castTo(const jni.JIntegerType(), deleteOriginal: true)
             .intValue(deleteOriginal: true),
@@ -3011,16 +3006,12 @@
     return jni.nullptr;
   }
 
-  factory MyInterface.implement({
-    required jni.JObjType<$T> T,
-    required void Function(jni.JString s) voidCallback,
-    required jni.JString Function(jni.JString s) stringCallback,
-    required $T Function($T t) varCallback,
-    required int Function(int a, bool b, int c, double d) manyPrimitives,
-  }) {
+  factory MyInterface.implement(
+    $MyInterfaceImpl<$T> $impl,
+  ) {
     final $p = ReceivePort();
     final $x = MyInterface.fromRef(
-      T,
+      $impl.T,
       ProtectedJniExtensions.newPortProxy(
         r"com.github.dart_lang.jnigen.interfaces.MyInterface",
         $p,
@@ -3028,15 +3019,7 @@
       ),
     ).._$p = $p;
     final $a = $p.sendPort.nativePort;
-    _$types[$a] = {};
-    _$methods[$a] = {};
-    _$types[$a]!["T"] = T;
-    _$methods[$a]![r"voidCallback(Ljava/lang/String;)V"] = voidCallback;
-    _$methods[$a]![r"stringCallback(Ljava/lang/String;)Ljava/lang/String;"] =
-        stringCallback;
-    _$methods[$a]![r"varCallback(Ljava/lang/Object;)Ljava/lang/Object;"] =
-        varCallback;
-    _$methods[$a]![r"manyPrimitives(IZCD)J"] = manyPrimitives;
+    _$impls[$a] = $impl;
     _$finalizer.attach($x, $p, detach: $x);
     $p.listen(($m) {
       final $i = $MethodInvocation.fromMessage($m);
@@ -3047,6 +3030,61 @@
   }
 }
 
+abstract class $MyInterfaceImpl<$T extends jni.JObject> {
+  factory $MyInterfaceImpl({
+    required jni.JObjType<$T> T,
+    required void Function(jni.JString s) voidCallback,
+    required jni.JString Function(jni.JString s) stringCallback,
+    required $T Function($T t) varCallback,
+    required int Function(int a, bool b, int c, double d) manyPrimitives,
+  }) = _$MyInterfaceImpl;
+
+  jni.JObjType<$T> get T;
+
+  void voidCallback(jni.JString s);
+  jni.JString stringCallback(jni.JString s);
+  $T varCallback($T t);
+  int manyPrimitives(int a, bool b, int c, double d);
+}
+
+class _$MyInterfaceImpl<$T extends jni.JObject>
+    implements $MyInterfaceImpl<$T> {
+  _$MyInterfaceImpl({
+    required this.T,
+    required void Function(jni.JString s) voidCallback,
+    required jni.JString Function(jni.JString s) stringCallback,
+    required $T Function($T t) varCallback,
+    required int Function(int a, bool b, int c, double d) manyPrimitives,
+  })  : _voidCallback = voidCallback,
+        _stringCallback = stringCallback,
+        _varCallback = varCallback,
+        _manyPrimitives = manyPrimitives;
+
+  @override
+  final jni.JObjType<$T> T;
+
+  final void Function(jni.JString s) _voidCallback;
+  final jni.JString Function(jni.JString s) _stringCallback;
+  final $T Function($T t) _varCallback;
+  final int Function(int a, bool b, int c, double d) _manyPrimitives;
+
+  void voidCallback(jni.JString s) {
+    return _voidCallback(s);
+  }
+
+  jni.JString stringCallback(jni.JString s) {
+    return _stringCallback(s);
+  }
+
+  $T varCallback($T t) {
+    return _varCallback(t);
+  }
+
+  int manyPrimitives(int a, bool b, int c, double d) {
+    return _manyPrimitives(a, b, c, d);
+  }
+}
+
 class $MyInterfaceType<$T extends jni.JObject>
     extends jni.JObjType<MyInterface<$T>> {
   final jni.JObjType<$T> T;
diff --git a/pkgs/jnigen/test/simple_package_test/dart_only/dart_bindings/simple_package.dart b/pkgs/jnigen/test/simple_package_test/dart_only/dart_bindings/simple_package.dart
index ef24ce3..9d016f7 100644
--- a/pkgs/jnigen/test/simple_package_test/dart_only/dart_bindings/simple_package.dart
+++ b/pkgs/jnigen/test/simple_package_test/dart_only/dart_bindings/simple_package.dart
@@ -2758,24 +2758,19 @@
         [jni.JValueInt(a), b ? 1 : 0, jni.JValueChar(c), d]).long;
   }
 
-  /// Maps a specific port to the implemented methods.
-  static final Map<int, Map<String, Function>> _$methods = {};
-
-  /// Maps a specific port to the type parameters.
-  static final Map<int, Map<String, jni.JObjType>> _$types = {};
+  /// Maps a specific port to the implemented interface.
+  static final Map<int, $MyInterfaceImpl> _$impls = {};
 
   ReceivePort? _$p;
 
   static final Finalizer<ReceivePort> _$finalizer = Finalizer(($p) {
-    _$methods.remove($p.sendPort.nativePort);
-    _$types.remove($p.sendPort.nativePort);
+    _$impls.remove($p.sendPort.nativePort);
     $p.close();
   });
 
   @override
   void delete() {
-    _$methods.remove(_$p?.sendPort.nativePort);
-    _$types.remove(_$p?.sendPort.nativePort);
+    _$impls.remove(_$p?.sendPort.nativePort);
     _$p?.close();
     _$finalizer.detach(this);
     super.delete();
@@ -2809,13 +2804,13 @@
     final $d = $i.methodDescriptor.toDartString(deleteOriginal: true);
     final $a = $i.args;
     if ($d == r"voidCallback(Ljava/lang/String;)V") {
-      _$methods[$p]![$d]!(
+      _$impls[$p]!.voidCallback(
         $a[0].castTo(const jni.JStringType(), deleteOriginal: true),
       );
       return jni.nullptr;
     }
     if ($d == r"stringCallback(Ljava/lang/String;)Ljava/lang/String;") {
-      final $r = _$methods[$p]![$d]!(
+      final $r = _$impls[$p]!.stringCallback(
         $a[0].castTo(const jni.JStringType(), deleteOriginal: true),
       );
       return (($r as jni.JObject).castTo(const jni.JObjectType())
@@ -2823,15 +2818,15 @@
           .reference;
     }
     if ($d == r"varCallback(Ljava/lang/Object;)Ljava/lang/Object;") {
-      final $r = _$methods[$p]![$d]!(
-        $a[0].castTo(_$types[$p]!["T"]!, deleteOriginal: true),
+      final $r = _$impls[$p]!.varCallback(
+        $a[0].castTo(_$impls[$p]!.T, deleteOriginal: true),
       );
       return (($r as jni.JObject).castTo(const jni.JObjectType())
             ..setAsDeleted())
           .reference;
     }
     if ($d == r"manyPrimitives(IZCD)J") {
-      final $r = _$methods[$p]![$d]!(
+      final $r = _$impls[$p]!.manyPrimitives(
         $a[0]
             .castTo(const jni.JIntegerType(), deleteOriginal: true)
             .intValue(deleteOriginal: true),
@@ -2850,16 +2845,12 @@
     return jni.nullptr;
   }
 
-  factory MyInterface.implement({
-    required jni.JObjType<$T> T,
-    required void Function(jni.JString s) voidCallback,
-    required jni.JString Function(jni.JString s) stringCallback,
-    required $T Function($T t) varCallback,
-    required int Function(int a, bool b, int c, double d) manyPrimitives,
-  }) {
+  factory MyInterface.implement(
+    $MyInterfaceImpl<$T> $impl,
+  ) {
     final $p = ReceivePort();
     final $x = MyInterface.fromRef(
-      T,
+      $impl.T,
       ProtectedJniExtensions.newPortProxy(
         r"com.github.dart_lang.jnigen.interfaces.MyInterface",
         $p,
@@ -2867,15 +2858,7 @@
       ),
     ).._$p = $p;
     final $a = $p.sendPort.nativePort;
-    _$types[$a] = {};
-    _$methods[$a] = {};
-    _$types[$a]!["T"] = T;
-    _$methods[$a]![r"voidCallback(Ljava/lang/String;)V"] = voidCallback;
-    _$methods[$a]![r"stringCallback(Ljava/lang/String;)Ljava/lang/String;"] =
-        stringCallback;
-    _$methods[$a]![r"varCallback(Ljava/lang/Object;)Ljava/lang/Object;"] =
-        varCallback;
-    _$methods[$a]![r"manyPrimitives(IZCD)J"] = manyPrimitives;
+    _$impls[$a] = $impl;
     _$finalizer.attach($x, $p, detach: $x);
     $p.listen(($m) {
       final $i = $MethodInvocation.fromMessage($m);
@@ -2886,6 +2869,61 @@
   }
 }
 
+abstract class $MyInterfaceImpl<$T extends jni.JObject> {
+  factory $MyInterfaceImpl({
+    required jni.JObjType<$T> T,
+    required void Function(jni.JString s) voidCallback,
+    required jni.JString Function(jni.JString s) stringCallback,
+    required $T Function($T t) varCallback,
+    required int Function(int a, bool b, int c, double d) manyPrimitives,
+  }) = _$MyInterfaceImpl;
+
+  jni.JObjType<$T> get T;
+
+  void voidCallback(jni.JString s);
+  jni.JString stringCallback(jni.JString s);
+  $T varCallback($T t);
+  int manyPrimitives(int a, bool b, int c, double d);
+}
+
+class _$MyInterfaceImpl<$T extends jni.JObject>
+    implements $MyInterfaceImpl<$T> {
+  _$MyInterfaceImpl({
+    required this.T,
+    required void Function(jni.JString s) voidCallback,
+    required jni.JString Function(jni.JString s) stringCallback,
+    required $T Function($T t) varCallback,
+    required int Function(int a, bool b, int c, double d) manyPrimitives,
+  })  : _voidCallback = voidCallback,
+        _stringCallback = stringCallback,
+        _varCallback = varCallback,
+        _manyPrimitives = manyPrimitives;
+
+  @override
+  final jni.JObjType<$T> T;
+
+  final void Function(jni.JString s) _voidCallback;
+  final jni.JString Function(jni.JString s) _stringCallback;
+  final $T Function($T t) _varCallback;
+  final int Function(int a, bool b, int c, double d) _manyPrimitives;
+
+  void voidCallback(jni.JString s) {
+    return _voidCallback(s);
+  }
+
+  jni.JString stringCallback(jni.JString s) {
+    return _stringCallback(s);
+  }
+
+  $T varCallback($T t) {
+    return _varCallback(t);
+  }
+
+  int manyPrimitives(int a, bool b, int c, double d) {
+    return _manyPrimitives(a, b, c, d);
+  }
+}
+
 class $MyInterfaceType<$T extends jni.JObject>
     extends jni.JObjType<MyInterface<$T>> {
   final jni.JObjType<$T> T;
diff --git a/pkgs/jnigen/test/simple_package_test/runtime_test_registrant.dart b/pkgs/jnigen/test/simple_package_test/runtime_test_registrant.dart
index cd6f999..1c40768 100644
--- a/pkgs/jnigen/test/simple_package_test/runtime_test_registrant.dart
+++ b/pkgs/jnigen/test/simple_package_test/runtime_test_registrant.dart
@@ -543,28 +543,31 @@
         // or `self` argument for each one of the callbacks.
         late final MyInterface<JInteger> myInterface;
         myInterface = MyInterface.implement(
-          voidCallback: (s) {
-            voidCallbackResult.complete(s);
-          },
-          stringCallback: (s) {
-            return (s.toDartString(deleteOriginal: true) * 2).toJString();
-          },
-          varCallback: (JInteger t) {
-            final result = (t.intValue(deleteOriginal: true) * 2).toJInteger();
-            varCallbackResult.complete(result);
-            return result;
-          },
-          manyPrimitives: (a, b, c, d) {
-            if (b) {
-              final result = a + c + d.toInt();
-              manyPrimitivesResult.complete(result);
+          $MyInterfaceImpl(
+            voidCallback: (s) {
+              voidCallbackResult.complete(s);
+            },
+            stringCallback: (s) {
+              return (s.toDartString(deleteOriginal: true) * 2).toJString();
+            },
+            varCallback: (JInteger t) {
+              final result =
+                  (t.intValue(deleteOriginal: true) * 2).toJInteger();
+              varCallbackResult.complete(result);
               return result;
-            } else {
-              // Call self, add to [a] when [b] is false and change b to true.
-              return myInterface.manyPrimitives(a + 1, true, c, d);
-            }
-          },
-          T: JInteger.type,
+            },
+            manyPrimitives: (a, b, c, d) {
+              if (b) {
+                final result = a + c + d.toInt();
+                manyPrimitivesResult.complete(result);
+                return result;
+              } else {
+                // Call self, add to [a] when [b] is false and change b to true.
+                return myInterface.manyPrimitives(a + 1, true, c, d);
+              }
+            },
+            T: JInteger.type,
+          ),
         );
         // [stringCallback] is going to be called first using [s].
         // The result of it is going to be used as the argument for