Imported from Vonage/vonage-video-android-app (
AGENTS.md). Install upstream withnpx skills add Vonage/vonage-video-android-app. Copyright stays with the author.
AGENTS.md
Module Map
| Module | Role |
|---|---|
app/ |
Composition root: navigation, Hilt DI, networking, screen orchestration |
vonage-meeting-room/ |
Self-contained prebuilt meeting-room library — no Hilt, manual DI via MeetingRoomContainer. Public API is only the classes in api/; everything else is internal. |
vonage-meeting-room-sample-app/ |
Minimal host app demonstrating vonage-meeting-room in isolation |
vonage-video-core/ |
Wraps vonage-video-sdk abstractions behind VonageVideoClient + CallFacade |
vonage-video-sdk/ |
Mockable Kotlin interfaces over the OpenTok SDK (VonageSession, VonageSdkFactory, etc.). Other modules depend on these, never on OpenTok directly. |
vonage-video-ui-compose/ |
Reusable Compose components + JSON-driven theme generator |
vonage-feature-*/ |
Optional capabilities with enabled/disabled flavor variants |
vonage-video-shared/ |
Shared utilities |
vonage-android-logger/ |
Logging pipeline with interceptor model |
vonage-audio-selector/ |
Audio output selector (Bluetooth, wired, earpiece, speaker) |
build-tools/ |
Custom Gradle plugins: com.vonage.json-config, com.vonage.theme-generator, detekt/kover/sonar wiring |
Config-Driven Behavior (Critical)
config/app-config.jsonis the single source of truth for feature toggles andbaseApiUrl.- Never hand-edit
gradle/generated-config.properties— it is generated by./gradlew generateVonageConfig. app/build.gradle.ktsreadsgradle/generated-config.propertiesand maps each property to both aBuildConfigfield and amissingDimensionStrategy(...)flavor selection.- Generated runtime constants:
AppConfigundercom.vonage.android.config(written toapp/build/generated/source/jsonConfig/). generateVonageConfigis a hard dependency of all Kotlin and KSP compile tasks inapp/build.gradle.kts; config changes must be regenerated before builds.- Theme is generated from
config/theme.jsonbycom.vonage.theme-generatorintovonage-video-ui-compose/src/main/java/com/vonage/android/compose/theme/. Do not edit generated theme files directly. BASE_API_URLcan be set three ways (evaluated in order):local.propertieskeyBASE_API_URL, environment variableBASE_API_URL, or replacing the placeholder directly inapp-config.json. Emulator local backend: use10.0.2.2.
Feature Toggle Pattern
- Optional modules use flavor dimensions with
enabled/disabledproduct flavors (e.g.,vonage-feature-chat/build.gradle.kts). - Public contracts live in
src/main; behavior implementations live insrc/enabledandsrc/disabled. When changing a feature API, update both flavor source sets. vonage-meeting-roomalso accepts a runtime filter:MeetingRoomBuilder.enabledFeatures(Set<MeetingRoomFeature>). A feature is active only when its compile-time flavor isenabledAND it is present in the runtime set. You cannot enable a compile-time disabled feature at runtime.vonage-meeting-roomhas its own flavor dimensions for three features (archiving,captions,screensharing) with full names likearchivingEnabled/archivingDisabled. The remaining features (chat, reactions, videofx, audiofx, settings) always resolve toenabledinside this module; runtime availability is viaMeetingRoomFeature.
Architecture Notes
- Session bootstrap path:
APIService.getSession()→SessionRepository→MeetingRoomViewModel.connect(...)→VonageVideoClient.initializeSession(...). BASE_API_URLpropagates to three locations: Retrofit base URL (RetrofitModule.kt), deep links (AppNavHost.kt), and sharing links (util/navigateToShare.kt).- Hilt DI is used in
app/.vonage-meeting-roomuses a manualMeetingRoomContainer— do not introduce Hilt into that module. - Feature signal plugins (chat, reactions) are injected into
VonageVideoClientviaSdkModule.provideVonageVideoClient(...)inapp/. vonage-meeting-roomis under active refactoring: use cases are being introduced andCall.kt(~800 lines) is being split into focused collaborators. Consult the inline comments in that module before making changes there.- Debug builds append
.debugtoapplicationIdand-DEBUGtoversionName, allowing debug and release to co-exist on device. - Firebase/Crashlytics plugins are only applied for release builds (guarded by
isReleaseBuildcheck inapp/build.gradle.kts).
Developer Workflows
# Required first-time setup: install pre-push Git hook
./gradlew installGitHooks
# After any config/app-config.json or theme.json edit
./gradlew generateVonageConfig
# Build and install debug
./gradlew installDebug
# All unit tests
./gradlew test
# Single module unit tests
./gradlew :vonage-meeting-room:test
./gradlew :vonage-video-core:test
# Instrumented tests (connected device/emulator)
./gradlew connectedAndroidTest
# Instrumented tests on Gradle Managed Device (no physical device needed)
./gradlew pixelDebugAndroidTest
# CI-equivalent quality gate (runs tests + coverage XML + detekt)
./gradlew clean koverXmlReportDebug detekt
# Local HTML coverage report
./gradlew koverHtmlReport
# Static analysis (also runs automatically on git push via pre-push hook)
./gradlew detekt
# Binary API compatibility check (runs in CI)
./gradlew apiCheck
Binary API Compatibility
vonage-audio-selectorandvonage-android-loggeruse thebinary-compatibility-validatorplugin with committed.apidump files.- After changing the public API surface of either module, run
./gradlew apiDumpand commit the updated.apifiles alongside the code change. ./gradlew apiCheckruns as a separate CI job and fails the build if the.apifiles are out of sync.
Snapshot Tests (vonage-video-ui-compose)
- Roborazzi (Robolectric-backed) snapshot tests live in
vonage-video-ui-compose/src/test/. - Golden PNGs are in
vonage-video-ui-compose/src/test/snapshots/images/and must be committed. - Workflow when changing a component's visuals:
- Make the UI change.
./gradlew :vonage-video-ui-compose:recordRoborazziDebug— regenerates goldens.- Review updated PNGs, then commit them alongside the code.
- Verify (CI-equivalent):
./gradlew :vonage-video-ui-compose:verifyRoborazziDebug - Each test class needs
@RunWith(RobolectricTestRunner::class)+@GraphicsMode(GraphicsMode.Mode.NATIVE)+@OptIn(ExperimentalRoborazziApi::class). Dark-mode variant needs@Config(qualifiers = "+night")on the method. - For dialogs (separate composition root), use
onAllNodes(isRoot())[1]instead ofonRoot().
UI / Instrumented Test Conventions
- Pattern: ScreenObject (wraps
SemanticsNodeInteractionsProvider) + ScreenTest (@HiltAndroidTest @RunWith(AndroidJUnit4::class)). - TestTag constants go in the source
*Route.ktfile as anobject <Screen>TestTags. Apply.testTag(TAG)first in the modifier chain. - Rule ordering:
HiltAndroidRuleatorder=0; if permissions needed,GrantPermissionRuleatorder=1;createComposeRulelast. - Always wrap
setContentinVonageVideoTheme { ... }. - Test naming:
given_<precondition>_THEN_<expected_outcome>. - Use
assertDoesNotExist()when a node is absent from composition;assertIsNotDisplayed()when it exists but is off-screen. - Screen actions data class: all lambda fields must default to
{}(no-op).
Conventions
- Kotlin + Java 17 throughout (
sourceCompatibility/targetCompatibility=VERSION_17). - Compose lint is strict via detekt compose rules plugin (
build-tools/detekt/detekt.yml). Run./gradlew detektbefore pushing (the pre-push hook does this automatically onceinstallGitHooksis run). - Branch from
develop, notmain; PRs targetdevelop. Branch naming for internal contributors:DEVELOPERNAME/TICKETNUMBER-SHORTDESCRIPTION. - Do not hardcode backend endpoints; use
BuildConfig.BASE_API_URLor generatedAppConfig. - Do not add feature flags ad-hoc in
app/when a feature module boundary already exists. - SDK version is in
gradle/libs.versions.toml(opentokAndroidSdk); do not pin it elsewhere. - Kover coverage excludes
vonage-video-ui-compose,vonage-video-sdk, andvonage-config-idea-plugin(see rootbuild.gradle.kts).