Add support to build on SDK 37 - #6590
Conversation
PR checklist ✅All required conditions are satisfied:
🎉 Great job! This PR is ready for review. |
SDK Size Comparison 📏
|
Align the Chat SDK with the shared Android toolchain (Chat/Video parity). Toolchain: - AGP 8.10.1 -> 9.1.1, Gradle 8.11.1 -> 9.3.1, JDK 21 CI - compileSdk/targetSdk/sampleTargetSdk -> 37 (suppressUnsupportedCompileSdk=37) - streamConventions -> 0.13.2-SNAPSHOT (AGP 9), macroBenchmark -> 1.5.0-alpha07 - Add Central snapshots repo for Stream snapshot plugins AGP 9 compatibility (transitional; remove before AGP 10): - android.builtInKotlin=false, android.newDsl=false so the standalone Kotlin plugin keeps working (avoids duplicate `kotlin` extension and the TestExtension -> BaseExtension cast failure) Remove gitVersioner: - It calls the Gradle-9-removed Project.exec(Action); derive the sample-app version from git directly in sample-app-versioner.gradle Migrate deprecated DSL (hard errors on AGP 9): - packagingOptions -> packaging, lintOptions -> lint - kotlinOptions -> compilerOptions, managedDevices.devices -> allDevices - composeCompiler: drop enableStrongSkippingMode, stabilityConfigurationFiles
# Conflicts: # gradle.properties
|
DB Entities have been updated. Do we need to upgrade DB Version? |
2. Add logic to fix robolectric and paprazzi runs 3. Update paparazzi for Agp 9+
Paparazzi 2.0.0-alpha05's layoutlib is unstable rendering at API 37: any HandlerThread hits Thread.setPosixNicenessInternal, and Popup dismissal hits a null WindowManager child list. API 36 is alpha05's stable ceiling. Keep the module on compileSdk 37 but render at 36 by overriding only the Paparazzi environment: detectEnvironment().copy(compileSdkVersion = 36). - Add a createPaparazzi(...) factory to PaparazziComposeTest and PaparazziViewTest that applies the override. - Route every Paparazzi test call site through the factory. Goldens must be re-recorded at API 36 via the record-paparazzi workflow + scripts/paparazzi-pull-goldens.sh. Remove the override once Paparazzi supports API 37.
Gradle 9 no longer auto-provisions org.junit.platform:junit-platform-launcher on the test runtime classpath. stream-chat-android-core calls useJUnitPlatform() directly (unlike the Android modules, which get the launcher from the android-junit5 plugin), so its :test task failed with "Failed to load JUnit Platform". Declare the launcher explicitly.
…ry configuration. Shot (in the sample apps) drags an old androidx.test (core 1.5.0 / monitor 1.6.0 / storage 1.4.2) onto the main runtime classpath
|
| contents: read | ||
| pull-requests: read | ||
| uses: GetStream/stream-build-conventions-android/.github/workflows/android-ci.yml@8c5fbade4ee9c06a0a979bcb0866e64669254f73 | ||
| uses: GetStream/stream-build-conventions-android/.github/workflows/android-ci.yml@feature/rahullohra/build-on-sdk-37 |
| java-version: '21' # Paparazzi 2.0.0-alpha05 requires JDK 21. | ||
| distribution: temurin | ||
| - name: Setup Gradle | ||
| uses: gradle/actions/setup-gradle@v4 |
WalkthroughThe PR upgrades Android build tooling for AGP 9 and Gradle 9, standardizes CI on Java 21, centralizes Paparazzi configuration, adds snapshot automation, and updates Android module DSL usage. ChangesAndroid build and CI updates
Estimated code review effort: 4 (Complex) | ~60 minutes Possibly related PRs
Suggested labels: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 2📝 Generate docstrings 💡
🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (3)
gradle.properties (2)
21-22: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winAlign the tooling-parallel property with the checked-in wrapper.
Gradle introduced
org.gradle.tooling.parallelin Gradle 9.4.0, butgradle/wrapper/gradle-wrapper.propertiesselects Gradle 9.3.1. The property is not active under the repository wrapper.org.gradle.parallel=truealready controls the older behavior. Remove this property until the wrapper is upgraded, or upgrade both versions and validate IDE sync. (docs.gradle.org)Proposed change
-# Enabled parallel sync for Gradle 9.4+ -org.gradle.tooling.parallel=true🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@gradle.properties` around lines 21 - 22, Remove the org.gradle.tooling.parallel=true entry from gradle.properties because the checked-in Gradle wrapper remains on 9.3.1; retain the existing org.gradle.parallel=true setting and do not upgrade the wrapper as part of this change.Source: MCP tools
8-8: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winRemove the SDK warning suppression unless CI still needs it.
AGP 9.1.1 supports Android API 37, so this property is redundant for the selected toolchain. Keeping it can hide a real compile-SDK compatibility warning. Remove it, or document the exact warning that still requires it. (developer.android.com)
Proposed change
-android.suppressUnsupportedCompileSdk=37🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@gradle.properties` at line 8, Remove the android.suppressUnsupportedCompileSdk=37 property from gradle.properties, since the selected AGP 9.1.1 toolchain supports API 37; only retain it if CI demonstrably requires it, and document the exact warning requiring the suppression.Source: MCP tools
gradle/libs.versions.toml (1)
81-81: 🔒 Security & Privacy | 🔵 Trivial | 🏗️ Heavy liftVerify the snapshot convention plugin before release.
streamConventions = "0.13.2-SNAPSHOT"is a moving target. Maven documents that a snapshot can resolve a different deployed build later, so the same source can use different convention logic. Confirm snapshot repository availability on every CI and release path. Replace it with an immutable release before publishing. (maven.apache.org)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@gradle/libs.versions.toml` at line 81, Update the streamConventions version in the dependency catalog from the moving 0.13.2-SNAPSHOT to an immutable released version before publishing, and verify that the chosen release is available through every CI and release repository path.Source: MCP tools
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/attach-release-apk.yml:
- Around line 24-26: Move the GetStream reusable workflow reference out of the
job’s steps and invoke it at job scope using the workflow-call syntax. Preserve
the java-version input, and keep any required signing or upload operations as
separate steps in the calling job, replacing the current workflow entry with a
setup action only if those steps still need to run.
In `@gradle/libs.versions.toml`:
- Around line 3-4: Before retaining the targetSdk and sampleTargetSdk values at
37, add device or emulator coverage for Android 17/API 37 affected flows,
including large-screen resizing and orientation, activity recreation, manifest
restrictions, SMS, DCL, reflection, and socket behavior. Ensure the tests
exercise these runtime changes rather than relying only on existing smoke tests.
In `@scripts/paparazzi-pull-goldens.sh`:
- Line 47: Update the snapshot-status pipeline in the paparazzi pull-goldens
script to replace the early-closing head -30 consumer with one that reads the
complete input while still limiting displayed output to 30 lines. Preserve the
existing git status filtering and staging behavior, and ensure the pipeline
remains successful under pipefail when more than 30 snapshot paths change.
In `@scripts/sample-app-versioner.gradle`:
- Around line 15-22: Ensure every CI workflow that builds or distributes the
sample app uses a complete Git checkout by configuring the relevant
actions/checkout steps with fetch-depth: 0, or replace commitCount in the
sampleAppVersionCode generation with an equivalent monotonic CI build number.
Keep sampleAppVersionName generation consistent with the selected version
source.
---
Nitpick comments:
In `@gradle.properties`:
- Around line 21-22: Remove the org.gradle.tooling.parallel=true entry from
gradle.properties because the checked-in Gradle wrapper remains on 9.3.1; retain
the existing org.gradle.parallel=true setting and do not upgrade the wrapper as
part of this change.
- Line 8: Remove the android.suppressUnsupportedCompileSdk=37 property from
gradle.properties, since the selected AGP 9.1.1 toolchain supports API 37; only
retain it if CI demonstrably requires it, and document the exact warning
requiring the suppression.
In `@gradle/libs.versions.toml`:
- Line 81: Update the streamConventions version in the dependency catalog from
the moving 0.13.2-SNAPSHOT to an immutable released version before publishing,
and verify that the chosen release is available through every CI and release
repository path.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
| - uses: GetStream/stream-build-conventions-android/.github/workflows/android-ci.yml@feature/rahullohra/build-on-sdk-37 | ||
| with: | ||
| java-version: '21' |
There was a problem hiding this comment.
🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Changed file:"
git ls-files .github/workflows/attach-release-apk.yml || true
echo
echo "Workflow excerpt:"
if [ -f .github/workflows/attach-release-apk.yml ]; then
sed -n '1,120p' .github/workflows/attach-release-apk.yml | cat -n
fi
echo
echo "Relevant references in workflows:"
rg -n "uses: GetStream/stream-build-conventions-android/.github/workflows/android-ci.yml|android-ci.yml|setup-java|getstream|GetStream" .github/workflows || true
echo
echo "Check repository context for workflow files on referenced repo unavailable?"
python3 - <<'PY'
# Deterministic check: parse YAML-like uses entries and identify /workflows/ paths under steps.
import pathlib,re
p=pathlib.Path('.github/workflows/attach-release-apk.yml')
if not p.exists():
print("file missing")
raise SystemExit
text=p.read_text()
lines=text.splitlines()
in_steps=False
step_depth=0
uses_entries=[]
for i,line in enumerate(lines,1):
if re.match(r'\s*steps:\s*$', line):
in_steps=True
step_depth=len(line)-len(line.lstrip())+2
continue
if in_steps:
m=re.match(r'\s{4}uses:\s*(.+)\s*$', line)
if m:
uses_entries.append((i,m.group(1).strip()))
if re.match(r'^\S', line):
break
print("uses entries near job:")
for i,u in uses_entries:
print(f"{i}: {u}")
print("uses paths under steps that target workflow files:", [u for i,u in uses_entries if '/workflows/' in u])
PYRepository: GetStream/stream-chat-android
Length of output: 1944
Move the reusable workflow call out of steps.
GetStream/stream-build-conventions-android/.github/workflows/android-ci.yml targets a workflow file, and GitHub Actions reusable workflow calls belong at the job scope, not as a step entry. Replace this step with a setup action if the job still needs custom signing/upload steps.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/attach-release-apk.yml around lines 24 - 26, Move the
GetStream reusable workflow reference out of the job’s steps and invoke it at
job scope using the workflow-call syntax. Preserve the java-version input, and
keep any required signing or upload operations as separate steps in the calling
job, replacing the current workflow entry with a setup action only if those
steps still need to run.
| targetSdk = "37" | ||
| sampleTargetSdk = "37" |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== files =="
git ls-files | rg '(^|/)gradle/(libs\.versions\.toml|.*convention.*|plugins/|build.gradle|settings.gradle)$' || true
echo "== libs.versions top =="
if [ -f gradle/libs.versions.toml ]; then
sed -n '1,80p' gradle/libs.versions.toml | cat -n
fi
echo "== targetSdk usages =="
rg -n 'targetSdk|sampleTargetSdk|android {|namespace|sdkVersion' -S . --glob '!build/**' --glob '!gradle/wrapper/**' || true
echo "== git diff stat/name/status =="
git diff --stat || true
git diff -- gradle/libs.versions.toml | sed -n '1,160p' || trueRepository: GetStream/stream-chat-android
Length of output: 1944
🌐 Web query:
Google Developer Android 17 API 37 large screen resize orientation activity recreation targetSdk behavior
💡 Result:
For apps targeting Android 17 (API level 37) or higher, significant changes to orientation, resizability, and activity recreation behavior take effect, particularly on large screen devices (smallest width >= 600dp) [1][2][3]. Key behavioral changes and targetSdk requirements for API 37: 1. Mandatory Adaptive Behavior: On large screens (sw >= 600dp), apps targeting API 37 or higher can no longer restrict orientation, resizability, or aspect ratio [1][2][3]. Restrictions such as android:screenOrientation, android:resizableActivity, and fixed aspect ratio attributes are ignored [1]. The system no longer uses pillarboxing for these apps; they are required to fill the available display window [1]. 2. Activity Recreation and Configuration Changes: Because these apps must adapt to frequent changes in window size, orientation, and aspect ratio, the system will, by default, trigger an Activity recreation during these events [2][4]. To maintain user state and avoid data loss, developers are strongly encouraged to implement robust state preservation (e.g., ViewModel, savedInstanceState) [2][4]. 3. Opt-in for Handling Changes: If you prefer to handle these configuration changes yourself rather than letting the system recreate the activity, you must update your manifest to include the relevant configuration changes [5][4]. Specifically, adding android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout" to your activity declaration will prevent the activity from restarting during these transitions [5][4]. In this case, the activity will instead receive a callback to onConfigurationChanged [5]. 4. New Default for Specific Configuration Changes: Note that starting in API 37, the system no longer restarts activities by default for several configuration changes that typically do not require full UI recreation, such as keyboard, navigation, and touch screen updates [5]. In summary, targeting API 37 mandates that your app be fully adaptive on large screens, removing previously available opt-outs for fixed orientations or aspect ratios [1][3]. Developers should focus on state preservation or explicit configuration handling to ensure a smooth user experience during the frequent resizing and rotation common in modern large-screen and multi-window environments [2][4].
Citations:
- 1: https://developer.android.com/about/versions/17/changes/ff-restrictions-ignored
- 2: https://developer.android.com/develop/adaptive-apps/guides/app-orientation-aspect-ratio-resizability
- 3: https://developer.android.com/about/versions/17/behavior-changes-17
- 4: https://developer.android.com/guide/topics/large-screens/configuration-and-continuity
- 5: https://developer.android.com/guide/topics/resources/runtime-changes
🌐 Web query:
Google Developer Android 17 SDK 37 behavior changes targetSdk compatibility
💡 Result:
Android 17 (API level 37) was released in June 2026 [1][2]. Apps targeting this version are subject to several significant behavior changes aimed at improving performance, security, and consistent UI behavior across device form factors [3][4][5]. Key behavior changes for apps targeting Android 17 (SDK 37) include: Large Screen Constraints: Apps can no longer opt out of orientation, resizability, or aspect ratio constraints on large screens (sw >= 600dp) [4][6][5]. Legacy manifest attributes (e.g., screenOrientation, resizeableActivity) and runtime APIs (e.g., setRequestedOrientation) are ignored, though games are exempt [6][5]. MessageQueue Performance: Android 17 introduces a lock-free implementation of android.os.MessageQueue [3][5]. This may break applications that use reflection to access private fields or methods within MessageQueue [3][5]. Static Field Restrictions: Apps targeting API 37 cannot modify static final fields using reflection [3]. Attempts to do so via reflection will result in an IllegalAccessException, and using JNI APIs (such as SetStaticLongField) will cause the application to crash [3]. Dynamic Code Loading (DCL): Safer DCL protections are extended to native libraries [5]. All native files loaded via System.load must be marked as read-only, or the system will throw an UnsatisfiedLinkError [5]. SMS OTP Protection: The system extends SMS OTP protection to standard SMS messages (those not using WebOTP or SMS Retriever formats) [3]. These messages are withheld from the SMS provider database and the SMS_RECEIVED_ACTION broadcast for three hours after receipt to prevent OTP hijacking [3]. Bluetooth and Keystore Limits: BluetoothSocket RFCOMM implementations now align with standard Java InputStream behavior [4]. Additionally, the system enforces a limit of 50,000 keys per app in the Android Keystore for non-system apps targeting API 37 [7]. For compatibility, developers should use the Android 17 SDK [8]. Google Play requirements mandate that new apps and updates must target at least Android 16 (API 36) by August 31, 2026 [9][10]. While migrating to API 37 is not yet a Play Store requirement as of August 2026, it is recommended for leveraging new features like the standardized Android Contact Picker [11]. Developers are encouraged to use the Android SDK Upgrade Assistant in Android Studio for assistance with these transitions [8].
Citations:
- 1: https://android-developers.googleblog.com/2026/06/Android-17.html
- 2: https://source.android.com/docs/setup/reference/build-numbers
- 3: https://developer.android.com/about/versions/17/behavior-changes-17
- 4: https://developer.android.com/about/versions/17/summary
- 5: https://developer.android.com/blog/posts/android-17-is-here
- 6: https://developer.android.com/about/versions/17/release-notes
- 7: https://developer.android.com/about/versions/17/behavior-changes-all
- 8: https://developer.android.com/about/versions/17/setup-sdk
- 9: https://developer.android.com/google/play/requirements/target-sdk
- 10: https://support.google.com/googleplay/android-developer/answer/11926878?hl=en
- 11: https://developer.android.com/about/versions/17/features
Cover Android 17 behavior changes before merging the SDK bump.
targetSdk = "37" makes apps expose Android 17 runtime changes on API 37, including large-screen resize/orientation handling, manifest restrictions being ignored, activity recreation for size changes, and changes to SMS, DCL, reflection, and socket behavior. Existing smoke testing is not enough. Add API 37 device or emulator coverage for affected flows.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@gradle/libs.versions.toml` around lines 3 - 4, Before retaining the targetSdk
and sampleTargetSdk values at 37, add device or emulator coverage for Android
17/API 37 affected flows, including large-screen resizing and orientation,
activity recreation, manifest restrictions, SMS, DCL, reflection, and socket
behavior. Ensure the tests exercise these runtime changes rather than relying
only on existing smoke tests.
Sources: Coding guidelines, MCP tools
There was a problem hiding this comment.
I assume there were no behavioural changes caused by the targetSdk bump? - Should we maybe do thorough manual testing before releasing this?
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '== Android manifest files =='
fd -t f 'AndroidManifest\.xml$' . -x sh -c 'echo "--- $1"; rg -n -i -C2 "screenOrientation|resizableActivity|maxAspectRatio|minAspectRatio|configChanges|uses-permission.*(SMS|RECEIVE)|receiver|activity" "$1" || true' sh {}
echo '== API 37 / emulator / managed-device configuration =='
rg -n -i -C3 'api.?37|sdk.?37|system.?image|managedDevices|device\s*=|avd|emulator|connectedAndroidTest' \
.github gradle . \
-g '*.yml' -g '*.yaml' -g '*.gradle' -g '*.gradle.kts' -g '*.properties' -g '*.toml' \
--glob '!build/**' || true
echo '== Compatibility-sensitive production code references =='
rg -n -i -C2 'setRequestedOrientation|requestedOrientation|onConfigurationChanged|MessageQueue|System\.load(?:Library)?\s*\(|DexClassLoader|PathClassLoader|Class\.forName|java\.lang\.reflect|BluetoothSocket|SmsManager|SMS_RECEIVED|Telephony\.Sms' \
. --glob '!**/src/test/**' --glob '!**/src/androidTest/**' --glob '!build/**' || true
echo '== Relevant test references =='
rg -n -i -C2 'ActivityScenario|recreate\s*\(|onConfigurationChanged|setRequestedOrientation|resize|multi.window|foldable|BluetoothSocket|SmsManager|DexClassLoader|System\.load|reflect' \
. --glob '**/src/test/**' --glob '**/src/androidTest/**' --glob '!build/**' || trueLength of output: 126039
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n -C3 'ANDROID_API_LEVEL|api-level:' .github/workflows \
-g 'e2e-test*.yml' -g 'e2e-test*.yaml' -g '*.yml' -g '*.yaml' || trueLength of output: 4958
@VelikovPetar, confirmed: the visible CI configuration does not run on Android 17/API 37.
Current emulator coverage is:
- E2E default: API 36.
- Backend E2E: API 34.
- Scheduled E2E: API 28.
- Snapshot tests: API 27.
The remaining recommendation is focused manual testing before release. Test the two sample applications on an Android 17/API 37 tablet or foldable emulator. Verify rotation, split-screen resizing, activity recreation, and the main chat and composer flows. This is relevant because both sample manifests set android:screenOrientation="portrait", which Android 17 can ignore on large screens.
You are interacting with an AI system.
|
|
||
| echo | ||
| echo "==> staged snapshot changes:" | ||
| git status --short -- ':(glob)**/src/test/snapshots/**' | head -30 |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
Avoid an early-closing pipeline consumer.
When more than 30 snapshot paths change, head -30 can close the pipe before git status finishes. With pipefail, the script can then exit nonzero after it stages the changes. Use a consumer that reads the complete input.
Proposed fix
-git status --short -- ':(glob)**/src/test/snapshots/**' | head -30
+git status --short -- ':(glob)**/src/test/snapshots/**' | sed -n '1,30p'📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| git status --short -- ':(glob)**/src/test/snapshots/**' | head -30 | |
| git status --short -- ':(glob)**/src/test/snapshots/**' | sed -n '1,30p' |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/paparazzi-pull-goldens.sh` at line 47, Update the snapshot-status
pipeline in the paparazzi pull-goldens script to replace the early-closing head
-30 consumer with one that reads the complete input while still limiting
displayed output to 30 lines. Preserve the existing git status filtering and
staging behavior, and ensure the pipeline remains successful under pipefail when
more than 30 snapshot paths change.
| // Derive the demo-app version from git. Replaces the gitVersioner plugin, which relied on | ||
| // the Gradle-9-removed Project.exec(Action) API. Groovy's String.execute() is plain JVM and | ||
| // Gradle-9-safe. versionCode = commit count on HEAD (monotonic); versionName = 1.<count>.<sha>. | ||
| def commitCount = "git rev-list --count HEAD".execute().text.trim() | ||
| def shortSha = "git rev-parse --short HEAD".execute().text.trim() | ||
| ext["sampleAppVersionCode"] = commitCount.toInteger() | ||
| // Interpolated "..." is a GString; .toString() coerces it to java.lang.String for `as String`. | ||
| ext["sampleAppVersionName"] = "1.${commitCount}.${shortSha}".toString() |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Use complete Git history for the version code.
Line 18 counts commits available in the current checkout. If CI uses a shallow checkout, this value can remain 1 or decrease between builds. The generated Android versionCode then is not monotonic and can block artifact distribution.
Configure every CI checkout that builds the sample app with full history, or use a CI build number that is monotonic.
#!/usr/bin/env bash
set -euo pipefail
# Inspect checkout depth in workflows that can build or distribute artifacts.
rg -n -C 4 'actions/checkout|fetch-depth|sampleAppVersion(Code|Name)' \
.github/workflows -g '*.yml' -g '*.yaml'
# Inspect the repository state used by this verification checkout.
git rev-parse --is-shallow-repository
git rev-list --count HEAD🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/sample-app-versioner.gradle` around lines 15 - 22, Ensure every CI
workflow that builds or distributes the sample app uses a complete Git checkout
by configuring the relevant actions/checkout steps with fetch-depth: 0, or
replace commitCount in the sampleAppVersionCode generation with an equivalent
monotonic CI build number. Keep sampleAppVersionName generation consistent with
the selected version source.
There was a problem hiding this comment.
Hmm this test looks suspicious - could it be an actual regression?
| minSdk = "21" | ||
| allureKotlin = "2.4.0" | ||
| androidGradlePlugin = "8.10.1" | ||
| androidGradlePlugin = "9.1.1" |
There was a problem hiding this comment.
I understand that this is necessary - but could this be a potential breaking change - breaking customer builds?
| targetSdk = "37" | ||
| sampleTargetSdk = "37" |
There was a problem hiding this comment.
I assume there were no behavioural changes caused by the targetSdk bump? - Should we maybe do thorough manual testing before releasing this?
gpunto
left a comment
There was a problem hiding this comment.
Some comments by me + Claude review.
Also, I had it create a task based on the deferred follow ups (i.e. stuff that we can't do now or are out of scope). Let me know if anything looks wrong!
https://linear.app/stream/issue/AND-1371/follow-ups-from-the-agp-9-gradle-9-compilesdk-37-migration
| shimmerCompose = "1.3.1" | ||
| shot = "6.1.0" | ||
| streamConventions = "0.13.1" | ||
| streamConventions = "0.13.2-SNAPSHOT" |
There was a problem hiding this comment.
Snapshots are pruned after some time, so this commit will start failing once that happens.
I'd publish a stable conventions version. We can use a version suffix if we want to keep the version separated from the regular ones for now.
There was a problem hiding this comment.
Can't we regenerate them locally like today? I checked on my machine and:
- When recording: some snapshots change, but the image diff shows 0 or close to 0 pixel changes
- When running validation: the check passes
I'd avoid having to rely on CI for regenerating snapshots if possible, as it will slow us down compared to today.
Also one less workflow + script to maintain.
| fetch-depth: 0 | ||
| persist-credentials: false | ||
| - uses: GetStream/android-ci-actions/actions/setup-java@main | ||
| - uses: GetStream/stream-build-conventions-android/.github/workflows/android-ci.yml@feature/rahullohra/build-on-sdk-37 |
There was a problem hiding this comment.
I wouldn't use temporary branch names. We can publish a release with a version suffix (e.g. 0.14.0-sdk37, 1.0.0-beta, or something along those lines) and point to that. Adding support in GetStream/stream-build-conventions-android#73
Aside from that, this seems incorrect. The step was using an action but now points to a workflow 🤔
| // Derive the demo-app version from git. Replaces the gitVersioner plugin, which relied on | ||
| // the Gradle-9-removed Project.exec(Action) API. Groovy's String.execute() is plain JVM and | ||
| // Gradle-9-safe. versionCode = commit count on HEAD (monotonic); versionName = 1.<count>.<sha>. |
There was a problem hiding this comment.
I'd remove this comment tbh. gitVersioner won't be in the repo anymore so it's not something a reader will care about + we don't have to justify that the execute function works.
| * Wraps [Paparazzi.snapshot], swallowing the known alpha05/layoutlib-16.2.1 teardown bugs | ||
| * (see [isPaparazziAlpha05LayoutlibBug]). The snapshot is captured before they throw. | ||
| */ | ||
| private fun snapshotSwallowingLayoutlibBugs(content: @Composable () -> Unit) { |
There was a problem hiding this comment.
This turns a golden mismatch into a green test.
I had Claude dig into it. It instrumented this method and ran the full compose suite on macOS: 398/400 snapshots take the clean path, 2 take the swallow path (AudioRecordingButtonTest > button hold and > button locked, both cashapp/paparazzi#2373). It then perturbed all four snapshots in that class by ~50% of pixels and re-ran verifyPaparazziDebug:
button idle FAIL Images differ (by 48.199162%)
button overview FAIL Images differ (by 49.103992%)
button hold PASS
button locked PASS
Paparazzi wrote a delta image for all four, so it caught the mismatch every time — but on the swallowed ones the teardown NPE masks the AssertionError and this catch eats it. The KDoc premise holds for the capture, not for the assertion: those two tests can no longer fail.
Splitting the two mechanisms keeps the coverage. They carry different risk:
dropKnownLayoutlibBugErrors()is safe to keep — it only filters Paparazzi's internal logger list by those two signatures, and a golden-mismatchAssertionErrornever lands there.- The
try/catcharoundsnapshot()is the part that eats assertions.
So: drop the try/catch, and @Ignore("cashapp/paparazzi#2373") the two tests that genuinely can't render (button hold, button locked). That leaves 398 snapshots asserting and 2 visibly disabled, instead of 400 that all look green.
| distributionBase=GRADLE_USER_HOME | ||
| distributionPath=wrapper/dists | ||
| distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip | ||
| distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip |
There was a problem hiding this comment.
It seems Gradle 9 silently disables our Gradle Enterprise plugin. The build on this branch prints:
Gradle Enterprise plugin 3.7 has been disabled as it is incompatible with this version of Gradle.
Upgrade to Gradle Enterprise plugin 3.13.1 or newer to restore functionality.
We should likely bump it (I think it's called Develocity now).
| // Align the androidx.test family across every configuration. Shot (in the sample apps) drags an | ||
| // old androidx.test (core 1.5.0 / monitor 1.6.0 / storage 1.4.2) onto the main runtime classpath | ||
| // via fragment-testing / compose-ui-test, while androidx.test.ext:junit 1.2.1 needs newer ones on | ||
| // the androidTest classpath. AGP consistent resolution then can't reconcile the two. Forcing the | ||
| // family (main included) makes both classpaths resolve the same versions. | ||
| configurations.configureEach { | ||
| resolutionStrategy { | ||
| force( | ||
| "androidx.test:core:1.6.1", | ||
| "androidx.test:core-ktx:1.6.1", | ||
| "androidx.test:runner:1.6.2", | ||
| "androidx.test:monitor:1.7.2", | ||
| "androidx.test.services:storage:1.5.0", | ||
| ) | ||
| } | ||
| } |
There was a problem hiding this comment.
It isn't Shot — it's debugImplementation(libs.androidx.fragment.testing) at stream-chat-android-ui-components-sample/build.gradle.kts:176. That's the pre-fragment-1.6 pattern (before the fragment-testing-manifest split), so it lands fragment-testing and its old androidx.test:core:1.5.0 directly on demoDebugRuntimeClasspath — which is where the strict constraint AGP can't reconcile comes from.
It's also unused: that module has no androidTest source set and never touches FragmentScenario.
I removed that line and this block together and re-resolved: ui-components-sample, compose-sample and ui-uitests androidTest classpaths all resolve, and the {strictly androidx.test:*} constraints disappear. ui-uitests does use FragmentScenario, but its existing exclude(module = "monitor") already covers it.
Worth preferring that: force silently overrides every configuration in every module, and this block leaves the debug APK shipping fragment-testing anyway (just at 1.6.1). It also means "remove when Shot is retired/updated" can never actually fire.
There was a problem hiding this comment.
Is this config needed because of some failure? I see we're setting it in code in test classes to sdk 33. Same for the ui-common counterpart




Goal
Build against compileSdk / targetSdk 37 (Android 17) and align with the shared Android toolchain (AGP 9 / Gradle 9), matching the Video SDK.
Summary
Getting to compileSdk 37 required moving the whole toolchain to AGP 9.1.1 / Gradle 9.3.1 / JDK 21 (AGP 8.10.1 doesn’t officially support API 37, and the shared-toolchain goal targets AGP 9). That, in turn, cascaded into a set of dependency, CI, build-script and test-infrastructure fixes — Gradle 9 removed several implicit conveniences the old build relied on, and Paparazzi/Shot needed rework for AGP 9 + API 37.
Notable: no production source code changes were required — chat was already on Kotlin 2.2, so none of the Kotlin-2 strictness fixes the Video SDK needed applied here. Every change below is build config, CI, or test infrastructure.
Implementation
1. Toolchain updates
compileSdktargetSdk(library lint/test)sampleTargetSdk(sample apps)2. Dependency updates
io.getstream:streamConventions(build-logic plugin)CommonExtension)androidx.baselineprofile/ benchmark (macroBenchmark)TestExtensionapp.cash.paparazziandroidx.test:coreandroidx.test.ext:junitandroidx.test:runnerandroidx.test:{core, monitor},androidx.test.services:storage(forced)androidx.collectionAudioPlayerStateusesIntFloatMap/intFloatMapOf(added in 1.4.0); the new graph resolved it too low — declared explicitly asapiorg.junit.platform:junit-platform-launcheruseJUnitPlatform()modulescom.pascalwelsch.gitversionerProject.exec(Action); unmaintained, no Gradle-9 release3. CI updates
android-ci.yml(pr-checks),sdk-size-checks.yml,pr-quality.yml@v0.13.1/ pinned SHA@feature/rahullohra/build-on-sdk-37detektjobsetup-gradleaction@v0.13.1@feature/rahullohra/build-on-sdk-37setup-javaacross workflows@main.github/workflows/record-paparazzi.ymlscripts/paparazzi-pull-goldens.shgh) and overlay them for review + commit4. Build script updates
gradle/wrapper/gradle-wrapper.propertiesgradle-8.11.1gradle-9.3.1gradle.propertiesandroid.suppressUnsupportedCompileSdk=34=37gradle.propertiesandroid.builtInKotlin=falsekotlinextension; opt out so the standalone KGP keeps working (avoids duplicate-extension crash)gradle.propertiesandroid.newDsl=falseBaseExtensioncast works (fixesTestExtension → BaseExtensioncrash)build.gradle.kts(root)alias(libs.plugins.gitversioner)build.gradle.kts(root)resolutionStrategy.force(androidx.test:*)settings.gradle.ktsresolutionStrategysettings.gradle.ktsio.getstream.*)streamConventions0.13.2-SNAPSHOT pluginbenchmark/build.gradle.ktskotlinOptions { jvmTarget = "11" }kotlin { compilerOptions { jvmTarget.set(JVM_11) } }benchmark/build.gradle.ktstestOptions.managedDevices.devices {}…managedDevices.allDevices {}ui-guides&ui-uitests/build.gradle.ktspackagingOptions {}packaging {}ui-uitests/build.gradle.ktslintOptions {}lint {}compose/build.gradle.ktscomposeCompiler { enableStrongSkippingMode = true; stabilityConfigurationFile = … }enableStrongSkippingMode;stabilityConfigurationFiles.add(…)client,ui-common,ui-components,composebuild.gradle.ktsbaselineProfileOutputDir = "."prepareReleaseArtProfilerecursively parsessrc/main(manifest, res) as ART-profile rules →Illegal token '<'ui-common/build.gradle.ktsapi(libs.androidx.collection)IntFloatMapleaks intoAudioPlayerState's public APIcore/build.gradle.ktstestRuntimeOnly(libs.junit.platform.launcher)useJUnitPlatform()module)compose&ui-components/build.gradle.ktssystemProperty("paparazzi.compileSdk", …)inTesttask-Ppaparazzi.compileSdkto the forked test workerscripts/sample-app-versioner.gradlegitVersioner { … }blockgit rev-list --count/rev-parse --short+.toString().toString()coerces the GroovyGStringso the sample'sas Stringcast doesn't fail5. Source code (production) updates
src/main/release source changes were required for compileSdk 37 — chat was already on Kotlin 2.2, so no Kotlin-2 strictness or API migrations were needed, and no newNewApilint surfaced.6. Test code (test / debug infrastructure) updates
PaparazziComposeTest.kt/PaparazziViewTest.ktPaparazzi(...)per testcreatePaparazzi(...)factory (render-SDK configurable via-Ppaparazzi.compileSdk, default 36)override val paparazzi = Paparazzi(...)= createPaparazzi(...)(and dropped the now-unusedPaparazziimport)PollMoreOptionsDialogTest.ktoverride val paparazzi: Paparazzi = …override val paparazzi = …(dropped: Paparazzi)PaparazziComposeTest.kttry/catch+ reflective logger-clearThread.setPosixNicenessInternalon a backgroundHandlerThread) and #2373 (WindowManagerImpl.removeViewNPE on Popup/Dialog teardown); the snapshot is captured before both throwui-common&composesrc/test/resources/robolectric.propertiessdk=28targetSdk=37→ pin the default low (matchesclient)compose/src/test/java/com/android/org/conscrypt/TrustManagerImpl.javaand.../src/debug/...TrustManagerImpl; when Coil builds an OkHttp client during render, OkHttp throws an uncaughtNoClassDefFoundError. The stub lets it fall back gracefully (test = Paparazzi, debug = Compose Preview)Known temporary items (remove when upstream catches up)
android.builtInKotlin=false/android.newDsl=false— AGP 9 escape hatches; removed in AGP 10. Migrate to built-in Kotlin before then.TrustManagerImplstub, error-swallow + logger-clear) — fixed upstream by layoutlib 16.2.3 (Paparazzi alpha05.2 / alpha06); remove when adopted.streamConventions 0.13.2-SNAPSHOTand the@feature/rahullohra/build-on-sdk-37CI refs — move to a released tag once the shared conventions repo publishes AGP-9 support.force(...)block — can be dropped if Shot is retired/updated (it's the source of the old transitive versions).🎨 UI Changes
None
Testing
Smoke Test the app
Summary by CodeRabbit
New Features
Bug Fixes
Tests