From 04e1dc03e5b7519658e0bc06a8d20947905c26fe Mon Sep 17 00:00:00 2001 From: Adrian Kuta Date: Fri, 12 Jun 2026 14:53:26 +0200 Subject: [PATCH] chore(deps): update all libraries and Gradle to latest stable versions Bump the version catalog, Gradle wrapper, and convention plugins to the latest stable releases. Verified with `./gradlew assembleDebug test` (BUILD SUCCESSFUL, 21 unit tests pass). Toolchain: - Gradle 9.1.0 -> 9.5.1 - AGP 9.0.1 -> 9.2.1 - Kotlin 2.3.20 -> 2.4.0 Libraries: - androidx-core 1.18.0 -> 1.19.0, appcompat 1.7.0 -> 1.7.1, fragment 1.8.5 -> 1.8.9, navigation 2.9.0 -> 2.9.8 - compose-bom 2026.03.01 -> 2026.05.01, material 1.12.0 -> 1.14.0 - coroutines 1.10.2 -> 1.11.0, serialization 1.8.1 -> 1.11.0, collections-immutable 0.3.8 -> 0.5.0 - koin 4.1.0 -> 4.2.1, ktor 3.1.3 -> 3.5.0, coil 3.1.0 -> 3.5.0 - JUnit 5.11.4 -> 6.1.0, turbine 1.2.0 -> 1.2.1, mockk 1.14.3 -> 1.14.11 Required side-effect: - compileSdk 36 -> 37 (mandated by androidx.core 1.19.0); targetSdk left at 36. Also refresh stale JUnit 5 / AGP 9.0 / compileSdk 36 references in the README and convention-plugin docs. --- README.md | 18 +++++----- .../AndroidUnitTestConventionPlugin.kt | 6 ++-- .../DomainModuleConventionPlugin.kt | 4 +-- .../convention/ProjectExtensions.kt | 2 +- .../usecase/GetCharactersPageUseCaseTest.kt | 2 +- gradle/libs.versions.toml | 36 +++++++++---------- gradle/wrapper/gradle-wrapper.properties | 2 +- 7 files changed, 35 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 938480d..9243828 100644 --- a/README.md +++ b/README.md @@ -36,17 +36,17 @@ a small MVVM *About* screen for contrast, and a dedicated **error-handling demo* | Concern | Choice | |---|---| | Build | Multi-module Gradle + `:build-logic` **convention plugins**; a single **version catalog** (`gradle/libs.versions.toml`) is the only place versions live | -| Toolchain | AGP 9.0.1, Kotlin 2.3.20, Gradle 9.1, `compileSdk`/`targetSdk` 36, `minSdk` 24, Java 17 | +| Toolchain | AGP 9.2.1, Kotlin 2.4.0, Gradle 9.5.1, `compileSdk` 37 / `targetSdk` 36, `minSdk` 24, Java 17 | | UI | Jetpack Compose (Material 3) + one classic **Views/XML** renderer | -| DI | Koin 4.1 (constructor DSL) | +| DI | Koin 4.2 (constructor DSL) | | Networking | Ktor (OkHttp engine) + KotlinX Serialization | | Images | Coil 3 | | Navigation | type-safe Compose Navigation (`@Serializable` routes) | | Logging | Timber | | Async | Coroutines + Flow | -| Testing | JUnit 5, MockK, Turbine, AssertK, `kotlinx-coroutines-test`, Ktor `MockEngine`, Compose UI test | +| Testing | JUnit 6, MockK, Turbine, AssertK, `kotlinx-coroutines-test`, Ktor `MockEngine`, Compose UI test | -> **AGP 9 gotcha:** AGP 9.0 has **built-in Kotlin**. Applying `com.android.application`/`library` +> **AGP 9 gotcha:** AGP 9.2 has **built-in Kotlin**. Applying `com.android.application`/`library` > auto-applies the Kotlin Android plugin, so the convention plugins must **not** apply > `org.jetbrains.kotlin.android` themselves. Source lives in `src/main/kotlin`. @@ -276,12 +276,12 @@ both resolve the **same** `CharacterListViewModel` class and supply its `SavedSt ## Testing -Tests prove the architecture, not just the code. Stack: **JUnit 5**, **MockK**, **Turbine** (Flow), +Tests prove the architecture, not just the code. Stack: **JUnit 6**, **MockK**, **Turbine** (Flow), **AssertK**, `kotlinx-coroutines-test`, Ktor **`MockEngine`**, and Compose UI test. | What | Where | Kind | |---|---|---| -| `GetCharactersPageUseCase` | `:feature:characters:domain` `src/test` | pure JVM, JUnit 5 | +| `GetCharactersPageUseCase` | `:feature:characters:domain` `src/test` | pure JVM, JUnit 6 | | `CharacterListViewModel`, `CharacterDetailViewModel` | `:feature:characters:presentation` `src/test` | JVM unit, MockK + Turbine + `SavedStateHandle` | | `NetworkCharacterRepository` | `:feature:characters:data` `src/test` | JVM unit, Ktor `MockEngine` | | `CharacterListScreen` (robot) | `:feature:characters:presentation-compose` `src/androidTest` | instrumented Compose UI | @@ -301,7 +301,7 @@ Conventions demonstrated: reads as a scenario; it asserts a rendered item, the empty/error states, and that a tap fires the right `Action`. -> **JUnit 5 on AGP 9:** the `de.mannodermaus.android-junit5` Gradle plugin targets AGP 8.x, so this +> **JUnit 6 on AGP 9:** the `de.mannodermaus.android-junit5` Gradle plugin targets AGP 8.x, so this > repo doesn't use it. `AndroidUnitTest` extends Gradle's `Test`, so the `architecture.android.unit.test` > convention plugin just calls `useJUnitPlatform()` and adds the `unit-test` bundle - including the > `junit-platform-launcher`, which Gradle 9 no longer bundles. @@ -323,7 +323,7 @@ possible but intentionally omitted (the VM logic is already covered by the share # Build ./gradlew assembleDebug # build the debug APK ./gradlew projects # print the module tree -./gradlew test # all JVM unit tests (JUnit 5) +./gradlew test # all JVM unit tests (JUnit 6) ./gradlew :feature:characters:presentation-compose:connectedDebugAndroidTest # Compose UI test (needs a device) ``` @@ -339,7 +339,7 @@ android docs search "" # search authoritative Android docs ``` Requires JDK 17+ (the Gradle build pins a Java 17 toolchain) and the Android SDK -(`compileSdk 36`, `minSdk 24`). +(`compileSdk 37`, `minSdk 24`). --- diff --git a/build-logic/convention/src/main/kotlin/com/example/architecture/convention/AndroidUnitTestConventionPlugin.kt b/build-logic/convention/src/main/kotlin/com/example/architecture/convention/AndroidUnitTestConventionPlugin.kt index 5abf15f..5ff12ee 100644 --- a/build-logic/convention/src/main/kotlin/com/example/architecture/convention/AndroidUnitTestConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/com/example/architecture/convention/AndroidUnitTestConventionPlugin.kt @@ -7,11 +7,11 @@ import org.gradle.kotlin.dsl.dependencies import org.gradle.kotlin.dsl.withType /** - * Runs an Android library module's local unit tests (`src/test`) on the **JUnit 5 platform** with the + * Runs an Android library module's local unit tests (`src/test`) on the **JUnit Platform** with the * shared `unit-test` toolset (JUnit Jupiter, kotlinx-coroutines-test, Turbine, AssertK). * * Deliberately does NOT use the `de.mannodermaus.android-junit5` Gradle plugin: its 1.11.x line - * targets AGP 8.x and we build on AGP 9.0. It isn't needed for *local* unit tests anyway - + * targets AGP 8.x and we build on AGP 9.2. It isn't needed for *local* unit tests anyway - * `com.android.build.gradle.tasks.factory.AndroidUnitTest` extends Gradle's [Test] task, so calling * `useJUnitPlatform()` on it is enough (this mirrors `DomainModuleConventionPlugin`, which does the * same for pure-JVM modules). @@ -21,7 +21,7 @@ class AndroidUnitTestConventionPlugin : Plugin { dependencies { add("testImplementation", libs.findBundle("unit-test").get()) add("testRuntimeOnly", libs.findLibrary("junit-jupiter-engine").get()) - // Gradle 9 dropped the bundled launcher; JUnit 5 won't start without it. + // Gradle 9 dropped the bundled launcher; the JUnit Platform won't start without it. add("testRuntimeOnly", libs.findLibrary("junit-platform-launcher").get()) } diff --git a/build-logic/convention/src/main/kotlin/com/example/architecture/convention/DomainModuleConventionPlugin.kt b/build-logic/convention/src/main/kotlin/com/example/architecture/convention/DomainModuleConventionPlugin.kt index b2045a3..4f0afa4 100644 --- a/build-logic/convention/src/main/kotlin/com/example/architecture/convention/DomainModuleConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/com/example/architecture/convention/DomainModuleConventionPlugin.kt @@ -8,7 +8,7 @@ import org.gradle.kotlin.dsl.withType /** * Pure-Kotlin (JVM) module for the domain layer: no Android dependencies. Adds Coroutines (for - * `Flow`-returning repository interfaces) and runs unit tests on the JUnit 5 platform. + * `Flow`-returning repository interfaces) and runs unit tests on the JUnit Platform. */ class DomainModuleConventionPlugin : Plugin { override fun apply(target: Project) = with(target) { @@ -25,7 +25,7 @@ class DomainModuleConventionPlugin : Plugin { add("testImplementation", libs.findLibrary("mockk").get()) add("testImplementation", libs.findLibrary("kotlinx-coroutines-test").get()) add("testRuntimeOnly", libs.findLibrary("junit-jupiter-engine").get()) - // Gradle 9 dropped the bundled launcher; JUnit 5 won't start without it. + // Gradle 9 dropped the bundled launcher; the JUnit Platform won't start without it. add("testRuntimeOnly", libs.findLibrary("junit-platform-launcher").get()) } diff --git a/build-logic/convention/src/main/kotlin/com/example/architecture/convention/ProjectExtensions.kt b/build-logic/convention/src/main/kotlin/com/example/architecture/convention/ProjectExtensions.kt index f95385f..a17d028 100644 --- a/build-logic/convention/src/main/kotlin/com/example/architecture/convention/ProjectExtensions.kt +++ b/build-logic/convention/src/main/kotlin/com/example/architecture/convention/ProjectExtensions.kt @@ -7,7 +7,7 @@ import org.gradle.kotlin.dsl.configure import org.gradle.kotlin.dsl.getByType import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension -internal const val COMPILE_SDK = 36 +internal const val COMPILE_SDK = 37 internal const val MIN_SDK = 24 internal const val TARGET_SDK = 36 internal const val JVM_TARGET = 17 diff --git a/feature/characters/domain/src/test/kotlin/com/example/architecture/feature/characters/domain/usecase/GetCharactersPageUseCaseTest.kt b/feature/characters/domain/src/test/kotlin/com/example/architecture/feature/characters/domain/usecase/GetCharactersPageUseCaseTest.kt index 4b58f51..874459e 100644 --- a/feature/characters/domain/src/test/kotlin/com/example/architecture/feature/characters/domain/usecase/GetCharactersPageUseCaseTest.kt +++ b/feature/characters/domain/src/test/kotlin/com/example/architecture/feature/characters/domain/usecase/GetCharactersPageUseCaseTest.kt @@ -18,7 +18,7 @@ import org.junit.jupiter.api.Test /** * Tests for the (thin pass-through) [GetCharactersPageUseCase]: it must forward the requested page to * the repository and return its result verbatim - success and error alike. Pure JVM test on the - * JUnit 5 platform (see DomainModuleConventionPlugin); the [CharacterRepository] collaborator is a + * JUnit Platform (see DomainModuleConventionPlugin); the [CharacterRepository] collaborator is a * MockK mock, stubbed with `coEvery` and verified with `coVerify`. */ class GetCharactersPageUseCaseTest { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7374fad..5466e57 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,46 +1,46 @@ [versions] # Build / language -agp = "9.0.1" -kotlin = "2.3.20" +agp = "9.2.1" +kotlin = "2.4.0" # AndroidX - core / lifecycle / activity / views -androidxCore = "1.18.0" +androidxCore = "1.19.0" androidxLifecycle = "2.10.0" androidxActivity = "1.13.0" -androidxAppcompat = "1.7.0" -androidxFragment = "1.8.5" +androidxAppcompat = "1.7.1" +androidxFragment = "1.8.9" androidxRecyclerview = "1.4.0" -androidxNavigation = "2.9.0" +androidxNavigation = "2.9.8" # Compose (BOM-managed) -composeBom = "2026.03.01" +composeBom = "2026.05.01" # Async / serialization -coroutines = "1.10.2" -kotlinxSerialization = "1.8.1" -kotlinxCollectionsImmutable = "0.3.8" +coroutines = "1.11.0" +kotlinxSerialization = "1.11.0" +kotlinxCollectionsImmutable = "0.5.0" # DI -koin = "4.1.0" +koin = "4.2.1" # Networking -ktor = "3.1.3" +ktor = "3.5.0" # Image loading -coil = "3.1.0" +coil = "3.5.0" # Logging timber = "5.0.1" # Material Components (Views renderer) -material = "1.12.0" +material = "1.14.0" # Testing -junitJupiter = "5.11.4" -junitPlatform = "1.11.4" -turbine = "1.2.0" +junitJupiter = "6.1.0" +junitPlatform = "6.1.0" +turbine = "1.2.1" assertk = "0.28.1" -mockk = "1.14.3" +mockk = "1.14.11" androidxTestExt = "1.3.0" androidxTestRunner = "1.7.0" androidxEspresso = "3.7.0" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2e11132..5dd3c01 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME