Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,14 +71,14 @@ jobs:
with:
distribution: temurin
java-version: "21"
# Sample app: compileSdk 34 (Compose AAR metadata), targetSdk 33.
# Sample app: compileSdk 35 (Compose 1.11 / modern AndroidX), targetSdk 35, minSdk 23.
# setup-android accepts licenses, installs cmdline-tools + listed packages,
# and exports ANDROID_HOME / ANDROID_SDK_ROOT for AGP.
- name: Set up Android SDK
uses: android-actions/setup-android@v3
with:
# Space-separated sdkmanager package ids (semicolons inside package names).
packages: "platform-tools platforms;android-34 platforms;android-33 build-tools;33.0.2 build-tools;34.0.0"
packages: "platform-tools platforms;android-35 platforms;android-34 platforms;android-33 build-tools;35.0.0 build-tools;34.0.0"
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v4
- name: Build application
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,6 @@ bazel-sample/bazel-*
**/bazel-out
**/bazel-testlogs
**/.bazeliskrc

.kotlin/
**/.kotlin/
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ buildscript {
minSdk = 21,
targetSdk = 33,
compileSdk = 34,
agpVersion = "8.1.2",
agpVersion = "8.13.2",
extraPlugins = listOf(
"androidx.navigation:navigation-safe-args-gradle-plugin:2.7.4",
"com.google.firebase:firebase-crashlytics-gradle:2.9.9",
Expand Down
2 changes: 1 addition & 1 deletion TICKETS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Update this file when picking or finishing work. Cron workers must pick the **hi
| F-002 | done | Audit build graph: plugins, sample app, CI workflows | Map modules → forma-core candidates; capture in `docs/ARCHITECTURE.md` |
| F-003 | done | Get plugins + sample `application/` building on modern toolchain | Plugins compile AGP 8.1.2 matches sample; Gradle 8.3 (plugins) / 8.4 (app); host builds green |
| F-004 | done | CI green on GitHub Actions for plugins + application | Temurin 17 all jobs + Android SDK 33 for app; PR #153 GHA green |
| F-018 | in_progress | JDK 21 + Gradle/AGP staged modernization | **Phase 1 + JDK 21 done:** wrappers **8.7** (#180); host/CI **JDK 21**; KSP **1.9.22-1.0.16**; Compose compiler **1.5.10**. AGP still **8.1.2** — next: AGP 8.5.2 → 8.7 → 8.13. AGP 9 = optional F-019. Plan: `~/.hermes/plans/2026-07-13_024508-forma-gradle-agp-upgrade.md` |
| F-018 | in_progress | JDK 21 + Gradle/AGP staged modernization | **2026-07-19 deps PR:** Gradle **8.14.5**, AGP **8.13.2**, Kotlin **2.0.21**, KSP **2.0.21-1.0.28**, Compose **1.9.4**/compiler **2.0.21**, sample libs modernized (AGP-8.13/SDK-35 ceiling). JDK 21 host/CI already. AGP 9 = F-019. |

## P1 — Android working product

Expand Down
10 changes: 5 additions & 5 deletions application/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
buildscript {
androidProjectConfiguration(
project = rootProject,
minSdk = 21,
targetSdk = 33,
// compileSdk 34 required by Compose transitive emoji2 1.4+ AAR metadata
compileSdk = 34,
agpVersion = "8.1.2",
minSdk = 23,
targetSdk = 35,
// compileSdk 35+ required by Compose 1.11 / modern AndroidX AAR metadata
compileSdk = 35,
agpVersion = "8.13.2",
extraPlugins =
listOf(
libs.plugins.toolsFormaDemoDependencies,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,32 @@

package tools.forma.sample.common.extensions.android.util

import androidx.fragment.app.Fragment
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer

fun <T> LifecycleOwner.observe(liveData: LiveData<T>, observer: (T) -> Unit) {
liveData.observe(this, {
liveData.observe(this) {
it?.let { t -> observer(t) }
})
}
}

fun <T> LifecycleOwner.observe(liveData: MutableLiveData<T>, observer: (T) -> Unit) {
liveData.observe(this, {
liveData.observe(this) {
it?.let { t -> observer(t) }
})
}
}

/** Prefer viewLifecycleOwner so observers clear with the view (Fragment lint FragmentLiveDataObserve). */
fun <T> Fragment.observe(liveData: LiveData<T>, observer: (T) -> Unit) {
liveData.observe(viewLifecycleOwner) {
it?.let { t -> observer(t) }
}
}

fun <T> Fragment.observe(liveData: MutableLiveData<T>, observer: (T) -> Unit) {
liveData.observe(viewLifecycleOwner) {
it?.let { t -> observer(t) }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,13 @@ package tools.forma.sample.common.recyclerview.widget
import android.graphics.Rect
import android.view.View
import androidx.annotation.VisibleForTesting
import androidx.annotation.VisibleForTesting.PRIVATE
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlin.math.ceil

class RecyclerViewItemDecoration(
@get:VisibleForTesting(otherwise = PRIVATE)
@get:VisibleForTesting(otherwise = VisibleForTesting.NONE)
internal val spacingPx: Int
) : RecyclerView.ItemDecoration() {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ package tools.forma.sample.core.mvvm.library.ui

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.annotation.VisibleForTesting
import androidx.annotation.VisibleForTesting.PRIVATE
import androidx.paging.PagedList
import androidx.paging.PagedListAdapter
import androidx.recyclerview.widget.DiffUtil
Expand All @@ -33,7 +31,6 @@ abstract class BasePagedListAdapter<T : Any>(
override fun areContentsTheSame(old: T, new: T): Boolean = contentsSame(old, new)
}) {

@VisibleForTesting(otherwise = PRIVATE)
internal var recyclerView: RecyclerView? = null

init {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class CharacterFavoriteViewModel @Inject constructor(
get() = _data

override val state: LiveData<ICharacterFavoriteViewState>
get() = Transformations.map(_data) {
get() = _data.map {
if (it.isEmpty()) {
Empty
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@
package tools.forma.sample.feature.characters.list.impl.ui

import androidx.lifecycle.LiveData
import androidx.lifecycle.Transformations
import androidx.lifecycle.ViewModel
import androidx.lifecycle.map
import androidx.lifecycle.switchMap
import androidx.paging.LivePagedListBuilder
import androidx.paging.PagedList
import tools.forma.sample.core.mvvm.library.lifecycle.SingleLiveData
Expand All @@ -37,13 +38,13 @@ class CharactersListViewModel @Inject constructor(
private val dataSourceFactory: CharactersPageDataSourceFactory
) : ViewModel(), ICharactersListViewModel {

override val networkState = Transformations.switchMap(dataSourceFactory.sourceLiveData) {
override val networkState = dataSourceFactory.sourceLiveData.switchMap {
it.networkState
}

override val event = SingleLiveData<ICharactersListViewEvent>()
override val data: LiveData<PagedList<ICharacter>> = LivePagedListBuilder(dataSourceFactory, PAGE_MAX_ELEMENTS).build()
override val state: LiveData<ICharactersListViewState> = Transformations.map(networkState) {
override val state: LiveData<ICharactersListViewState> = networkState.map {
when (it) {
is NetworkState.Success ->
if (it.isAdditional && it.isEmptyResponse) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ class HomeFragment : BaseViewBindingFragment(tools.forma.sample.feature.home.vie
}

private fun startStateObserver() {
viewModel.state.observe(this) {
viewModel.state.observe(viewLifecycleOwner) {
viewBinding.appBarLayout.isVisible = it.isNavigationScreen()
viewBinding.bottomNavigation.isVisible = it.isNavigationScreen()
}
Expand Down
2 changes: 1 addition & 1 deletion application/gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.5-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Expand Down
56 changes: 31 additions & 25 deletions application/settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -18,31 +18,37 @@ pluginManagement {

buildscript {
dependencies {
classpath("androidx.navigation:navigation-safe-args-gradle-plugin:2.7.4")
classpath("androidx.navigation:navigation-safe-args-gradle-plugin:2.7.7")
configurations.all {
resolutionStrategy {
force(
"com.android.tools.build:bundletool:1.15.5",
"com.google.guava:guava:31.1-jre",
"org.ow2.asm:asm:9.2",
"org.ow2.asm:asm-commons:9.2",
"org.ow2.asm:asm-util:9.2",
"com.google.code.gson:gson:2.8.9",
"org.apache.httpcomponents:httpclient:4.5.13",
"org.apache.httpcomponents:httpcore:4.4.15",
"com.google.protobuf:protobuf-java:3.19.2",
"com.google.protobuf:protobuf-java-util:3.19.2",
"org.checkerframework:checker-qual:3.12.0",
"com.google.errorprone:error_prone_annotations:2.11.0",
"commons-codec:commons-codec:1.15",
"com.android.tools.build:aapt2-proto:8.1.2-10154469",
// Gradle 8.7 embeds Kotlin 1.9.22 — force stdlib family lockstep under failOnVersionConflict
"com.android.tools.build:bundletool:1.18.3",
"com.google.guava:guava:33.3.1-jre",
"org.ow2.asm:asm:9.7.1",
"org.ow2.asm:asm-commons:9.7.1",
"org.ow2.asm:asm-util:9.7.1",
"org.ow2.asm:asm-tree:9.7.1",
"org.ow2.asm:asm-analysis:9.7.1",
"com.google.code.gson:gson:2.13.2",
"com.google.j2objc:j2objc-annotations:3.0.0",
"org.apache.httpcomponents:httpclient:4.5.14",
"org.apache.httpcomponents:httpcore:4.4.16",
"com.google.protobuf:protobuf-java:3.25.5",
"com.google.protobuf:protobuf-java-util:3.25.5",
"org.checkerframework:checker-qual:3.43.0",
"com.google.errorprone:error_prone_annotations:2.28.0",
"commons-codec:commons-codec:1.17.1",
"com.android.tools.build:aapt2-proto:8.13.2-14304508",
// Gradle 8.14.5 embeds Kotlin 2.0.21 — force stdlib family lockstep under failOnVersionConflict
"org.jetbrains.kotlin:kotlin-stdlib:$embeddedKotlinVersion",
"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$embeddedKotlinVersion",
"org.jetbrains.kotlin:kotlin-stdlib-jdk8:$embeddedKotlinVersion",
"org.jetbrains.kotlin:kotlin-stdlib-common:$embeddedKotlinVersion",
"org.jetbrains.kotlin:kotlin-reflect:$embeddedKotlinVersion",
"com.android.tools.build:gradle:8.1.2",
"org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2",
"org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.10.2",
"org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2",
"com.android.tools.build:gradle:8.13.2",
"org.jetbrains.kotlin:kotlin-gradle-plugin:$embeddedKotlinVersion",
"com.squareup:javapoet:1.13.0"
)
Expand All @@ -57,7 +63,7 @@ plugins {
id("convention-dependencies")
id("tools.forma.includer")
id("tools.forma.android")
id("com.gradle.enterprise") version ("3.15")
id("com.gradle.enterprise") version ("3.19.2")
}

enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
Expand All @@ -66,9 +72,9 @@ includer { arbitraryBuildScriptNames = true }

rootProject.name = "application"

val coilVersion = "2.1.0"
val sqliteVersion = "2.2.0"
val roomVersion = "2.5.1"
val coilVersion = "2.7.0"
val sqliteVersion = "2.5.1"
val roomVersion = "2.7.2"

val ksp = CustomConfiguration("ksp")

Expand All @@ -91,12 +97,12 @@ projectDependencies(
"androidx.room:room-common:$roomVersion",
),
plugin("tools.forma.demo:dependencies", "0.0.1"),
plugin("androidx.navigation:navigation-safe-args-gradle-plugin", "2.7.4"),
plugin("com.google.firebase:firebase-crashlytics-gradle", "2.9.9"),
plugin("androidx.navigation:navigation-safe-args-gradle-plugin", "2.7.7"),
plugin("com.google.firebase:firebase-crashlytics-gradle", "3.0.7"),
plugin(
id = "com.google.devtools.ksp:symbol-processing-gradle-plugin",
// Gradle 8.7 embedded Kotlin is 1.9.22; KSP line starts at 1.9.22-1.0.16 (1.0.13 does not exist)
version = "1.9.22-1.0.16",
// Gradle 8.14.5 embeds Kotlin 2.0.21; KSP must match
version = "2.0.21-1.0.28",
configuration = ksp,
"androidx.room:room-compiler:$roomVersion"
)
Expand Down
4 changes: 2 additions & 2 deletions bazel-adapter/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ version = "0.1.3-spike"

java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(17))
languageVersion.set(JavaLanguageVersion.of(21))
}
}

kotlin {
jvmToolchain(17)
jvmToolchain(21)
}

repositories {
Expand Down
2 changes: 1 addition & 1 deletion bazel-adapter/gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.5-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Expand Down
4 changes: 2 additions & 2 deletions bazel-adapter/settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ pluginManagement {
gradlePluginPortal()
mavenCentral()
}
// Use same Kotlin as the main build (embedded in Gradle 8.3 env)
// Match Gradle 8.14.5 embedded Kotlin
plugins {
id("org.jetbrains.kotlin.jvm") version "1.9.0"
id("org.jetbrains.kotlin.jvm") version "2.0.21"
}
}

Expand Down
6 changes: 4 additions & 2 deletions build-dependencies/dependencies/src/main/kotlin/Androidx.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ object androidx {
private val lifecycle_viewmodel_ktx = deps(
"androidx.lifecycle:lifecycle-viewmodel-ktx:${versions.androidx.lifecycle}".dep,
"androidx.lifecycle:lifecycle-viewmodel:${versions.androidx.lifecycle}".dep,
"androidx.lifecycle:lifecycle-livedata:${versions.androidx.lifecycle}".dep,
"androidx.lifecycle:lifecycle-livedata-ktx:${versions.androidx.lifecycle}".dep,
"androidx.lifecycle:lifecycle-runtime-ktx:${versions.androidx.lifecycle}".dep,
kotlinx.coroutines_android
)

Expand Down Expand Up @@ -253,7 +256,6 @@ object androidx {
)
val constraintlayout = deps(
"androidx.constraintlayout:constraintlayout:${versions.androidx.constraintlayout}".dep,
"androidx.constraintlayout:constraintlayout-solver:${versions.androidx.constraintlayout}".dep,
appcompat,
core
)
Expand Down Expand Up @@ -300,7 +302,7 @@ object androidx {
/**
* Jetpack Compose runtime + UI + foundation + material (pinned versions).
* Pair with `compose = true` / `composeWidget` targets (F-013).
* Versions match Compose Compiler 1.5.3 / Kotlin 1.9.10.
* Versions match Compose Compiler **2.0.21** / Kotlin **2.0.21** (Gradle 8.14.5).
* Uses [transitiveDeps] so Compose UI unit/graphics transitively resolve.
* Does not include activity-compose (pulls emoji2 requiring compileSdk 34);
* add that separately when hosting Compose in Activities on SDK 34+.
Expand Down
5 changes: 4 additions & 1 deletion build-dependencies/dependencies/src/main/kotlin/Google.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
object google {
val inject = "javax.inject:javax.inject:1".dep
val jakartaInject = "jakarta.inject:jakarta.inject-api:2.0.1".dep

val material = deps(
"com.google.android.material:material:${versions.google.material}".dep,
androidx.appcompat,
androidx.cardview,
androidx.constraintlayout,
androidx.core,
androidx.annotation,
androidx.legacy_ui,
Expand All @@ -15,6 +17,7 @@ object google {

val dagger = deps(
google.inject,
google.jakartaInject,
"com.google.dagger:dagger:${versions.google.dagger}".dep,
"com.google.dagger:dagger-compiler:${versions.google.dagger}".kapt
)
Expand All @@ -27,6 +30,6 @@ object google {
"com.google.code.gson:gson:${versions.google.gson}".dep
)

val firebase = transitivePlatform("com.google.firebase:firebase-bom:26.1.0")
val firebase = transitivePlatform("com.google.firebase:firebase-bom:33.16.0")

}
Loading
Loading