./gradlew build- Languages: Kotlin (primary for all new code) and Java (legacy — existing Java code is not subject to Kotlin standards, but should be internally consistent)
- Architecture: Gradle multi-module monorepo
- Build system: Gradle with Kotlin DSL (
build.gradle.kts), convention plugins inbuild-logic/plugins/ - Min SDK: 24, Compile SDK: 36, JVM toolchain: 17
- Dependency management: Version catalog at
gradle/libs.versions.toml— all dependencies, plugins, and versions MUST be declared there - Root
gradle.properties— version (VERSION_NAME), group (POM_GROUP=com.amplifyframework)
- PascalCase for Kotlin/Java source files matching class name:
AmplifyKinesisClient.kt,AmplifyException.java, etc. - Module directories use kebab-case:
aws-auth-cognito,aws-api-appsync,core-kotlin - Package structure:
com.amplifyframework.<category>(e.g.,com.amplifyframework.kinesis,com.amplifyframework.auth.cognito) - Test files:
<ClassName>Test.kt— placed in the same package under the test source tree - KMP modules use
commonMain/kotlin/,androidMain/kotlin/,androidHostTest/kotlin/source sets
# Check
./gradlew ktlintCheck checkstyle apiCheck
# Fix
./gradlew ktlintFormat apiDump- Categories: Abstract interfaces (
AuthCategory,StorageCategory, etc.) define the public API incore - Plugins: Concrete implementations (
AWSCognitoAuthPlugin) implement category interfaces - Escape hatch: Plugins expose the underlying SDK client via
getEscapeHatch() - Java callback pattern:
Consumer<T>for success,Consumer<Exception>for error,Actionfor void callbacks
- KMP module (
foundation) withcommonMainsource set — shared across platforms Result<T, E>sealed interface withSuccessandFailuresubtypes — used instead of throwing exceptionsAmplifyExceptionbase class withmessage,recoverySuggestion, andcauseLoggerinterface with lazy message suppliers:logger.debug { "message" }AwsCredentialsProviderfor credential management
- Standalone clients (not category plugins):
AmplifyKinesisClient,AmplifyFirehoseClient - Suspend-first API returning
Result<T, E>— no exceptions thrown in normal usage - Options via builder pattern with Kotlin DSL + Java builder support (see Builders section)
Builders MUST work in both Kotlin and Java:
data class Options internal constructor(val foo: String) {
companion object {
@JvmStatic
fun builder() = Builder()
@JvmSynthetic
operator fun invoke(func: Builder.() -> Unit) = Builder().apply(func).build()
@JvmStatic
fun defaults() = builder().build()
}
class Builder internal constructor() {
var foo: String = "defaultValue"
@JvmSynthetic set
fun foo(value: String) = apply { foo = value }
fun build() = Options(foo = foo)
}
}This enables idiomatic usage in both languages:
// Kotlin DSL
val options = Options { foo = "something" }
// Java builder
Options options = Options.builder().foo("something").build();- Amplify is Kotlin-first but supports Java consumers
- Companion object public APIs MUST be annotated
@JvmStatic
- Kotlin code SHOULD NOT throw exceptions as part of their API — use
Result<T, E>instead - Exceptions are reserved for unexpected errors (logic bugs, contract violations)
- Functions returning null in exceptional cases SHOULD be suffixed with
OrNull - Sealed exception hierarchies for typed error handling:
sealed class AmplifyKinesisException(...) : AmplifyException(...) { companion object { internal fun from(error: Throwable): AmplifyKinesisException = when (error) { ... } } } class AmplifyKinesisStorageException(...) : AmplifyKinesisException(...) class AmplifyKinesisValidationException(...) : AmplifyKinesisException(...)
- All exceptions include
message,recoverySuggestion, and optionalcause
- New code MUST use Kotlin Coroutines — avoid Executors, Runnables, Futures, RxJava, CountdownLatch
- Suspending functions MUST be main-safe (never block the calling thread)
- Use
withContext(Dispatchers.IO)for IO-bound work,withContext(Dispatchers.Default)for CPU-bound - SHOULD NOT use
GlobalScopeorrunBlocking(except bridging legacy blocking APIs on background threads) - Every
CoroutineScopeMUST have a defined lifetime and be canceled when done
@InternalAmplifyApi— ERROR-level opt-in; internal API not for external use@InternalApiWarning— WARNING-level opt-in; same intent, softer enforcement@AmplifyFlutterApi— ERROR-level opt-in; visible only for Amplify Flutter bridge- All three are excluded from the public API surface by the binary compatibility validator
- New tests MUST be written in Kotlin
- Unit tests use backtick names:
`flush should handle mixed record states correctly` - Connected Android tests MUST NOT use backticks (unsupported pre-API 30)
- Arrange-Act-Assert structure (Given-When-Then)
- Assertions: Use Kotest assertions (
shouldBe,shouldNotBeNull) — not JUnitassertEquals - Mocking: MockK preferred (
mockk,coEvery,coVerify). UsejustRunsfor Unit-returning mocks. PreferverifywithwithArgover slots. - Static mocks (
mockkStatic,mockkObject) MUST be cleaned up — prefer scoped versions - Prefer unit tests wherever reasonable, Robolectric over connected Android tests
- Coroutine tests use
runTestfromkotlinx-coroutines-test - Custom test assertions in
testutilsmodule (e.g.,shouldBeSuccess,shouldBeFailure)
- New Gradle files MUST use Kotlin DSL (
.gradle.kts) - Build logic shared via convention plugins in
build-logic/plugins/— NOT viasubprojects/allprojects - Convention plugins are hierarchical:
amplify.android.libraryappliesamplify.kotlinwhich appliesamplify.ktlint - All dependencies declared in
gradle/libs.versions.tomlusing type-safe accessors - Convention plugins included via
includeBuild("build-logic")insettings.gradle.kts
Key modules:
core— Framework categories, plugin interfaces,AmplifyException(Java)core-kotlin— Kotlin coroutine facades (suspend funwrappers around callback APIs)foundation— KMP module withResult,Logger,AwsCredentialsProvider, newAmplifyExceptionfoundation-bridge— Bridges foundation types to V2 typesannotations—@InternalAmplifyApi,@InternalApiWarning,@AmplifyFlutterApiaws-auth-cognito— Cognito auth plugin (state machine-based, largest module)aws-auth-plugins-core— Shared auth plugin utilitiesaws-api,aws-datastore,aws-storage-s3,aws-analytics-pinpoint, etc. — Category plugin implementationsaws-kinesis— Standalone client (new pattern, not a category plugin)testutils,testmodels— Shared test infrastructurerxbindings— RxJava bindings (legacy)