Contributing
How do I build and test the library?
Section titled “How do I build and test the library?”The commands and what each one is for:
./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 integrationThe 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.
Why is the public API surface frozen?
Section titled “Why is the public API surface frozen?”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.
./gradlew :admob-cmp:updateKotlinAbigit add api/What does a good pull request look like?
Section titled “What does a good pull request look like?”- Has a test that fails before the change and passes after it.
- Runs both test tasks and
checkKotlinAbilocally. - 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.
What must never change?
Section titled “What must never change?”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
staticLibrariesto the cinterop.deffiles. - The rule that
AdManagerimplementations 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.
How is a release cut?
Section titled “How is a release cut?”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.