build: convert to AGP 9.2.0 and latest kotlin multiplatform
authorDa Risk <da_risk@geekorum.com>
Mon, 04 May 2026 18:00:16 -0400
changeset 114 ab226603d0f5
parent 113 5986ef49853d
child 115 5c6baebd860d
build: convert to AGP 9.2.0 and latest kotlin multiplatform
buildSrc/src/main/kotlin/AndroidTests.kt
buildSrc/src/main/kotlin/RepositoryChangeset.kt
buildSrc/src/main/kotlin/conventions/android.kt
buildSrc/src/main/kotlin/conventions/mpp-library-with-android.gradle.kts
core/build.gradle.kts
gradle/libs.versions.toml
gradle/wrapper/gradle-wrapper.properties
sample/androidApp/build.gradle.kts
sample/androidApp/src/main/AndroidManifest.xml
sample/androidApp/src/main/kotlin/MainActivity.kt
sample/build.gradle.kts
sample/src/androidMain/kotlin/com/geekorum/aboutoss/sampleapp/MainActivity.kt
settings.gradle.kts
ui/common/build.gradle.kts
ui/material2/build.gradle.kts
ui/material3/build.gradle.kts
--- a/buildSrc/src/main/kotlin/AndroidTests.kt	Mon May 04 16:21:57 2026 -0400
+++ b/buildSrc/src/main/kotlin/AndroidTests.kt	Mon May 04 18:00:16 2026 -0400
@@ -21,8 +21,10 @@
  */
 package com.geekorum.build
 
+import com.android.build.api.dsl.ApplicationExtension
 import com.android.build.api.dsl.CommonExtension
 import com.android.build.api.dsl.DefaultConfig
+import com.android.build.api.dsl.LibraryExtension
 import com.android.build.gradle.BaseExtension
 import com.android.build.gradle.internal.dsl.TestOptions
 import org.gradle.api.Project
@@ -36,28 +38,37 @@
 import org.gradle.kotlin.dsl.dependencies
 import org.gradle.kotlin.dsl.kotlin
 
-const val espressoVersion = "3.5.0-alpha07" // alpha for this bug https://github.com/robolectric/robolectric/issues/6593
-const val androidxTestRunnerVersion = "1.4.0"
-const val androidxTestCoreVersion = "1.4.0"
-const val robolectricVersion = "4.8.2"
+const val espressoVersion = "3.6.1"
+const val androidxTestRunnerVersion = "1.6.2"
+const val androidxTestCoreVersion = "1.6.1"
+const val robolectricVersion = "4.14.1"
 
-private typealias BaseExtension = CommonExtension<*, *, DefaultConfig, *, *, *>
 
 /*
  * Configuration for espresso and robolectric usage in an Android project
  */
-@Suppress("UnstableApiUsage")
 internal fun Project.configureTests() {
-    extensions.configure<BaseExtension>("android") {
-        defaultConfig {
-            testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
-            testInstrumentationRunnerArguments += mapOf(
-                "clearPackageData" to "true",
-                "disableAnalytics" to "true"
-            )
+    extensions.configure<CommonExtension>("android") {
+        if (this is ApplicationExtension) {
+            defaultConfig {
+                testInstrumentationRunner = "com.geekorum.ttrss.HiltRunner"
+                testInstrumentationRunnerArguments += mapOf(
+                    "clearPackageData" to "true",
+                    "disableAnalytics" to "true"
+                )
+            }
+        }
+        if (this is LibraryExtension) {
+            defaultConfig {
+                testInstrumentationRunner = "com.geekorum.ttrss.HiltRunner"
+                testInstrumentationRunnerArguments += mapOf(
+                    "clearPackageData" to "true",
+                    "disableAnalytics" to "true"
+                )
+            }
         }
 
-        testOptions {
+        testOptions.apply {
             execution = "ANDROIDX_TEST_ORCHESTRATOR"
             animationsDisabled = true
 
@@ -67,20 +78,20 @@
         }
     }
 
-
     dependencies {
         dualTestImplementation(kotlin("test-junit"))
 
-        androidTestUtil("androidx.test:orchestrator:$androidxTestRunnerVersion")
+        androidTestUtil("androidx.test:orchestrator:1.5.1")
         androidTestImplementation("androidx.test:runner:$androidxTestRunnerVersion")
-        dualTestImplementation("androidx.test.ext:junit-ktx:1.1.1")
+        dualTestImplementation("androidx.test.ext:junit-ktx:1.2.1")
 
         dualTestImplementation("androidx.test:core-ktx:$androidxTestCoreVersion")
-        dualTestImplementation("androidx.test:rules:$androidxTestRunnerVersion")
+        dualTestImplementation("androidx.test:rules:1.6.1")
+
         // fragment testing is usually declared on debugImplementation configuration and need these dependencies
         constraints {
             debugImplementation("androidx.test:core:$androidxTestCoreVersion")
-            debugImplementation("androidx.test:monitor:$androidxTestRunnerVersion")
+            debugImplementation("androidx.test:monitor:1.7.2")
         }
 
         dualTestImplementation("androidx.test.espresso:espresso-core:$espressoVersion")
@@ -88,12 +99,12 @@
         dualTestImplementation("androidx.test.espresso:espresso-intents:$espressoVersion")
 
         // assertions
-        dualTestImplementation("com.google.truth:truth:1.0")
-        dualTestImplementation("androidx.test.ext:truth:1.3.0-alpha01")
+        dualTestImplementation("com.google.truth:truth:1.4.4")
+        dualTestImplementation("androidx.test.ext:truth:1.6.0")
 
         // mock
-        testImplementation("io.mockk:mockk:1.13.2")
-        androidTestImplementation("io.mockk:mockk-android:1.13.2")
+        testImplementation("io.mockk:mockk:1.13.8")
+        androidTestImplementation("io.mockk:mockk-android:1.13.8")
         testImplementation("org.robolectric:robolectric:$robolectricVersion")
 
         constraints {
--- a/buildSrc/src/main/kotlin/RepositoryChangeset.kt	Mon May 04 16:21:57 2026 -0400
+++ b/buildSrc/src/main/kotlin/RepositoryChangeset.kt	Mon May 04 18:00:16 2026 -0400
@@ -31,7 +31,6 @@
 import org.gradle.api.tasks.Input
 import org.gradle.api.tasks.OutputFile
 import org.gradle.api.tasks.TaskAction
-import org.gradle.configurationcache.extensions.capitalized
 import org.gradle.kotlin.dsl.register
 import org.gradle.process.ExecOperations
 import java.io.ByteArrayOutputStream
@@ -154,3 +153,5 @@
         })
     }
 }
+
+private fun String.capitalized() = replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() }
--- a/buildSrc/src/main/kotlin/conventions/android.kt	Mon May 04 16:21:57 2026 -0400
+++ b/buildSrc/src/main/kotlin/conventions/android.kt	Mon May 04 18:00:16 2026 -0400
@@ -21,17 +21,43 @@
  */
 package com.geekorum.build.conventions
 
+import com.android.build.api.dsl.CommonExtension
+import com.android.build.api.dsl.KotlinMultiplatformAndroidLibraryTarget
 import com.android.build.gradle.BaseExtension
 import org.gradle.api.Project
+import org.gradle.api.plugins.ExtensionAware
 import org.gradle.kotlin.dsl.findByType
+import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
+
+const val ANDROID_COMPILE_SDK = 37
+const val ANDROID_MIN_SDK = 28
 
 fun Project.conventionForAndroidProject() {
+    // new AGP 9 built-in kotlin use these configuration
+    extensions.findByType<CommonExtension>()?.apply {
+        compileSdk = ANDROID_COMPILE_SDK
+        defaultConfig.minSdk = ANDROID_MIN_SDK
+    }
+
+    // new androidLibrary in multiplatform project
+    extensions.findByType<KotlinMultiplatformExtension>()?.apply {
+        (this as ExtensionAware).extensions.findByType<KotlinMultiplatformAndroidLibraryTarget>()?.apply {
+            compileSdk {
+                version = release(ANDROID_COMPILE_SDK)
+            }
+            minSdk = ANDROID_MIN_SDK
+            aarMetadata {
+                minCompileSdk = ANDROID_COMPILE_SDK
+            }
+        }
+    }
+
     extensions.findByType<BaseExtension>()?.apply {
-        setCompileSdkVersion(36)
+        setCompileSdkVersion(ANDROID_COMPILE_SDK)
         defaultConfig {
-            minSdk = 28
+            minSdk = ANDROID_MIN_SDK
             aarMetadata {
-                minCompileSdk = 28
+                minCompileSdk = ANDROID_COMPILE_SDK
             }
         }
     }
--- a/buildSrc/src/main/kotlin/conventions/mpp-library-with-android.gradle.kts	Mon May 04 16:21:57 2026 -0400
+++ b/buildSrc/src/main/kotlin/conventions/mpp-library-with-android.gradle.kts	Mon May 04 18:00:16 2026 -0400
@@ -23,7 +23,7 @@
 
 plugins {
     id("com.geekorum.build.conventions.mpp-library")
-    id("com.android.library")
+    id("com.android.kotlin.multiplatform.library")
 }
 
 conventionForAndroidProject()
--- a/core/build.gradle.kts	Mon May 04 16:21:57 2026 -0400
+++ b/core/build.gradle.kts	Mon May 04 18:00:16 2026 -0400
@@ -28,7 +28,17 @@
 }
 
 kotlin {
-    androidTarget()
+    android {
+        namespace = "com.geekorum.aboutoss.core"
+
+        @Suppress("UnstableApiUsage")
+        optimization {
+            consumerKeepRules.apply {
+                publish = true
+                file("consumer-rules.pro")
+            }
+        }
+    }
 
     jvm("desktop")
 
@@ -49,41 +59,13 @@
             api(libs.kotlinx.coroutines)
             implementation(libs.kotlinx.serialization.json)
         }
-    }
-}
-
-android {
-    namespace = "com.geekorum.aboutoss.core"
-
-    defaultConfig {
-        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
-        consumerProguardFiles("consumer-rules.pro")
-    }
-
-    buildTypes {
-        release {
-            isMinifyEnabled = false
-            proguardFiles(
-                getDefaultProguardFile("proguard-android-optimize.txt"),
-                "proguard-rules.pro"
-            )
-        }
-    }
-
-    publishing {
-        singleVariant("release") {
-            withJavadocJar()
-            withSourcesJar()
+        androidUnitTest.dependencies {
+            implementation(libs.androidx.test.ext.junit)
+            implementation(libs.espresso.core)
         }
     }
 }
 
-dependencies {
-
-    testImplementation(libs.junit)
-    androidTestImplementation(libs.androidx.test.ext.junit)
-    androidTestImplementation(libs.espresso.core)
-}
 
 mavenPublishing {
     coordinates(groupId = group.toString(), name, version.toString())
--- a/gradle/libs.versions.toml	Mon May 04 16:21:57 2026 -0400
+++ b/gradle/libs.versions.toml	Mon May 04 18:00:16 2026 -0400
@@ -19,10 +19,10 @@
 # along with AboutOss.  If not, see <http://www.gnu.org/licenses/>.
 
 [versions]
-android-gradle-plugin = "8.13.0"
+android-gradle-plugin = "9.2.0"
 dokka = "2.1.0"
-kotlin = "2.2.21"
-jetbrains-compose-multiplatform = "1.9.2"
+kotlin = "2.3.21"
+jetbrains-compose-multiplatform = "1.10.3"
 junit = "4.13.2"
 androidx-test-ext-junit = "1.3.0"
 espresso-core = "3.7.0"
@@ -64,6 +64,7 @@
 jetbrains-compose-materialIconsCore = { module = "org.jetbrains.compose.material:material-icons-core", version.ref = "jetbrains-compose-materialIconsCore" }
 jetbrains-compose-material3AdaptiveNavigation = { module = "org.jetbrains.compose.material3.adaptive:adaptive-navigation", version.ref = "jetbrains-compose-material3AdaptiveNavigation" }
 jetbrains-compose-uiBackhandler = { module = "org.jetbrains.compose.ui:ui-backhandler", version.ref = "jetbrains-compose-ui" }
+jetbrains-compose-uiTooling = { module = "org.jetbrains.compose.ui:ui-tooling", version.ref = "jetbrains-compose-multiplatform" }
 
 androidx-compose-uiTooling = { group = "androidx.compose.ui", name = "ui-tooling" }
 androidx-compose-uiToolingPreview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
@@ -80,7 +81,7 @@
 dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" }
 kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
 kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
-google-gms-oss-license = { id = "com.google.android.gms.oss-licenses-plugin", version = "0.10.6" }
+google-gms-oss-license = { id = "com.google.android.gms.oss-licenses-plugin", version = "0.11.0" }
 kotlinx-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
 vanniktech-maven-publish = { id = "com.vanniktech.maven.publish", version = "0.34.0" }
 
--- a/gradle/wrapper/gradle-wrapper.properties	Mon May 04 16:21:57 2026 -0400
+++ b/gradle/wrapper/gradle-wrapper.properties	Mon May 04 18:00:16 2026 -0400
@@ -1,6 +1,6 @@
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
 networkTimeout=10000
 validateDistributionUrl=true
 zipStoreBase=GRADLE_USER_HOME
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sample/androidApp/build.gradle.kts	Mon May 04 18:00:16 2026 -0400
@@ -0,0 +1,103 @@
+/*
+ * AboutOss is an utility library to retrieve and display
+ * opensource licenses in Android applications.
+ *
+ * Copyright (C) 2023-2025 by Frederic-Charles Barthelery.
+ *
+ * This file is part of AboutOss.
+ *
+ * AboutOss is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * AboutOss is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with AboutOss.  If not, see <http://www.gnu.org/licenses/>.
+ */
+import org.jetbrains.kotlin.gradle.dsl.JvmTarget
+
+@Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed
+plugins {
+    id("com.android.application")
+    id("com.geekorum.build.source-license-checker")
+    alias(libs.plugins.kotlin.compose)
+    alias(libs.plugins.jetbrains.compose.multiplatform)
+    alias(libs.plugins.kotlinx.serialization)
+    alias(libs.plugins.google.gms.oss.license)
+}
+
+// workaround bug https://issuetracker.google.com/issues/275534543
+buildscript {
+    dependencies {
+        classpath("com.android.tools.build:gradle:9.2.0")
+    }
+}
+
+android {
+    namespace = "com.geekorum.aboutoss.sampleapp.android"
+
+    compileSdk {
+        version = release(37)
+    }
+    defaultConfig {
+        applicationId = "com.geekorum.aboutoss.sampleapp"
+        minSdk = 28
+        targetSdk = 35
+        versionCode = 1
+        versionName = "1.0"
+
+        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+        vectorDrawables {
+            useSupportLibrary = true
+        }
+    }
+
+    buildTypes {
+        release {
+            isMinifyEnabled = false
+            proguardFiles(
+                getDefaultProguardFile("proguard-android-optimize.txt"),
+                "proguard-rules.pro"
+            )
+        }
+    }
+    compileOptions {
+        sourceCompatibility = JavaVersion.VERSION_17
+        targetCompatibility = JavaVersion.VERSION_17
+    }
+
+    buildFeatures {
+        compose = true
+        buildConfig = true
+    }
+    packaging {
+        resources {
+            excludes += "/META-INF/{AL2.0,LGPL2.1}"
+        }
+    }
+}
+
+dependencies {
+    implementation(project(":sample"))
+    implementation(compose.material3)
+    implementation(compose.components.resources)
+    implementation(compose.components.uiToolingPreview)
+    implementation(libs.jetbrains.androidx.lifecycle.viewModelCompose)
+    api(libs.androidx.activity)
+    implementation(dependencies.enforcedPlatform(libs.androidx.compose.bom))
+    implementation(libs.androidx.activity.compose)
+    implementation(libs.geekdroid)
+
+    testImplementation(libs.junit)
+    androidTestImplementation(libs.androidx.test.ext.junit)
+    androidTestImplementation(libs.espresso.core)
+    androidTestImplementation(platform(libs.androidx.compose.bom))
+    androidTestImplementation(libs.androidx.compose.uiTestJunit4)
+    debugImplementation(libs.androidx.compose.uiTooling)
+    debugImplementation(libs.androidx.compose.uiTestManifest)
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sample/androidApp/src/main/AndroidManifest.xml	Mon May 04 18:00:16 2026 -0400
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+
+    AboutOss is an utility library to retrieve and display
+    opensource licenses in Android applications.
+
+    Copyright (C) 2023-2025 by Frederic-Charles Barthelery.
+
+    This file is part of AboutOss.
+
+    AboutOss is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    AboutOss is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with AboutOss.  If not, see <http://www.gnu.org/licenses/>.
+
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <queries>
+        <intent>
+            <action android:name="android.support.customtabs.action.CustomTabsService" />
+        </intent>
+    </queries>
+
+    <application
+        android:allowBackup="true"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:roundIcon="@mipmap/ic_launcher_round"
+        android:supportsRtl="true"
+        android:theme="@style/Theme.AboutOss">
+        <activity
+            android:name=".MainActivity"
+            android:exported="true"
+            android:theme="@style/Theme.AboutOss">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name="com.geekorum.aboutoss.sampleapp.PrebuiltLicencesMaterial2Activity"
+            android:theme="@style/Theme.AboutOss.Material2"
+            android:exported="false" />
+
+        <activity android:name="com.geekorum.aboutoss.sampleapp.PrebuiltLicencesMaterial3Activity"
+            android:theme="@android:style/Theme.Material.Light.NoActionBar"
+            android:exported="false" />
+
+    </application>
+
+</manifest>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sample/androidApp/src/main/kotlin/MainActivity.kt	Mon May 04 18:00:16 2026 -0400
@@ -0,0 +1,80 @@
+/*
+ * AboutOss is an utility library to retrieve and display
+ * opensource licenses in Android applications.
+ *
+ * Copyright (C) 2023-2025 by Frederic-Charles Barthelery.
+ *
+ * This file is part of AboutOss.
+ *
+ * AboutOss is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * AboutOss is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with AboutOss.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.geekorum.aboutoss.sampleapp.android
+
+import android.content.Intent
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.activity.enableEdgeToEdge
+import com.geekorum.aboutoss.sampleapp.PrebuiltLicencesMaterial2Activity
+import com.geekorum.aboutoss.sampleapp.PrebuiltLicencesMaterial3Activity
+import com.geekorum.aboutoss.sampleapp.SampleApp
+import com.geekorum.aboutoss.sampleapp.ui.theme.AboutOssTheme
+import com.geekorum.aboutoss.sampleapp.ui.theme.OpenSourceLicenseTheme
+import com.geekorum.aboutoss.ui.material3.OpenSourceLicensesActivity
+import com.geekorum.aboutoss.ui.material.OpenSourceLicensesActivity as Material2OpenSourceLicensesActivity
+
+class MainActivity : ComponentActivity() {
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        enableEdgeToEdge()
+        setContent {
+            AboutOssTheme {
+                SampleApp(
+                    onMaterial2Click = {
+                        startMaterial2LicenseActivity()
+                    },
+                    onMaterial3Click = {
+                        startMaterial3LicenseActivity()
+                    }
+                )
+            }
+        }
+    }
+
+    private fun startMaterial2LicenseActivity() {
+        val intent = if (BuildConfig.DEBUG) {
+            // we launch a custom activity in debug to display some fake licenses
+            // see Material2AcPrebuiltLicencesMaterial2Activitytivity for more info
+            Intent(this, PrebuiltLicencesMaterial2Activity::class.java)
+        } else {
+            Intent(this, Material2OpenSourceLicensesActivity::class.java)
+        }
+        startActivity(intent)
+    }
+
+    private fun startMaterial3LicenseActivity() {
+        // Don't use default MaterialTheme but supply our own
+        OpenSourceLicensesActivity.themeProvider = { content ->
+            OpenSourceLicenseTheme(content)
+        }
+        val intent = if (BuildConfig.DEBUG) {
+            // we launch a custom activity in debug to display some fake licenses
+            // see PrebuiltLicencesMaterial3Activity for more info
+            Intent(this, PrebuiltLicencesMaterial3Activity::class.java)
+        } else {
+            Intent(this, OpenSourceLicensesActivity::class.java)
+        }
+        startActivity(intent)
+    }
+}
--- a/sample/build.gradle.kts	Mon May 04 16:21:57 2026 -0400
+++ b/sample/build.gradle.kts	Mon May 04 18:00:16 2026 -0400
@@ -23,24 +23,20 @@
 
 @Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed
 plugins {
-    id("com.android.application")
-    kotlin("multiplatform")
+    id("com.geekorum.build.conventions.mpp-library-with-android")
     id("com.geekorum.build.source-license-checker")
     alias(libs.plugins.kotlin.compose)
     alias(libs.plugins.jetbrains.compose.multiplatform)
     alias(libs.plugins.kotlinx.serialization)
-    alias(libs.plugins.google.gms.oss.license)
 }
 
-// workaround bug https://issuetracker.google.com/issues/275534543
-buildscript {
-    dependencies {
-        classpath("com.android.tools.build:gradle:8.13.0")
-    }
-}
 
 kotlin {
-    androidTarget {
+    android {
+        namespace = "com.geekorum.aboutoss.sampleapp"
+        androidResources {
+            enable = true
+        }
         compilerOptions {
             jvmTarget.set(JvmTarget.JVM_17)
         }
@@ -65,10 +61,10 @@
 
     sourceSets {
         commonMain.dependencies {
-            implementation(project(":core"))
-            implementation(project(":ui:common"))
-            implementation(project(":ui:material2"))
-            implementation(project(":ui:material3"))
+            api(project(":core"))
+            api(project(":ui:common"))
+            api(project(":ui:material2"))
+            api(project(":ui:material3"))
             implementation(compose.material3)
             implementation(compose.components.resources)
             implementation(compose.components.uiToolingPreview)
@@ -91,54 +87,3 @@
     }
 }
 
-android {
-    namespace = "com.geekorum.aboutoss.sampleapp"
-    compileSdk = 36
-
-    defaultConfig {
-        applicationId = "com.geekorum.aboutoss.sampleapp"
-        minSdk = 28
-        targetSdk = 35
-        versionCode = 1
-        versionName = "1.0"
-
-        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
-        vectorDrawables {
-            useSupportLibrary = true
-        }
-    }
-
-    buildTypes {
-        release {
-            isMinifyEnabled = false
-            proguardFiles(
-                getDefaultProguardFile("proguard-android-optimize.txt"),
-                "proguard-rules.pro"
-            )
-        }
-    }
-    compileOptions {
-        sourceCompatibility = JavaVersion.VERSION_17
-        targetCompatibility = JavaVersion.VERSION_17
-    }
-
-    buildFeatures {
-        compose = true
-        buildConfig = true
-    }
-    packaging {
-        resources {
-            excludes += "/META-INF/{AL2.0,LGPL2.1}"
-        }
-    }
-}
-
-dependencies {
-    testImplementation(libs.junit)
-    androidTestImplementation(libs.androidx.test.ext.junit)
-    androidTestImplementation(libs.espresso.core)
-    androidTestImplementation(platform(libs.androidx.compose.bom))
-    androidTestImplementation(libs.androidx.compose.uiTestJunit4)
-    debugImplementation(libs.androidx.compose.uiTooling)
-    debugImplementation(libs.androidx.compose.uiTestManifest)
-}
--- a/sample/src/androidMain/kotlin/com/geekorum/aboutoss/sampleapp/MainActivity.kt	Mon May 04 16:21:57 2026 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,77 +0,0 @@
-/*
- * AboutOss is an utility library to retrieve and display
- * opensource licenses in Android applications.
- *
- * Copyright (C) 2023-2025 by Frederic-Charles Barthelery.
- *
- * This file is part of AboutOss.
- *
- * AboutOss is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * AboutOss is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with AboutOss.  If not, see <http://www.gnu.org/licenses/>.
- */
-package com.geekorum.aboutoss.sampleapp
-
-import android.content.Intent
-import android.os.Bundle
-import androidx.activity.ComponentActivity
-import androidx.activity.compose.setContent
-import androidx.activity.enableEdgeToEdge
-import com.geekorum.aboutoss.sampleapp.ui.theme.AboutOssTheme
-import com.geekorum.aboutoss.sampleapp.ui.theme.OpenSourceLicenseTheme
-import com.geekorum.aboutoss.ui.material3.OpenSourceLicensesActivity
-import com.geekorum.aboutoss.ui.material.OpenSourceLicensesActivity as Material2OpenSourceLicensesActivity
-
-class MainActivity : ComponentActivity() {
-    override fun onCreate(savedInstanceState: Bundle?) {
-        super.onCreate(savedInstanceState)
-        enableEdgeToEdge()
-        setContent {
-            AboutOssTheme {
-                SampleApp(
-                    onMaterial2Click = {
-                        startMaterial2LicenseActivity()
-                    },
-                    onMaterial3Click = {
-                        startMaterial3LicenseActivity()
-                    }
-                )
-            }
-        }
-    }
-
-    private fun startMaterial2LicenseActivity() {
-        val intent = if (BuildConfig.DEBUG) {
-            // we launch a custom activity in debug to display some fake licenses
-            // see Material2AcPrebuiltLicencesMaterial2Activitytivity for more info
-            Intent(this, PrebuiltLicencesMaterial2Activity::class.java)
-        } else {
-            Intent(this, Material2OpenSourceLicensesActivity::class.java)
-        }
-        startActivity(intent)
-    }
-
-    private fun startMaterial3LicenseActivity() {
-        // Don't use default MaterialTheme but supply our own
-        OpenSourceLicensesActivity.themeProvider = { content ->
-            OpenSourceLicenseTheme(content)
-        }
-        val intent = if (BuildConfig.DEBUG) {
-            // we launch a custom activity in debug to display some fake licenses
-            // see PrebuiltLicencesMaterial3Activity for more info
-            Intent(this, PrebuiltLicencesMaterial3Activity::class.java)
-        } else {
-            Intent(this, OpenSourceLicensesActivity::class.java)
-        }
-        startActivity(intent)
-    }
-}
--- a/settings.gradle.kts	Mon May 04 16:21:57 2026 -0400
+++ b/settings.gradle.kts	Mon May 04 18:00:16 2026 -0400
@@ -35,5 +35,6 @@
 include(":ui:material2")
 include(":ui:material3")
 include(":sample")
+include(":sample:androidApp")
 include(":dokka")
 
--- a/ui/common/build.gradle.kts	Mon May 04 16:21:57 2026 -0400
+++ b/ui/common/build.gradle.kts	Mon May 04 18:00:16 2026 -0400
@@ -29,7 +29,20 @@
 }
 
 kotlin {
-    androidTarget()
+    android {
+        namespace = "com.geekorum.aboutoss.ui.common"
+        androidResources {
+            enable = true
+        }
+
+        @Suppress("UnstableApiUsage")
+        optimization {
+            consumerKeepRules.apply {
+                publish = true
+                file("consumer-rules.pro")
+            }
+        }
+    }
 
     jvm("desktop")
 
@@ -57,6 +70,10 @@
             api(libs.androidx.activity)
             implementation(libs.androidx.activity.compose)
         }
+        androidUnitTest.dependencies {
+            implementation(libs.androidx.test.ext.junit)
+            implementation(libs.espresso.core)
+        }
     }
 }
 
@@ -64,36 +81,9 @@
     publicResClass = true
 }
 
-android {
-    namespace = "com.geekorum.aboutoss.ui.common"
-
-    defaultConfig {
-        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
-        consumerProguardFiles("consumer-rules.pro")
-    }
-
-    buildTypes {
-        release {
-            isMinifyEnabled = false
-            proguardFiles(
-                getDefaultProguardFile("proguard-android-optimize.txt"),
-                "proguard-rules.pro"
-            )
-        }
-    }
-
-    publishing {
-        singleVariant("release") {
-            withJavadocJar()
-            withSourcesJar()
-        }
-    }
-}
 
 dependencies {
-    testImplementation(libs.junit)
-    androidTestImplementation(libs.androidx.test.ext.junit)
-    androidTestImplementation(libs.espresso.core)
+    androidRuntimeClasspath(libs.jetbrains.compose.uiTooling)
     "androidMainApi"(libs.geekdroid) {
         exclude("androidx.compose.material3")
     }
--- a/ui/material2/build.gradle.kts	Mon May 04 16:21:57 2026 -0400
+++ b/ui/material2/build.gradle.kts	Mon May 04 18:00:16 2026 -0400
@@ -31,8 +31,20 @@
 }
 
 kotlin {
-    androidTarget()
+    android {
+        namespace = "com.geekorum.aboutoss.ui.material"
+        androidResources {
+            enable = true
+        }
 
+        @Suppress("UnstableApiUsage")
+        optimization {
+            consumerKeepRules.apply {
+                publish = true
+                file("consumer-rules.pro")
+            }
+        }
+    }
     jvm("desktop")
 
     listOf(
@@ -62,44 +74,16 @@
             implementation(dependencies.platform(libs.androidx.compose.bom))
             implementation(libs.androidx.activity.compose)
         }
-    }
-}
 
-
-android {
-    namespace = "com.geekorum.aboutoss.ui.material"
-
-    defaultConfig {
-        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
-        consumerProguardFiles("consumer-rules.pro")
-    }
-
-    buildTypes {
-        release {
-            isMinifyEnabled = false
-            proguardFiles(
-                getDefaultProguardFile("proguard-android-optimize.txt"),
-                "proguard-rules.pro"
-            )
-        }
-    }
-
-    buildFeatures {
-        compose = true
-    }
-
-    publishing {
-        singleVariant("release") {
-            withJavadocJar()
-            withSourcesJar()
+        androidUnitTest.dependencies {
+            implementation(libs.androidx.test.ext.junit)
+            implementation(libs.espresso.core)
         }
     }
 }
 
 dependencies {
-    testImplementation(libs.junit)
-    androidTestImplementation(libs.androidx.test.ext.junit)
-    androidTestImplementation(libs.espresso.core)
+    androidRuntimeClasspath(libs.jetbrains.compose.uiTooling)
 }
 
 mavenPublishing {
--- a/ui/material3/build.gradle.kts	Mon May 04 16:21:57 2026 -0400
+++ b/ui/material3/build.gradle.kts	Mon May 04 18:00:16 2026 -0400
@@ -30,8 +30,20 @@
 }
 
 kotlin {
-    androidTarget()
+    android {
+        namespace = "com.geekorum.aboutoss.ui.material3"
+        androidResources {
+            enable = true
+        }
 
+        @Suppress("UnstableApiUsage")
+        optimization {
+            consumerKeepRules.apply {
+                publish = true
+                file("consumer-rules.pro")
+            }
+        }
+    }
     jvm("desktop")
 
     listOf(
@@ -64,47 +76,17 @@
             implementation(dependencies.platform(libs.androidx.compose.bom))
             implementation(libs.androidx.activity.compose)
         }
+
+        androidUnitTest.dependencies {
+            implementation(libs.androidx.test.ext.junit)
+            implementation(libs.espresso.core)
+        }
     }
 }
 
 
-android {
-    namespace = "com.geekorum.aboutoss.ui.material3"
-
-    defaultConfig {
-        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
-        consumerProguardFiles("consumer-rules.pro")
-    }
-
-    buildTypes {
-        release {
-            isMinifyEnabled = false
-            proguardFiles(
-                getDefaultProguardFile("proguard-android-optimize.txt"),
-                "proguard-rules.pro"
-            )
-        }
-    }
-
-    buildFeatures {
-        compose = true
-    }
-
-    publishing {
-        singleVariant("release") {
-            withJavadocJar()
-            withSourcesJar()
-        }
-    }
-}
-
 dependencies {
-    testImplementation(libs.junit)
-    androidTestImplementation(libs.androidx.test.ext.junit)
-    androidTestImplementation(libs.espresso.core)
-    afterEvaluate {
-        "androidDebugImplementation"(libs.androidx.compose.uiTooling)
-    }
+    androidRuntimeClasspath(libs.jetbrains.compose.uiTooling)
 }
 
 mavenPublishing {