Skip to content

Contributing

The commands and what each one is for:

Terminal window
./gradlew :admob-cmp:iosSimulatorArm64Test # common tests, iOS runner
./gradlew :admob-cmp:testAndroidHostTest # common tests, JVM runner
./gradlew :admob-cmp:checkKotlinAbi # public API surface check
./gradlew :admob-cmp:updateKotlinAbi # regenerate api/ after an intentional API change
./gradlew :admob-cmp-core:doctorIos # diagnose iOS consumer integration

The test design so contributions match it: tests live in commonTest only and use hand-written fakes rather than a mocking framework, with injectable clock and foregroundEvents seams so time-dependent and lifecycle-dependent behaviour is testable without a device. That is why the same tests run on both the iOS and JVM runners.

A note on what commonTest does not give you. Native behaviour — the part of the code that calls into GMA — is not exercised by these tests. A test that asserts “the SDK is called with the right ad unit id” needs a fake at the platform boundary, which the hand-written fakes provide. A test that asserts “the SDK returns this ad” needs a real device or a platform-specific mock, neither of which is in the test suite. The test suite is a contract test, not an integration test, and its purpose is to lock the public behaviour of the library’s common code.

The mechanism and the reason together: the module uses explicitApi() plus Kotlin Gradle plugin ABI validation, and api/admob-cmp.klib.api is committed. Any change to the public surface changes that file, so a reviewer sees the API diff in the pull request rather than discovering it after publication. Multiple versions are published and consumed, so a breaking change is not free.

Terminal window
./gradlew :admob-cmp:updateKotlinAbi
git add api/
  • Has a test that fails before the change and passes after it.
  • Runs both test tasks and checkKotlinAbi locally.
  • Ships its documentation in the same pull request. Docs live in this repository at docs-site/src/content/docs/, so an API change and its guide update version together — that is the entire reason the docs site is not a separate repository.
  • Updates /reference/changelog/ when it changes behaviour a consumer can observe.
  • Keeps the trademark and neutrality rules intact: nominative use of “AdMob” only, and no comparative claims about other projects beyond verifiable capability facts.

The “ships its documentation in the same pull request” line deserves emphasis. The docs site is intentionally co-located with the library so an API change cannot ship without its guide update. A change to AdPlacement requires a change to the relevant guide; a change to a controller surface requires a change to the relevant format page; a change to the public ABI requires a regenerated api/*.klib.api dump. The ./scripts/release-readiness.sh script bundles these checks and is the only verification step in the project.

The hard invariants, stated so a contributor does not propose them in good faith:

  • The published Maven coordinate dev.avinya.ads:admob-cmp.
  • The frozen public ABI, absent a deliberate major version.
  • The bindings-only iOS distribution — never add staticLibraries to the cinterop .def files.
  • The rule that AdManager implementations are not constructed directly by consumers.

The first three are the published promises to every consumer. The fourth is the implementation rule that keeps the rest of the public API honest. The architecture reference has the full rationale for each.

A practical note on the “never construct AdManager” rule. The library exposes a single manager instance through rememberAdManager() in Compose and AdMob.manager(context) on Android. A contribution that adds a public AdManager constructor or factory would break the singleton invariant and introduce a state-mismatch surface the library does not have to defend. The rule is enforceable in code review, but the intent is to prevent well-meaning additions that look harmless and quietly double the manager.

Short and routed to the maintainer guide rather than duplicated: releases bump VERSION_NAME in gradle.properties, publish to Maven Central through the repository’s publish workflow, and must update /reference/changelog/ in the same pull request. The full maintainer procedure lives in admob-cmp/docs/PUBLISHING.md in the repository, and the changelog page is the user-facing record.

A note on the version bump. The same VERSION_NAME lives in two files — the root gradle.properties and admob-cmp-gradle-plugin/gradle.properties — and both must move in lockstep. The publish script asserts this and refuses to run if the two disagree, so a forgotten half of the bump is a build failure rather than a release with mismatched versions. The version-catalog form described in Installation is the right shape for consumers; the two-file form is the right shape for the library.