Imported from med-united/popp-module (
AGENTS.md). Install upstream withnpx skills add med-united/popp-module. Copyright stays with the author.
AGENTS.md
This file provides guidance to AI agents working with code in this repository.
Project purpose
Implementation of the gematik PoPP-Module specification (https://gemspec.gematik.de/prereleases/Draft_PoPP_26_1/) as a Kotlin Multiplatform project with Android and iOS targets. The business logic lives in a reusable :popp-sdk library (exported as an Android AAR and an iOS XCFramework) so it can be embedded into arbitrary host apps. The popp-demo/ folder holds the demo: a common UI library (popp-demo:shared) plus two host-app demos (a 3rd-party app and an insurance app) that each consume the SDK.
Build & test commands
The Gradle daemon auto-provisions JDK 21 (Amazon Corretto) via gradle/gradle-daemon-jvm.properties, but Kotlin/JVM compilation targets JVM 11. Configuration cache and build cache are enabled in gradle.properties.
- Build everything:
./gradlew build - 3rd-party demo Android app:
./gradlew :popp-demo:popp-3rd-party-app-demo:android3rdPartyApp:installDebugthenadb shell monkey -p de.servicehealth.poppmodule.demo.thirdparty -c android.intent.category.LAUNCHER 1 - Insurance demo Android app:
./gradlew :popp-demo:popp-insurance-app-demo:androidInsuranceApp:installDebugthenadb shell monkey -p de.servicehealth.poppmodule.demo.insurance -c android.intent.category.LAUNCHER 1 - Run SDK tests (Android host JVM, fast):
./gradlew :popp-sdk:testAndroidHostTest - Run SDK tests (iOS simulator, requires macOS + simulator):
./gradlew :popp-sdk:iosSimulatorArm64Test - Run a single test class:
./gradlew :popp-sdk:testAndroidHostTest --tests "de.servicehealth.poppmodule.sdk.PoppSdkTest" - Code coverage (aggregated over
:popp-sdk+:popp-demo:shared):./gradlew :popp-sdk:testAndroidHostTest :popp-demo:shared:testAndroidHostTest :koverXmlReport :koverHtmlReport(output underbuild/reports/kover/) - Build the SDK Android AAR:
./gradlew :popp-sdk:assemble(output underpopp-sdk/build/outputs/aar/) - Build the SDK iOS XCFramework:
./gradlew :popp-sdk:assemblePoppSdkXCFramework(output underpopp-sdk/build/XCFrameworks/) - iOS apps: open the relevant Xcode project, e.g.
popp-demo/popp-3rd-party-app-demo/ios3rdPartyApp/iosApp.xcodeproj
The iOS test task name is iosSimulatorArm64Test (not the older iosTest) because only iosArm64() and iosSimulatorArm64() are declared — there is intentionally no iosX64() target.
Architecture
One SDK module plus a popp-demo/ group (common lib + two host-app demos):
:popp-sdk— Kotlin Multiplatform library holding the PoPP business logic, with no Compose/UI dependencies so any host app (Compose, SwiftUI/UIKit, View-based, backend) can consume it. New AGP 9 KMP plugin (com.android.kotlin.multiplatform.library). Public APIPoppSdk; produces an Android AAR + static iOS XCFrameworkPoppSdk. Namespacede.servicehealth.poppmodule.sdk. The Android NFC eGK channel (…sdk.egk.nfc.EgkNfcChannel, POPPM-119) implementsEgkApduChannelvia PACE + secure messaging; the card stack under…egk.nfc.internalis ported from gematik's E-Rezept-App-Android (EUPL-1.2 → GPLv2, seeNOTICE.md) and depends on BouncyCastle (androidMain only).:popp-demo:shared— Compose Multiplatform library: common demo UI (service·health brand theme in…/theme,BrandShowcaseScreenin…/demo, which renders the SDK-integration proof) + TWK Everett fonts via Compose resources (packageOfResClass = de.servicehealth.poppmodule.demo.generated.resources). Depends on:popp-sdk. No iOS framework of its own (consumed by the per-app modules). Namespacede.servicehealth.poppmodule.demo.- 3rd-party demo under
popp-demo/popp-3rd-party-app-demo/::popp-demo:popp-3rd-party-app-demo:shared3rdPartyApp-> Compose-MP library;App.kt(commonMain, package…demo) +MainViewController.kt(iosMain, package…demo.thirdparty); depends onprojects.poppDemo.shared; static iOS frameworkShared3rdPartyApp. Namespace…demo.thirdparty.shared.:popp-demo:popp-3rd-party-app-demo:android3rdPartyApp->com.android.application;MainActivity(package…demo.thirdparty) callsApp(). Namespace/applicationIdde.servicehealth.poppmodule.demo.thirdparty.ios3rdPartyApp/-> Xcode project;ContentView.swiftimport Shared3rdPartyApp; run-scriptcd "$SRCROOT/../../.."then./gradlew :popp-demo:popp-3rd-party-app-demo:shared3rdPartyApp:embedAndSignAppleFrameworkForXcode. Bundle id…demo.thirdparty.
- Insurance demo under
popp-demo/popp-insurance-app-demo/-> identical shape:sharedInsuranceApp(frameworkSharedInsuranceApp, package/namespace…demo.insurance[.shared]),androidInsuranceApp(id…demo.insurance),iosInsuranceApp.
Dependency flow per app: androidXApp → sharedXApp → popp-demo:shared → :popp-sdk
Why each app and its shared UI are separate modules (AGP 9 constraint)
Since AGP 9.0, com.android.application cannot be applied together with org.jetbrains.kotlin.multiplatform in one module, and the KMP-compatible Android plugin (com.android.kotlin.multiplatform.library) produces a library, not an APK. So a module can't be both a KMP iOS-framework producer and a runnable Android app. Hence each demo splits into a KMP library (sharedXApp, also the iOS framework producer), a thin com.android.application (androidXApp), and an Xcode project (iosXApp).
Source set layout (KMP modules)
KMP source-set folders exist only once they hold files, though Gradle recognises the standard ones by convention regardless. Current state:
:popp-sdk—commonMain,androidMain,iosMain,commonTest.:popp-demo:shared—commonMain+commonTest.:popp-demo:popp-3rd-party-app-demo:shared3rdPartyApp—commonMain,androidMain,iosMain(+QrCameraViewfinder.ios.kt),androidHostTest,commonTest.:popp-demo:popp-insurance-app-demo:sharedInsuranceApp—commonMain,androidMain,iosMain.
Roles: commonMain (shared Kotlin + Compose UI; expect decls), androidMain/iosMain (actual impls + platform entry points), commonTest (multiplatform tests, run on every target). androidHostTest (Android-target host JVM tests; AGP 9 name, not androidUnitTest) and iosTest exist by convention but have no folder yet in :popp-sdk and :popp-demo:shared; shared3rdPartyApp already has an androidHostTest folder with real tests. androidHostTest is pre-configured via withHostTest { isIncludeAndroidResources = true } in :popp-sdk, :popp-demo:shared, and :popp-demo:popp-3rd-party-app-demo:shared3rdPartyApp.
Platform abstraction pattern
Cross-platform code uses Kotlin's expect/actual. In :popp-sdk, common code declares expect fun getPlatform(): Platform; each platform source set provides an actual (Platform.android.kt, Platform.ios.kt). Follow this pattern when adding platform-specific behavior needed for the PoPP-Module spec.
Code coverage & CI
Kover is applied at the root and aggregates coverage over :popp-sdk, :popp-demo:shared, and :popp-demo:popp-3rd-party-app-demo:shared3rdPartyApp. Compose-generated classes are filtered out of the reports via root Kover excludes (*.generated.resources.*, *ComposableSingletons*). .github/workflows/code-coverage.yml runs both modules' testAndroidHostTest plus the root :koverXmlReport/:koverHtmlReport, uploads artifacts, and pushes the XML report to Codecov. .github/dependabot.yml watches Gradle dependencies weekly.
ADR compliance
Before generating any code, architecture designs, or refactoring plans, scan docs/adr/ for all ADR files. Strictly follow every rule defined in ADRs with status Accepted. If a request would contradict an accepted ADR, warn the user before proceeding.
Conventions
- Package root:
de.servicehealth.poppmodule(SDK under.sdk; demo common under.demo; per-app under.demo.thirdparty/.demo.insurance). - Dependencies are managed through the Gradle version catalog at
gradle/libs.versions.toml— add libraries/plugins there rather than inlining versions in build scripts. - Type-safe project accessors are enabled (
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")insettings.gradle.kts), so reference modules asprojects.poppSdk,projects.poppDemo.shared,projects.poppDemo.popp3rdPartyAppDemo.shared3rdPartyApp - Kotlin code style: official (
kotlin.code.style=officialingradle.properties).