Skip to content

feat(gradle-plugin): support Android library targets and AGP 8.x/9.x - #118

Open
aryapreetam wants to merge 1 commit into
Kotlin:mainfrom
aryapreetam:feat/android-library-support
Open

aryapreetam wants to merge 1 commit into
Kotlin:mainfrom
aryapreetam:feat/android-library-support

Conversation

@aryapreetam

@aryapreetam aryapreetam commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Problem

Storytale currently has several limitations when used with modern Android projects:

  1. Application Only: Only com.android.application is supported. Multiplatform libraries using com.android.library or com.android.kotlin.multiplatform.library fail during configuration.
  2. Legacy AGP Coupling: The plugin couples to deprecated AppExtension and ApplicationVariant APIs, causing deprecation warnings and incompatibilities on AGP 8.x and 9.x.
  3. DEX Symbol Crash on Story Names with Spaces: Stories named with spaces (e.g. val `Primary Button` by story) require DEX format 040 (Android API 30+). Projects with minSdk < 30 fail during DEX compilation.
  4. Deprecated Gradle APIs: Task implementations rely on deprecated project.exec and internal Gradle capitalized() extensions.

Solution

1. Android Library & KMP Library Support

  • Dual Target Configuration: StorytaleGradlePlugin.kt and AndroidMultiplatformTask.kt detect and configure both application and library targets.
  • Instrumented Test Execution: Because library modules produce .aar artifacts that cannot launch directly, the plugin configures an instrumented runner (androidTest / androidDeviceTest) with a generated StorytaleAppActivity manifest to host the gallery.
  • Direct Source Set Wiring: Symmetrically wires mainStoriesSourceSet.kotlin.srcDirs into the target source set using Gradle DSL (kotlin.srcDir(...)), avoiding temporary filesystem copies.

2. AGP 8.x & 9.x Compatibility

  • AndroidComponentsHelper: Encapsulates variant resolution and device-test registration across AGP 8.x and 9.x APIs.
  • MinSdk Floor for Stories: Uses beforeVariants to elevate minSdk to 30 specifically for the Stories variant. This enables DEX format 040 for space-separated story names without modifying the user's release minSdk.
  • AAR Metadata Compatibility: Disables check*AarMetadata tasks for stories tasks, preventing AGP 8.x from halting on advisory minAgpVersion checks when newer Compose dependencies specify AGP 9.1+.
  • ADB Device Selection: Added active device detection to automatically pick a connected physical device or running emulator.

3. Modernized APIs

  • Replaced project.exec { ... } with ProcessBuilder helpers in Utils.kt.
  • Replaced internal Gradle capitalized() with Kotlin stdlib replaceFirstChar.
  • Annotated task outputs in AndroidSourceGeneratorTask.kt with @get:OutputDirectory and @get:OutputFile.

Design Decisions

  • Why use instrumented tests for libraries? Android library modules cannot be launched via am start on their own. Executing through the device-test infrastructure packages a runnable APK containing StorytaleAppActivity without requiring a separate application harness module.
  • Why elevate minSdk via beforeVariants? DEX format 040 is required to support spaces in identifier names. Raising the floor to 30 strictly for the Stories variant solves this cleanly without altering the user's release build configuration.
  • Why use reflective lookups in AndroidComponentsHelper? AGP introduced breaking method signature changes between 8.2 and 9.1 (HasDeviceTests vs HasAndroidTest). Using targeted reflection avoids hard compile-time dependencies, allowing one plugin binary to support AGP 8.x and 9.x.

Verification

  • ./gradlew spotlessCheck (passes)
  • ./gradlew -p modules/gradle-plugin test (5/5 tests pass in AndroidTargetSupportTest)
Visual Verification & Reproduction Steps
1. Application (:examples) 2. KMP Library (storytale-sample)
Examples Android Stories Sample KMP Library Stories
examples-Stories.apk shared-androidTest.apk

Reproduction Steps

1. Internal Application (:examples)

./gradlew :examples:androidStoriesRun

2. External KMP Project with :shared library aryapreetam/storytale-sample

  1. Publish the updated Gradle plugin locally (from Storytale root):
./gradlew publishToMavenLocal
  1. Clone storytale-sample (if not already cloned):
git clone https://github.com/aryapreetam/storytale-sample.git
cd storytale-sample
  1. In shared/build.gradle.kts, apply the local snapshot:
plugins {
  ...
- id("io.github.aryapreetam.storytale") version "0.0.6"
+ id("org.jetbrains.compose.storytale") version "0.0.4-SNAPSHOT"
}
  1. Run the Android stories task on a connected device or emulator:
./gradlew :shared:androidStoriesRun -PcmpProfile=1.10

Note: Verified against CMP 1.10 profile (Kotlin 2.3.21) with zero configuration changes. (The following PR addresses compiler plugin FIR/IR compatibility for Kotlin 2.4+).

@aryapreetam
aryapreetam force-pushed the feat/android-library-support branch from 978d3bb to 8f95c98 Compare October 15, 2026 10:38
@aryapreetam
aryapreetam marked this pull request as ready for review October 15, 2026 10:53
- Replace deprecated AppExtension/ApplicationVariant coupling with dynamic plugin detection
- Support com.android.library and com.android.kotlin.multiplatform.library targets
- Configure device test runner tasks and manifest generation for library modules
- Enforce minSdk floor of 30 for Stories variant to support DEX 040 symbols
- Disable check*AarMetadata tasks for stories execution to ensure compatibility across AGP 8.x and 9.x
- Replace deprecated Project.exec and String.capitalized with ProcessBuilder and Kotlin stdlib
- Add unit test suite covering application, library, and KMP library targets
@aryapreetam
aryapreetam force-pushed the feat/android-library-support branch from 8f95c98 to c6176fa Compare October 16, 2026 19:50

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant