linkAndroid function

void linkAndroid(
  1. String pluginName,
  2. List<String> moduleLibs, {
  3. String baseDir = '.',
  4. List<ModuleInfo>? moduleInfos,
})

Configures android/build.gradle (or .kts) so the generated Kotlin bridge files in lib/src/generated/kotlin/ are compiled as part of the Android build.

Without the kotlin.srcDirs entry, all .bridge.g.kt files are generated but never compiled — causing "Unresolved reference: XxxJniBridge" errors at build time.

Implementation

void linkAndroid(
  String pluginName,
  List<String> moduleLibs, {
  String baseDir = '.',
  List<ModuleInfo>? moduleInfos,
}) {
  File? buildGradle;
  for (final candidate in [
    File(p.join(baseDir, 'android', 'build.gradle')),
    File(p.join(baseDir, 'android', 'build.gradle.kts')),
  ]) {
    if (candidate.existsSync()) {
      buildGradle = candidate;
      break;
    }
  }
  if (buildGradle == null) return;

  var content = buildGradle.readAsStringSync();
  bool modified = false;
  final isKts = buildGradle.path.endsWith('.kts');

  // 0. Upgrade a legacy apply-plugin build.gradle to the modern plugins{} DSL.
  final upgrade = _gradleUpgradeToPluginsDsl(content);
  content = upgrade.content;
  modified = modified || upgrade.modified;

  // 1. Ensure kotlin.srcDirs for the generated Kotlin bridges.
  final srcDirs = _gradleEnsureKotlinSrcDirs(content, isKts);
  content = srcDirs.content;
  modified = modified || srcDirs.modified;

  // 2. Ensure kotlinOptions has the expected JVM target for correct bytecode.
  if (!content.contains('kotlinOptions')) {
    final androidMatch = RegExp(r'\bandroid\s*\{').firstMatch(content);
    if (androidMatch != null) {
      content = content.replaceRange(
        androidMatch.end,
        androidMatch.end,
        '\n    kotlinOptions { jvmTarget = "${BuildVersions.androidJvmTarget}" }',
      );
      modified = true;
    }
  }

  // 3. Ensure kotlinx-coroutines (required for generated Kotlin suspend bridge functions).
  if (!content.contains('kotlinx-coroutines')) {
    final depsMatch = RegExp(r'\bdependencies\s*\{').firstMatch(content);
    if (depsMatch != null) {
      content = content.replaceRange(
        depsMatch.end,
        depsMatch.end,
        '\n    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3"\n    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3"',
      );
    } else {
      content +=
          '\ndependencies {\n    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3"\n    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3"\n}\n';
    }
    modified = true;
  }

  // 4. Ensure consumerProguardFiles "consumer-rules.pro" is wired into
  //    defaultConfig for any module using the Kotlin JNI bridge.
  final proguard = _gradleEnsureConsumerProguard(
    content,
    isKts: isKts,
    moduleInfos: moduleInfos,
    baseDir: baseDir,
  );
  content = proguard.content;
  modified = modified || proguard.modified;

  if (modified) buildGradle.writeAsStringSync(content);
}