Javasteam wishlist demo - #1789
Conversation
Adds SteamWishlistService covering IWishlistService add/remove/read. The stored session token comes from a client login and may not carry the audience the web endpoints want, so writes try it first and fall back to a web-audience token minted from the refresh token; both paths log which one worked so we can settle which route Steam actually accepts. A featured WISHLIST action with an appid now wishlists in place and renders as Wishlisted when the game is already on the list. Everything else still opens the store page, and a failed call falls back to that too, so a bad token degrades to the old behaviour rather than a dead button. FeaturedItem gains an appId because a campaign previously had no way to name its Steam app. USE_LOCAL_FEATURED serves a hardcoded Whisk campaign so this can be exercised without a server-side campaign. It needs to go back off before this ships, and the hero endpoint needs appId in its schema for a real one to work.
The web-token approach could not work: the app only persists access/refresh tokens when rememberSession is set, so on this device PrefManager had no token and a steamid of 0, and every call fell straight through to opening the store page. Writes now go out on the already-authenticated JavaSteam connection via a Wishlist UnifiedService stub, modelled on CloudConfigStoreService, so no token is involved at all. The read still uses the public web endpoint since it only needs a steamid, taken from the live session rather than prefs; a private wishlist stays unreadable and reports unknown rather than "not wishlisted". Requires the wishlist protos, so localBuild is on and points at the local JavaSteam -23 jar. That has to go back to a published build before this merges.
The CM does route Wishlist.AddToWishlist, but a plain client session comes back AccessDenied, and the CM proto header has no access_token field to carry a web identity. So the write now falls back to the web endpoint using a token minted from the session refresh token via Authentication.GenerateAccessTokenForApp over the CM. That token was previously unavailable: PrefManager only stores it when rememberSession is set. SteamService now keeps it in memory for the session, without persisting it.
…st write" This reverts commit bfe2ff4.
The proto now carries its service block, so JavaSteam generates the stub and the hand-written one here is redundant. Points localBuild at the -25 jars, since the proto branch is based on jt/gamenative-latest rather than the older -23 line.
Pulls the inline wishlist button logic out of RecommendedGameScreen into FeaturedCtaButton, which renders any featured action: types with an in-app handler (WISHLIST, GET_DEMO) run on-device with probe/busy/done states and fall back to the action URL on failure; everything else deep-links as before. A campaign composes any subset of actions, so wishlist-only, demo-only, or both need no client changes, and a new in-app type is one InAppCta entry. GET_DEMO requests a free license over the CM (SteamService.requestFreeLicense) and shows In Library once licenses reflect it. Actions gain an optional per-action appId since a demo is its own app; it falls back to the campaign appId. The local test campaign borrows BZZZT Demo to exercise the path, as Whisk has no demo.
Whisk does ship a demo (appid 4320000, per the store's demos field); the BZZZT borrow was based on not having checked that field.
featured_conversion fires on every confirmed in-app CTA success (wishlist added, demo granted) — always, since campaign billing needs a complete count, but consent decides its shape: opted-in users send a normal identified event, opted-out users a personless one under a single-use random id, carrying only campaign_id/action_type/app_id/source. Country breakdowns come from PostHog's server-side GeoIP either way. The gated featured_action_clicked behavioral event is unchanged. JavaSteam wishlist protos are merged upstream and published (1.8.0.1-26-SNAPSHOT), so localBuild goes back off and the version bumps from -24.
The hardcoded Whisk campaign stays available for testing behind USE_LOCAL_FEATURED; the hero endpoint is live again.
The hand-built FeaturedItem test campaign skipped deserialization — the layer that actually breaks on a client/server schema mismatch. MOCK_HERO_RESPONSE now feeds a verbatim /api/games/hero JSON payload through parseHero, so the mock exercises everything a real server response would except the socket. Doubles as the payload contract for the backend campaign schema.
The two per-action failure strings collapse into one generic featured_action_failed (the fallback behavior is identical), and the demo-added snackbar goes away — the button flipping to In Library is the confirmation. The surviving four new strings (wishlisted, get-demo, in-library, failed) are translated into all 14 locales that carry the featured block.
capsuleImageUrl pointed at the 231x87 store thumbnail rather than the vertical library capsule the grid card expects, so the card rendered a tiny stretched strip. heroImageUrl had the same problem one size up (460x215 header behind a 280dp hero). Both now use the library assets from the store's asset manifest; Whisk is a recent app, so its art lives under hashed paths and the usual unhashed URLs 404.
D-pad input had no anchor: nothing on the screen ever took focus, so controller navigation was dead on arrival. Focus now lands on the primary action when the screen opens (first featured CTA, or the buy button), the same pattern LibraryAppScreen uses for its play button. CTA buttons, the buy button, and the back arrow get the shared focusRing so the focused element is actually visible.
The mock is also the backend payload contract, so a schema drift between it and the data classes should fail in CI rather than on a device.
A disabled button is not focusable, so finishing a wishlist or demo action dropped controller focus with nothing to fall back to and left the screen unnavigable — the same happened for the moment the action was in flight. The buttons now stay enabled and swallow taps while busy or done, dimming instead of greying out.
It was flipped on for device testing and got swept into the strings commit.
📝 WalkthroughWalkthroughThe PR adds app-specific featured CTA data, Steam wishlist and free-license actions, controller focus handling, conversion tracking, localized action states, mock hero data, and updated JavaSteam dependencies. ChangesFeatured campaign CTA
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant RecommendedGameScreen
participant FeaturedCtaButton
participant SteamWishlistService
participant SteamService
participant ConversionTracker
RecommendedGameScreen->>FeaturedCtaButton: render featured CTA
FeaturedCtaButton->>SteamWishlistService: execute wishlist action
FeaturedCtaButton->>SteamService: request demo license
SteamWishlistService-->>FeaturedCtaButton: return wishlist outcome
SteamService-->>FeaturedCtaButton: return license result
FeaturedCtaButton->>ConversionTracker: record successful conversion
FeaturedCtaButton-->>RecommendedGameScreen: update CTA state or open store URL
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 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: 6
🧹 Nitpick comments (1)
app/src/main/java/app/gamenative/service/SteamWishlistService.kt (1)
99-106: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winRethrow
CancellationExceptioninrunJob.
catch (e: Exception)also catchesCancellationException. That converts coroutine cancellation intoOutcome.Failed(null)and logs it as an error. Rethrow cancellation so the caller's scope cancels normally.♻️ Proposed fix
+import kotlinx.coroutines.CancellationException + private suspend fun runJob(method: String, block: suspend () -> EResult?): Outcome = try { val result = block() Timber.tag(TAG).i("$method -> $result") if (result == EResult.OK) Outcome.Success else Outcome.Failed(result) +} catch (e: CancellationException) { + throw e } catch (e: Exception) { Timber.tag(TAG).e(e, "$method failed") Outcome.Failed(null) }🤖 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 `@app/src/main/java/app/gamenative/service/SteamWishlistService.kt` around lines 99 - 106, Update SteamWishlistService.runJob to catch CancellationException separately and rethrow it before the general Exception handler; preserve the existing failure logging and Outcome.Failed(null) behavior for other exceptions.
🤖 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 `@app/src/main/java/app/gamenative/service/SteamService.kt`:
- Around line 736-749: Wrap the awaited Steam callback in requestFreeLicense
with the same 5,000 ms withTimeout guard used by getEncryptedAppTicket,
preserving the existing success logging, result validation, and exception
fallback to false.
- Around line 730-734: The GetDemo completion flow must preserve the successful
button state after requestFreeLicense succeeds, even while licenseDao has not
yet received the updated license list. Update the GetDemo-specific done/isDone
handling around SteamService.isAppInLibrary so later reads cannot reset a
locally successful result; if rejection of a previously accepted license must be
detected, implement that consistency check in a separate GetDemo path rather
than changing isAppInLibrary globally.
In `@app/src/main/java/app/gamenative/ui/screen/library/FeaturedCtaButton.kt`:
- Line 65: Update the openUrl lambda in FeaturedCtaButton to catch
ActivityNotFoundException around context.startActivity, including malformed or
unresolvable URLs, and log the failure with Timber instead of allowing the app
to crash. Preserve its use as the fallback for failed in-app actions.
In `@app/src/main/java/app/gamenative/utils/ConversionTracker.kt`:
- Around line 26-35: Update the disabled-analytics branch in the conversion
tracking flow to call PostHog.capture without the custom distinctId argument.
Keep setting properties["$process_person_profile"] to false and rely on the
SDK’s anonymous distinct ID while preserving the existing "featured_conversion"
event and properties.
In `@app/src/main/res/values-zh-rTW/strings.xml`:
- Line 2084: Update the featured_action_in_library translation to use the file’s
established “遊戲庫” term instead of “收藏庫,” matching app_library and
destination_library while preserving the existing meaning.
In `@gradle/libs.versions.toml`:
- Line 15: Update the javasteam dependency version in the version catalog to a
resolvable published artifact, or ensure the referenced gamenative-latest build
is actually published and available through the configured repositories; do not
leave the dependency pointing to the unavailable 1.8.0.1-26-SNAPSHOT artifact.
---
Nitpick comments:
In `@app/src/main/java/app/gamenative/service/SteamWishlistService.kt`:
- Around line 99-106: Update SteamWishlistService.runJob to catch
CancellationException separately and rethrow it before the general Exception
handler; preserve the existing failure logging and Outcome.Failed(null) behavior
for other exceptions.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 0e5cd98b-f6aa-46ad-816f-744942136ead
📒 Files selected for processing (26)
app/build.gradle.ktsapp/src/main/java/app/gamenative/data/Featured.ktapp/src/main/java/app/gamenative/data/RecommendationRepository.ktapp/src/main/java/app/gamenative/data/RecommendedGame.ktapp/src/main/java/app/gamenative/service/SteamService.ktapp/src/main/java/app/gamenative/service/SteamWishlistService.ktapp/src/main/java/app/gamenative/ui/screen/library/FeaturedCtaButton.ktapp/src/main/java/app/gamenative/ui/screen/library/RecommendedGameScreen.ktapp/src/main/java/app/gamenative/utils/ConversionTracker.ktapp/src/main/res/values-da/strings.xmlapp/src/main/res/values-de/strings.xmlapp/src/main/res/values-es/strings.xmlapp/src/main/res/values-fr/strings.xmlapp/src/main/res/values-it/strings.xmlapp/src/main/res/values-ja/strings.xmlapp/src/main/res/values-ko/strings.xmlapp/src/main/res/values-pl/strings.xmlapp/src/main/res/values-pt-rBR/strings.xmlapp/src/main/res/values-ro/strings.xmlapp/src/main/res/values-ru/strings.xmlapp/src/main/res/values-uk/strings.xmlapp/src/main/res/values-zh-rCN/strings.xmlapp/src/main/res/values-zh-rTW/strings.xmlapp/src/main/res/values/strings.xmlapp/src/test/java/app/gamenative/data/MockHeroResponseTest.ktgradle/libs.versions.toml
| /** Whether any owned license grants [appId]. Follows the license list, so a freshly | ||
| * granted free license flips this once Steam pushes the updated list. */ | ||
| suspend fun isAppInLibrary(appId: Int): Boolean = | ||
| instance?.licenseDao?.getAllLicenses()?.any { appId in it.appIds } == true | ||
|
|
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Inspect how FeaturedCtaButton reconciles run()/isDone() state after a successful requestFreeLicense call.
ast-grep run --pattern 'class $_ {
$$$
override suspend fun run(): Boolean = $$$
$$$
}' --lang kotlin app/src/main/java/app/gamenative/ui/screen/library/FeaturedCtaButton.kt
rg -n -B3 -A15 'isDone|fun run\(\)' app/src/main/java/app/gamenative/ui/screen/library/FeaturedCtaButton.ktRepository: utkarshdalal/GameNative
Length of output: 2127
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== FeaturedCtaButton relevant sections =="
wc -l app/src/main/java/app/gamenative/ui/screen/library/FeaturedCtaButton.kt
sed -n '1,120p' app/src/main/java/app/gamenative/ui/screen/library/FeaturedCtaButton.kt
echo
echo "== SteamService relevant sections =="
wc -l app/src/main/java/app/gamenative/service/SteamService.kt
sed -n '660,820p' app/src/main/java/app/gamenative/service/SteamService.kt
echo
echo "== CTA data/types =="
rg -n -B4 -A20 'data class FeaturedCta|enum class FeaturedCta|FeaturedCtaButton|sealed class Inapp|data class InApp' app/src/main/java/app/gamenative -g '*.kt'Repository: utkarshdalal/GameNative
Length of output: 19158
Handle license propagation for GetDemo completion.
FeaturedCtaButton sets done = true immediately when requestFreeLicense(appId) returns true, but SteamService.isAppInLibrary(appId) still reads the cached license list and can return false before Steam updates it. For the GetDemo action, keep the button done/success-only when local run() succeeds; do not let later isDone() reads that still read licenseDao flip the UI back to "not in library". If the server/client can reject a license that was previously accepted, keep a success-consistency check in a separate GetDemo path instead of reusing isAppInLibrary(appId) unchanged.
🤖 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 `@app/src/main/java/app/gamenative/service/SteamService.kt` around lines 730 -
734, The GetDemo completion flow must preserve the successful button state after
requestFreeLicense succeeds, even while licenseDao has not yet received the
updated license list. Update the GetDemo-specific done/isDone handling around
SteamService.isAppInLibrary so later reads cannot reset a locally successful
result; if rejection of a previously accepted license must be detected,
implement that consistency check in a separate GetDemo path rather than changing
isAppInLibrary globally.
| suspend fun requestFreeLicense(appId: Int): Boolean = withContext(Dispatchers.IO) { | ||
| val steamApps = instance?._steamApps ?: return@withContext false | ||
| try { | ||
| val callback = steamApps.requestFreeLicense(appId).toFuture().await() | ||
| Timber.i( | ||
| "requestFreeLicense($appId) -> ${callback.result}, " + | ||
| "apps=${callback.grantedApps}, packages=${callback.grantedPackages}", | ||
| ) | ||
| callback.result == EResult.OK && appId in callback.grantedApps | ||
| } catch (e: Exception) { | ||
| Timber.e(e, "requestFreeLicense($appId) failed") | ||
| false | ||
| } | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
Add a timeout to requestFreeLicense.
requestFreeLicense awaits the Steam callback without a timeout. If Steam never responds, the coroutine hangs indefinitely. getEncryptedAppTicket in this same file wraps its one-shot RPC in withTimeout(5_000) for the same class of risk. Apply the same guard here, since this call is triggered directly by a user tapping a featured CTA button.
🕐 Proposed fix to add a timeout
suspend fun requestFreeLicense(appId: Int): Boolean = withContext(Dispatchers.IO) {
val steamApps = instance?._steamApps ?: return@withContext false
try {
- val callback = steamApps.requestFreeLicense(appId).toFuture().await()
+ val callback = withTimeout(15_000) {
+ steamApps.requestFreeLicense(appId).toFuture().await()
+ }
Timber.i(
"requestFreeLicense($appId) -> ${callback.result}, " +
"apps=${callback.grantedApps}, packages=${callback.grantedPackages}",
)
callback.result == EResult.OK && appId in callback.grantedApps
} catch (e: Exception) {
Timber.e(e, "requestFreeLicense($appId) failed")
false
}
}📝 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.
| suspend fun requestFreeLicense(appId: Int): Boolean = withContext(Dispatchers.IO) { | |
| val steamApps = instance?._steamApps ?: return@withContext false | |
| try { | |
| val callback = steamApps.requestFreeLicense(appId).toFuture().await() | |
| Timber.i( | |
| "requestFreeLicense($appId) -> ${callback.result}, " + | |
| "apps=${callback.grantedApps}, packages=${callback.grantedPackages}", | |
| ) | |
| callback.result == EResult.OK && appId in callback.grantedApps | |
| } catch (e: Exception) { | |
| Timber.e(e, "requestFreeLicense($appId) failed") | |
| false | |
| } | |
| } | |
| suspend fun requestFreeLicense(appId: Int): Boolean = withContext(Dispatchers.IO) { | |
| val steamApps = instance?._steamApps ?: return@withContext false | |
| try { | |
| val callback = withTimeout(15_000) { | |
| steamApps.requestFreeLicense(appId).toFuture().await() | |
| } | |
| Timber.i( | |
| "requestFreeLicense($appId) -> ${callback.result}, " + | |
| "apps=${callback.grantedApps}, packages=${callback.grantedPackages}", | |
| ) | |
| callback.result == EResult.OK && appId in callback.grantedApps | |
| } catch (e: Exception) { | |
| Timber.e(e, "requestFreeLicense($appId) failed") | |
| false | |
| } | |
| } |
🤖 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 `@app/src/main/java/app/gamenative/service/SteamService.kt` around lines 736 -
749, Wrap the awaited Steam callback in requestFreeLicense with the same 5,000
ms withTimeout guard used by getEncryptedAppTicket, preserving the existing
success logging, result validation, and exception fallback to false.
| } | ||
| } | ||
|
|
||
| val openUrl = { context.startActivity(Intent(Intent.ACTION_VIEW, action.url.toUri())) } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Guard startActivity against ActivityNotFoundException.
openUrl starts an ACTION_VIEW intent without a guard. If no activity resolves the URL, or if action.url is empty or malformed, the app crashes. This path is also the failure fallback at Line 101, so a failed in-app action can turn into a crash.
🛡️ Proposed fix
- val openUrl = { context.startActivity(Intent(Intent.ACTION_VIEW, action.url.toUri())) }
+ val openUrl = {
+ try {
+ context.startActivity(Intent(Intent.ACTION_VIEW, action.url.toUri()))
+ } catch (e: ActivityNotFoundException) {
+ Timber.e(e, "no activity to open featured action url")
+ SnackbarManager.show(context.getString(R.string.featured_action_failed))
+ }
+ }Add the imports:
import android.content.ActivityNotFoundException
import timber.log.Timber📝 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.
| val openUrl = { context.startActivity(Intent(Intent.ACTION_VIEW, action.url.toUri())) } | |
| val openUrl = { | |
| try { | |
| context.startActivity(Intent(Intent.ACTION_VIEW, action.url.toUri())) | |
| } catch (e: ActivityNotFoundException) { | |
| Timber.e(e, "no activity to open featured action url") | |
| SnackbarManager.show(context.getString(R.string.featured_action_failed)) | |
| } | |
| } |
🤖 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 `@app/src/main/java/app/gamenative/ui/screen/library/FeaturedCtaButton.kt` at
line 65, Update the openUrl lambda in FeaturedCtaButton to catch
ActivityNotFoundException around context.startActivity, including malformed or
unresolvable URLs, and log the failure with Timber instead of allowing the app
to crash. Preserve its use as the fallback for failed in-app actions.
| if (PrefManager.usageAnalyticsEnabled) { | ||
| PostHog.capture(event = "featured_conversion", properties = properties) | ||
| } else { | ||
| properties["\$process_person_profile"] = false | ||
| PostHog.capture( | ||
| event = "featured_conversion", | ||
| distinctId = UUID.randomUUID().toString(), | ||
| properties = properties, | ||
| ) | ||
| } |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
PostHog Android SDK 3.8.0 capture distinctId $process_person_profile personless events
💡 Result:
In the PostHog Android SDK (including version 3.8.0), you can capture events in "personless" mode—meaning no person profile is created or updated for the event—by setting the $process_person_profile property to false within the properties dictionary of your capture call [1][2]. When this property is set to false, PostHog processes the event without creating a person record in the database, instead using a deterministic "fake" person UUID derived from the distinctId [1]. This is useful for reducing ingestion resources [1]. Example implementation: PostHog.capture( event = "your_event_name", properties = mapOf( "$process_person_profile" to false)) Important considerations: 1. Compatibility: This property is explicitly supported for standard event capture. However, you cannot use this mode with identity-related events such as $identify, $create_alias, $merge_dangerously, or $groupidentify, as these events are specifically intended to manage person profiles and will be dropped if processed without person support [3]. 2. Later Identification: If a user who previously generated "personless" events later triggers an $identify event, PostHog can create an override to link their previous anonymous/personless history to their newly identified person profile [1]. 3. SDK Defaults: Note that the Android SDK's overall event capture behavior (whether events are considered "anonymous" or "identified") is also governed by your initialization configuration (personProfiles = PersonProfiles.IDENTIFIED_ONLY by default) [4][5]. Setting $process_person_profile: false is a specific instruction to bypass person processing entirely for that individual event [1].
Citations:
- 1: https://posthog.com/handbook/engineering/person-processing
- 2: https://posthog.com/docs/api/capture
- 3: https://github.com/PostHog/posthog/blob/4a5e388e/nodejs/src/ingestion/event-processing/normalize-process-person-flag-step.ts
- 4: https://posthog.com/docs/libraries/android
- 5: https://posthog.com/docs/product-analytics/capture-events
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== candidate files =="
fd -a 'PostHog|build.gradle|ConversionTracker' . | sed 's#^\./##' | head -80
echo "== ConversionTracker context =="
file=$(fd 'ConversionTracker.kt' . | head -1 | xargs -r readlink -f || true)
if [ -n "$file" ]; then
wc -l "$file"
cat -n "$file" | sed -n '1,80p'
fi
echo "== gradle posthog refs =="
rg -n --glob '*.gradle*' --glob '*.kts' 'posthog|PostHog|use ' . | head -80
echo "== local PostHog capture usage =="
rg -n 'PostHog\.capture|capture\s*\(' . --glob '*.kt' --glob '*.kts' --glob '*.java' | head -100Repository: utkarshdalal/GameNative
Length of output: 8828
🌐 Web query:
PostHog Android SDK 3.8.0 API capture distinctId signature Kotlin capture(event distinctId properties)
💡 Result:
In the PostHog Android SDK (including version 3.8.0), the capture method does not accept distinctId as a parameter in its primary signature [1][2]. The PostHog Android SDK manages the distinctId automatically based on the user's identification state (anonymous or identified) to ensure data consistency [3]. The standard Kotlin signature for the capture method is [1][4]: PostHog.capture( event: String, properties: Map<String, Any>? = null, userProperties: Map<String, Any>? = null, userPropertiesSetOnce: Map<String, Any>? = null, groupProperties: Map<String, Any>? = null) If you need to associate an event with a specific user, you should use the identify method to set the user's distinctId before calling capture [5][3]. Example usage: // Identify the user first PostHog.identify("your_distinct_id") // Capture the event PostHog.capture( event = "user_signed_up", properties = mapOf("is_free_trial" to true)) The properties parameter is a map used to include additional context with the event [4][6]. If you are attempting to override the distinctId on a per-event basis, this is generally not supported by the Android SDK's capture API, as identifying a user is intended to be a session-level or persistent state change [5][3].
Citations:
- 1: https://github.com/PostHog/posthog-android/blob/main/posthog-android/USAGE.md
- 2: https://posthog.com/docs/libraries/android
- 3: https://posthog.com/docs/product-analytics/capture-events
- 4: https://github.com/PostHog/skills/blob/main/skills/omnibus/instrument-integration/references/android.md
- 5: https://posthog.com/docs/product-analytics/installation/android
- 6: https://github.com/PostHog/posthog.com/blob/master/contents/docs/libraries/android/index.mdx
🌐 Web query:
PostHog Android 3.8.0 PostHog.capture distinctId Kotlin source
💡 Result:
In the PostHog Android SDK (including version 3.8.0), the PostHog.capture method does not accept a distinctId as an argument [1][2][3]. PostHog's mobile SDKs are designed to manage the distinctId automatically [4]. When using the Android SDK, the library generates and persists an anonymous distinctId for the user, which is then automatically attached to all events captured via PostHog.capture [2][3][4]. To associate events with a specific user (a known distinctId), you should use the PostHog.identify method instead of attempting to pass an ID during capture [5][4]. Kotlin usage example for capturing an event: import com.posthog.PostHog // Capture an event with the currently identified or anonymous distinctId PostHog.capture( event = "user_signed_up", properties = mapOf("is_free_trial" to true)) Kotlin usage example for identifying a user: import com.posthog.PostHog // Associate future events with this specific distinctId PostHog.identify( distinctId = "your_user_id_or_email", userProperties = mapOf("name" to "Max Hedgehog")) If you need to retrieve the current distinctId being used by the SDK, you can call PostHog.distinctId [1][6]. Version 3.8.0 of the PostHog Android SDK was released on October 3, 2024 [7][8].
Citations:
- 1: https://github.com/PostHog/posthog-android/blob/main/posthog-android/USAGE.md
- 2: https://posthog.com/docs/product-analytics/installation/android
- 3: https://posthog.com/docs/product-analytics/capture-events
- 4: https://posthog.com/docs/getting-started/identify-users.md
- 5: https://posthog.com/docs/product-analytics/identify.md
- 6: https://github.com/PostHog/posthog-android/blob/main/posthog/USAGE.md
- 7: Update dependency com.posthog:posthog-android to v3.8.0 element-hq/element-x-android#3591
- 8: https://repo1.maven.org/maven2/com/posthog/posthog-android/
🌐 Web query:
site:github.com/PostHog/posthog-android 3.8.0 PostHog.capture distinctId override posthog-android
💡 Result:
In the PostHog Android SDK, there is no direct "override" method for the distinctId during event capture. Instead, PostHog follows a standard identity lifecycle [1]. To manage the distinctId, use the following patterns: 1. Identify Users: Use the identify method to associate a specific distinctId with a user [1]. Note that once a user is identified, subsequent calls to identify may be restricted or ignored if the user is already identified [1]. 2. Bootstrap Identity: If you need to set a known identity or distinctId on the very first launch (before the SDK initializes or fetches its own state), use the PostHogConfig.bootstrap property [2]. This allows you to seed the identity (and feature flag state) before any network requests occur [2]. 3. Reset: If you need to change the user or distinctId (e.g., after a logout), you must call the reset method first [1]. This clears the current identity and allows for a new session or user identification [1]. 4. Alias: If you want to merge two different distinctIds (e.g., an anonymous ID and a registered user ID), use the alias method [1]. For checking the current ID, you can use the PostHog.distinctId method to read the identifier currently in use by the SDK [3][4]. Always refer to the official PostHog Android documentation for the specific version you are using, as SDK behaviors regarding identity persistence and identification can evolve [5][6].
Citations:
- 1:
identifyshould not allow already identified users PostHog/posthog-android#108 - 2: feat: add bootstrap support for identity and feature flag seeding PostHog/posthog-android#618
- 3: https://github.com/PostHog/posthog-android/blob/main/posthog-android/USAGE.md
- 4: https://github.com/PostHog/posthog-android/blob/main/posthog/USAGE.md
- 5: https://github.com/PostHog/posthog-android/
- 6: https://github.com/PostHog/posthog-android/blob/main/README.md
🌐 Web query:
PostHog Android SDK capture(event distinctId Kotlin signature
💡 Result:
In the PostHog Android SDK, the capture method does not take a distinctId parameter directly [1][2][3]. The SDK automatically manages the distinctId based on the current user session [1][4]. To associate events with a specific user, you must use the identify method to set the distinctId before calling capture [1][4]. 1. Use PostHog.identify(distinctId = "your-user-id") to set the identity [4]. 2. Subsequent calls to PostHog.capture(event = "event-name") will automatically associate the event with that distinctId [1][4]. The standard Kotlin signature for capturing an event is: PostHog.capture( event: String, properties: Map<String, Any>? = null, options: CaptureOptions? = null) If you need to change the user or associate an event with a different identifier, you should call identify again with the new ID, provided the session allows it [4]. If you are working in a context where you must explicitly manage user context for a single event (such as in server-side scenarios), the Android/Kotlin client SDK design relies on the persistent identity set via identify [4].
Citations:
- 1: https://posthog.com/docs/libraries/android
- 2: https://github.com/PostHog/posthog-android/blob/main/posthog/USAGE.md
- 3: https://github.com/PostHog/posthog-android/blob/main/posthog-android/USAGE.md
- 4: https://posthog.com/docs/product-analytics/identify.md
🌐 Web query:
PostHog Android CaptureOptions distinctId captureOptions posthog-android
💡 Result:
In the PostHog Android SDK, distinct IDs are managed via the PostHog API rather than through a direct CaptureOptions object [1][2]. Key points regarding distinct ID and capturing in the Android SDK: 1. Identifying Users: You do not provide a distinct ID within a CaptureOptions parameter when capturing an event. Instead, you use the PostHog.identify(distinctId) method [3]. Once identify is called, the SDK persists this ID and associates it with all subsequent events until you call PostHog.reset [3]. 2. Capturing Events: When using the capture method, you provide the event name and optional properties [4][2]. There is no requirement to manually pass a distinct ID for each capture call, as the SDK handles this automatically using the currently identified (or anonymous) ID [5][6]. 3. Retrieving the ID: You can check the currently active distinct ID at any time by calling PostHog.distinctId [1][7]. 4. CaptureOptions: While some PostHog SDKs (such as those for Kotlin Multiplatform) utilize a CaptureOptions object for attaching group context or custom timestamps to individual events [8], the core PostHog Android SDK primarily manages configuration and identification at the global level during initialization or via dedicated methods like identify and group [1][2][3]. For standard Android implementations, prioritize using PostHog.identify to associate events with a specific user [3]. If you are working in a complex environment like Kotlin Multiplatform, verify if your specific SDK version supports the CaptureOptions object for events [8].
Citations:
- 1: https://posthog.com/docs/libraries/android
- 2: https://posthog.com/docs/product-analytics/installation/android
- 3: https://posthog.com/tutorials/android-analytics
- 4: https://github.com/PostHog/posthog-android/blob/main/posthog/USAGE.md
- 5: https://posthog.com/docs/product-analytics/identify
- 6: https://posthog.com/docs/getting-started/identify-users
- 7: https://github.com/PostHog/posthog-android/blob/main/posthog-android/USAGE.md
- 8: https://posthog.com/docs/libraries/kmp
🌐 Web query:
github PostHog posthog-android 3.8.0 PostHog.kt capture distinctId
💡 Result:
In the PostHog Android SDK (including version 3.8.0), you associate a user with a specific identifier by using the PostHog.identify method [1][2][3]. To identify a user, call the method with their unique identifier (such as an email or database ID) [1][2]: import com.posthog.PostHog PostHog.identify( distinctId = "your_user_unique_id", userProperties = mapOf( "name" to "Max Hedgehog", "email" to "max@hedgehogmail.com")) Key points regarding distinctId in the Android SDK: 1. Automatic vs. Manual: If you do not call identify, the SDK automatically generates and uses an anonymous distinct ID [1][4]. Once you call identify with a specific ID, the SDK associates subsequent events with that identity [2][5]. 2. Retrieving the current ID: You can retrieve the current distinct ID (whether it is the generated anonymous ID or an ID set via identify) by calling PostHog.distinctId [1][6]. 3. Aliasing: If you need to link multiple distinct IDs to a single user (e.g., merging an anonymous web ID with an authenticated mobile ID), use the PostHog.alias method [1][7]. 4. Release Context: Version 3.8.0 of posthog-android was released on October 3, 2024 [8]. It primarily included minor features and dependency updates [8]. The usage of identify and distinctId remains consistent with the standard patterns for the PostHog Android SDK [1][6]. For full implementation details, refer to the official PostHog Android documentation [1][9].
Citations:
- 1: https://posthog.com/docs/libraries/android
- 2: https://posthog.com/tutorials/android-analytics
- 3: https://posthog.com/docs/product-analytics/identify.md
- 4: https://posthog.com/docs/getting-started/identify-users
- 5: https://posthog.com/docs/product-analytics/identify
- 6: https://github.com/PostHog/posthog-android/blob/main/posthog/USAGE.md
- 7: https://github.com/PostHog/posthog.com/blob/master/contents/docs/libraries/android/index.mdx
- 8: Update dependency com.posthog:posthog-android to v3.8.0 element-hq/element-x-android#3591
- 9: https://posthog.com/docs/product-analytics/installation/android
🌐 Web query:
PostHog Android SDK distinctId capture override private distinctId CaptureOptions distinctId Kotlin
💡 Result:
In the PostHog Android SDK, you cannot manually override the distinctId on a per-event basis through CaptureOptions [1]. PostHog is designed to maintain a consistent identity for a user session once they have been identified [2]. To manage or change the distinctId, use the following official methods: 1. Identify the user: Use PostHog.identify(distinctId) to associate events with a specific user [1][3]. Once identified, this distinctId will be used for all subsequent events captured by the SDK [4][3]. 2. Aliasing: If you need to associate multiple distinct IDs with a single user (e.g., merging an anonymous ID with a known user ID), use PostHog.alias("new_distinct_id") [3]. 3. Reset: To switch users or clear the current identity, call PostHog.reset(). This unlinks the person profile and forces the SDK to generate a new anonymous distinctId for future events [5]. 4. Bootstrapping: If you know the user's identity before the SDK initializes (e.g., from a backend session token), you can use PostHogBootstrapConfig before calling PostHog.setup() [6]. This ensures events captured early in the application lifecycle are associated with the correct ID [4][6]. If you need to retrieve the current ID, you can call PostHog.distinctId() [7][3]. Avoid attempting to inject a custom distinctId directly into capture calls, as the SDK enforces identity consistency internally [2].
Citations:
- 1: https://posthog.com/docs/libraries/kmp
- 2: https://posthog.com/docs/product-analytics/identify
- 3: https://github.com/PostHog/skills/blob/main/skills/omnibus/instrument-integration/references/android.md
- 4: https://posthog.com/docs/libraries/android
- 5: https://posthog.com/docs/data/anonymous-vs-identified-events
- 6: https://posthog.com/docs/libraries/bootstrapping
- 7: https://github.com/PostHog/posthog-android/blob/main/posthog-android/USAGE.md
Use capture() without passing a custom distinctId.
PostHog Android 3.8.0 does not expose PostHog.capture(..., distinctId = ..., ...), so this call will not compile as written. Set the personless intent via properties["$process_person_profile"] = false, and rely on the SDK’s anonymous distinct ID instead of trying to supply one here.
🤖 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 `@app/src/main/java/app/gamenative/utils/ConversionTracker.kt` around lines 26
- 35, Update the disabled-analytics branch in the conversion tracking flow to
call PostHog.capture without the custom distinctId argument. Keep setting
properties["$process_person_profile"] to false and rely on the SDK’s anonymous
distinct ID while preserving the existing "featured_conversion" event and
properties.
| <string name="featured_action_wishlist_on">在 %1$s 加入願望清單</string> | ||
| <string name="featured_action_wishlisted">已加入願望清單</string> | ||
| <string name="featured_action_get_demo">取得試玩版</string> | ||
| <string name="featured_action_in_library">已在收藏庫中</string> |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Align "in library" wording with the file's established term.
featured_action_in_library uses "收藏庫" (collection). This file uses "遊戲庫" for the library concept everywhere else, for example in app_library and destination_library. Use the same term here to avoid implying a different concept (a saved/wishlist collection instead of an owned game).
💬 Proposed wording fix
- <string name="featured_action_in_library">已在收藏庫中</string>
+ <string name="featured_action_in_library">已在遊戲庫中</string>📝 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.
| <string name="featured_action_in_library">已在收藏庫中</string> | |
| <string name="featured_action_in_library">已在遊戲庫中</string> |
🤖 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 `@app/src/main/res/values-zh-rTW/strings.xml` at line 2084, Update the
featured_action_in_library translation to use the file’s established “遊戲庫” term
instead of “收藏庫,” matching app_library and destination_library while preserving
the existing meaning.
| play-integrity = "1.6.0" # https://mvnrepository.com/artifact/com.google.android.play/integrity | ||
| hiltNavigationCompose = "1.2.0" # https://mvnrepository.com/artifact/androidx.hilt/hilt-navigation-compose | ||
| javasteam = "1.8.0.1-24-SNAPSHOT" # https://github.com/joshuatam/JavaSteam/tree/gamenative-latest | ||
| javasteam = "1.8.0.1-26-SNAPSHOT" # https://github.com/joshuatam/JavaSteam/tree/gamenative-latest |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Check whether the JavaSteam snapshot version is referenced consistently and confirm the repository URL configuration.
rg -n "javasteam" gradle/libs.versions.toml app/build.gradle.ktsRepository: utkarshdalal/GameNative
Length of output: 1116
🌐 Web query:
io.github.joshuatam javasteam 1.8.0.1-26-SNAPSHOT maven central repository
💡 Result:
The requested artifact, io.github.joshuatam:javasteam:1.8.0.1-26-SNAPSHOT, is not a standard or documented release for the JavaSteam library [1][2][3]. The primary JavaSteam library is maintained under the Maven group ID in.dragonbra [1][4]. Joshua Tam is a contributor to the JavaSteam project [1][5][2], but there is no evidence of a distinct io.github.joshuatam repository or group for this library in Maven Central. Snapshot versions for the JavaSteam library are typically hosted in the Sonatype snapshot repository [1][6], not via a joshuatam group ID. If you are attempting to use a specific snapshot version, it is recommended to verify the correct dependency coordinates in your project build file: Group: in.dragonbra Artifact: javasteam You can reference the official repository and build instructions via the project's GitHub page at https://github.com/Longi94/JavaSteam [1].
Citations:
- 1: https://github.com/Longi94/JavaSteam
- 2: https://github.com/Longi94/JavaSteam/releases
- 3: https://github.com/Longi94/JavaSteam/releases/tag/1.8.0
- 4: https://repo1.maven.org/maven2/in/dragonbra/javasteam/1.2.0/
- 5: https://github.com/joshuatam
- 6: https://context7.com/longi94/javasteam/llms.txt
🌐 Web query:
repo1.maven.org/maven2/io/github/joshuatam/javasteam/1.8.0.1-26-SNAPSHOT
💡 Result:
The artifact identified as io.github.joshuatam:javasteam:1.8.0.1-26-SNAPSHOT is a development snapshot version of the JavaSteam library, a project that provides an interface to interact with Steam's servers [1]. This specific artifact is associated with a fork or personal repository of JavaSteam maintained by user joshuatam [1]. It is not part of the primary, official JavaSteam distribution, which is hosted under the Maven coordinates in.dragonbra:javasteam [2][3][4]. Development in the community frequently involves creating forks or branches to implement specific features, optimizations, or bug fixes (such as query parameter handling or Android optimizations) that are then tested in downstream projects like GameNative [5][6]. The version suffix -26-SNAPSHOT indicates that this is the 26th build of the 1.8.0.1-SNAPSHOT development cycle within that specific fork's CI/CD pipeline [6]. Users looking for the official, stable, or main development versions of JavaSteam should use the in.dragonbra group ID [2][3][4]. The official project releases are available via Maven Central, and users should verify their dependency coordinates against the official project repository to ensure they are using the intended version [2][4].
Citations:
- 1: https://github.com/joshuatam
- 2: https://github.com/Longi94/JavaSteam
- 3: https://context7.com/longi94/javasteam/llms.txt
- 4: https://github.com/utkarshdalal/JavaSteam
- 5: https://github.com/Longi94/JavaSteam/releases/tag/1.8.0
- 6: fix: javasteam query params handling #1652
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Print the relevant catalog/version repository configuration without running repository code.
echo "gradle/libs.versions.toml version and dependency entries:"
sed -n '10,20p;74,82p' gradle/libs.versions.toml
echo
echo "app/build.gradle.kts dependency block context:"
sed -n '320,345p' app/build.gradle.kts
echo
echo "Repository declarations for JavaSteam/Snapshot repositories:"
rg -n "maven|snapshot|joshua|dragoon|depotdownloader" build.gradle.kts settings.gradle.kts gradle -g '*.kts' -g '*.toml' -g '*.gradle' || trueRepository: utkarshdalal/GameNative
Length of output: 3914
🌐 Web query:
"central.sonatype.com/repository/maven-snapshots" "io.github.joshuatam:javasteam"
💡 Result:
The Java library for interacting with Steam servers is named JavaSteam, maintained primarily under the repository Longi94/JavaSteam [1][2][3]. The correct Maven coordinate for this library is in.dragonbra:javasteam, not io.github.joshuatam:javasteam [1][4][3]. While joshuatam is listed as a contributor to the JavaSteam project [4][2][3], they are not the maintainer of the project's primary Maven artifact. If you are looking to use JavaSteam, you should use the following dependency configuration [1][3][5]: Maven: in.dragonbra javasteam x.y.z Gradle: implementation 'in.dragonbra:javasteam:x.y.z' Regarding the central.sonatype.com/repository/maven-snapshots/ URL, this is the official repository used by the Maven Central Publishing Portal for hosting snapshot versions of projects [6]. Although JavaSteam documentation historically mentions that snapshots may be available via the Sonatype repository [4][3][5], the project itself is hosted under the in.dragonbra group ID [1][4]. If you are attempting to consume a snapshot, you would need to configure your build tool to include this repository URL with specific content filters for the correct group ID [6].
Citations:
- 1: https://github.laiyagushi.com/Longi94/JavaSteam
- 2: https://github.com/Longi94/JavaSteam/releases
- 3: https://github.com/longi94/javasteam
- 4: https://github.com/jeremybernstein/JavaSteam
- 5: https://github.com/Longi94/JavaSteam
- 6: https://central.sonatype.org/publish/publish-portal-snapshots/
🌐 Web query:
github.com/joshuatam/JavaSteam maven deploy github pages io.github.joshuatam
💡 Result:
The GitHub repository joshuatam/JavaSteam is a project that provides a Java library for interacting with Valve's Steam servers [1][2]. It is not currently associated with a maven deployment to GitHub Pages under the io.github.joshuatam namespace. The JavaSteam project is primarily maintained and distributed through other community forks (such as Longi94/JavaSteam), which manage their releases via Maven Central under the in.dragonbra groupId [3][4][5]. Regarding the concepts mentioned in your query: 1. Maven Deployment to GitHub Packages: You can automate the deployment of Java artifacts to GitHub Packages using GitHub Actions. This involves configuring your pom.xml with the appropriate distribution management section and using a workflow with actions/setup-java to authenticate via GITHUB_TOKEN [6]. 2. Maven Site to GitHub Pages: Publishing a Maven site (generated documentation) to GitHub Pages is a distinct process from deploying Maven artifacts. This is typically achieved using plugins like maven-scm-publish-plugin to push site contents to the gh-pages branch of a repository [7]. 3. Namespace io.github.joshuatam: If you intend to publish to Maven Central under this namespace, you must verify ownership of the io.github.joshuatam coordinate through the Central Portal (Sonatype), which confirms your GitHub account identity [8]. There is no evidence that joshuatam/JavaSteam currently implements a Maven-to-GitHub-Pages workflow or uses the io.github.joshuatam group ID for distribution [1].
Citations:
- 1: https://github.com/joshuatam
- 2: https://github.com/danbruno/JavaSteam
- 3: https://github.com/longi94/javasteam
- 4: https://github.com/Longi94/JavaSteam/releases
- 5: https://github.com/utkarshdalal/JavaSteam
- 6: https://docs.github.com/actions/guides/publishing-java-packages-with-maven
- 7: https://www.lorenzobettini.it/2020/01/publishing-a-maven-site-to-github-pages/
- 8: https://github.com/deepaksorthiya/java-maven-github-and-mavencentral-deploy
🌐 Web query:
"utkarshdalal/JavaSteam" "maven" "io.github.joshuatam"
💡 Result:
The JavaSteam library, which provides an interface to interact with Valve's Steam network, is primarily maintained under the group ID in.dragonbra in the Maven Central repository [1][2]. There is no standard or widely used Maven artifact associated with io.github.joshuatam for JavaSteam [3]. While Joshua Tam is a contributor to the JavaSteam library (specifically credited for work on the depot downloader implementation) [4][3], the official releases are distributed via in.dragonbra:javasteam [2][5]. Several GitHub repositories, such as utkarshdalal/JavaSteam and Longi94/JavaSteam, appear to be references or mirrors for the same underlying project managed by in.dragonbra [1][6][7]. You should use the following dependency configuration for your project [8]: in.dragonbra javasteam 1.8.0 Always verify the latest version available on the Maven Central repository [2].
Citations:
- 1: https://github.com/utkarshdalal/JavaSteam
- 2: https://mvnrepository.com/artifact/in.dragonbra/javasteam
- 3: https://github.com/joshuatam
- 4: https://github.com/Longi94/JavaSteam/releases/tag/1.8.0
- 5: https://central.sonatype.com/artifact/in.dragonbra/javasteam/1.7.0
- 6: https://github.com/Longi94/JavaSteam
- 7: https://github.com/longi94/javasteam
- 8: https://context7.com/longi94/javasteam/llms.txt
🌐 Web query:
utkarshdalal/JavaSteam javasteam-1.8.0.1-26-SNAPSHOT
💡 Result:
The identifier javasteam-1.8.0.1-26-SNAPSHOT refers to a development snapshot version of the JavaSteam library, which is a Java port of SteamKit2 used to interface with Valve's Steam servers [1][2]. This specific version is used as a dependency in GameNative, an open-source project by Utkarsh Dalal that enables native PC gaming on Android [3][4]. GameNative uses a modified or extended version of the JavaSteam library to handle core Steam functionality, such as authentication, game library management, and content downloads [5][3]. Key points regarding this artifact include: - Relationship: While the primary JavaSteam library is maintained at github.com/Longi94/JavaSteam, the GameNative project frequently integrates custom snapshots and contributions (including those by Utkarsh Dalal) to optimize the library for Android performance and compatibility [2][6][3][7]. - Usage: The version string format (e.g., 1.8.0.1-26-SNAPSHOT) represents a specific build in the development cycle of the library as it is used within the GameNative ecosystem [8]. Developers working on or building GameNative locally may reference these snapshot JARs directly in their build configurations (e.g., in build.gradle.kts) [9][8]. - Context: These snapshots are often created to fix specific issues or implement features required for GameNative, such as handling query parameters for API requests or optimizing download processes [5][8].
Citations:
- 1: https://github.com/utkarshdalal/JavaSteam
- 2: https://github.com/Longi94/JavaSteam/releases
- 3: https://github.com/utkarshdalal/GameNative
- 4: https://github.com/utkarshdalal
- 5: https://deepwiki.com/utkarshdalal/GameNative/5.1-steam-integration
- 6: https://github.com/Longi94/JavaSteam/releases/tag/1.8.0
- 7: https://newreleases.io/project/github/utkarshdalal/GameNative/release/v0.5.2
- 8: fix: javasteam query params handling #1652
- 9: https://github.com/utkarshdalal/GameNative/blob/af4cec05/app/build.gradle.kts
🌐 Web query:
site:joshuatam.github.io/JavaSteam "1.8.0.1-26-SNAPSHOT"
💡 Result:
No indexed results were found for:
site:joshuatam.github.io/JavaSteam "1.8.0.1-26-SNAPSHOT"
🌐 Web query:
site:raw.githubusercontent.com/joshuatam/JavaSteam "1.8.0.1-26-SNAPSHOT"
💡 Result:
No indexed results were found for:
site:raw.githubusercontent.com/joshuatam/JavaSteam "1.8.0.1-26-SNAPSHOT"
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Git history for JavaSteam version references (concise)"
git -C .. log --oneline --all -- gradle/libs.versions.toml app/build.gradle.kts gradle/libs.versions.toml | head -20 2>/dev/null || true
echo
echo "Dependency comments and localBuild references"
rg -n "gamenative-latest|localBuild|javasteam-1\.8\.0\.1-2(4|5|6)-SNAPSHOT|javasteam-depotdownloader-1\.8\.0\.1-2(4|5|6)-SNAPSHOT" gradle appRepository: utkarshdalal/GameNative
Length of output: 898
🌐 Web query:
joshuatam.jfrog.io javasteam 1.8.0.1-26-SNAPSHOT
💡 Result:
The term javasteam refers to a Java library that provides an interface to interact with Valve's Steam servers [1][2]. It is a port of the.NET library SteamKit2 and targets Java 17 [1][2]. The specific artifact version 1.8.0.1-26-SNAPSHOT is a development snapshot build of this library. These snapshot versions are frequently referenced in projects like GameNative [3], a project that includes contributions from joshuatam (Joshua Tam) [4]. Development snapshots, such as the ...-21-SNAPSHOT or ...-26-SNAPSHOT series, are often managed in custom or local Gradle dependency configurations to incorporate specific fixes—such as improvements to query parameter handling—before they are formally released in the main in.dragonbra:javasteam Maven artifact [3][1][2]. Users encountering this specific snapshot typically find it within the dependency declarations of projects that use JavaSteam as a library, rather than as a publicly hosted artifact on standard repositories like Maven Central [3]. You can find the main source code and releases for the library at the official repository [5][2].
Citations:
- 1: https://github.laiyagushi.com/Longi94/JavaSteam
- 2: https://github.com/longi94/javasteam
- 3: fix: javasteam query params handling #1652
- 4: https://github.com/joshuatam
- 5: https://github.com/Longi94/JavaSteam/releases
🌐 Web query:
io.github.joshuatam javasteam 1.8.0.1-26-SNAPSHOT "maven-snapshots"
💡 Result:
The artifact 1.8.0.1-26-SNAPSHOT refers to a specific development snapshot build of JavaSteam, a Java port of SteamKit2 [1]. While user "joshuatam" is a contributor to the official JavaSteam project (maintained under the Longi94/JavaSteam repository) [2][3][4], the library itself is published under the Maven Group ID in.dragonbra [2][5][4]. There is no official artifact group ID io.github.joshuatam for this library; references to such versions often appear in downstream projects (such as GameNative) that track specific SNAPSHOT builds of the upstream library for testing or integration purposes [6]. Key points regarding this artifact: * Origin: The core project is located at github.com/Longi94/JavaSteam [2][1]. * Group ID: Official releases and snapshots are published under in.dragonbra:javasteam [2][5][1]. * Snapshot Usage: Snapshot versions like 1.8.0.1-26-SNAPSHOT are typically hosted in the Sonatype maven-snapshots repository [5][4][1]. They are used by developers to access the latest commits (such as fixes for query parameter handling or dependency updates) before they are promoted to a formal release [6]. * Context: Projects like GameNative often track these specific build numbers (e.g., -21-SNAPSHOT, -26-SNAPSHOT) in their build configuration files (libs.versions.toml or build.gradle.kts) to maintain compatibility with the latest developments in the JavaSteam codebase [6].
Citations:
- 1: https://github.com/Longi94/JavaSteam
- 2: https://github.com/longi94/javasteam
- 3: https://github.com/Longi94/JavaSteam/releases/tag/1.8.0
- 4: https://github.com/utkarshdalal/JavaSteam
- 5: https://context7.com/longi94/javasteam/llms.txt
- 6: fix: javasteam query params handling #1652
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf 'Attempt Maven repository path probe:\n'
for base in \
"https://repo1.maven.org/maven2" \
"https://central.sonatype.com/repository/maven-snapshots"
do
url="$base/io/github/joshuatam/javasteam/1.8.0.1-26-SNAPSHOT/javasteam-1.8.0.1-26-SNAPSHOT.pom"
printf '%s\n' "$url"
if command -v curl >/dev/null 2>&1; then
curl -fsSIL "$url" | sed -n '1,5p' || true
else
printf 'curl not available\n'
fi
doneRepository: utkarshdalal/GameNative
Length of output: 873
Resolve the JavaSteam snapshot before merging.
io.github.joshuatam:javasteam:1.8.0.1-26-SNAPSHOT is not available from the configured Maven Central / Maven-snapshots repositories. This external SNAPSHOT is resolved with isChanging = true; use a published artifact, update the gamenative-latest checkout to actually contain and publish this build, or add a configured repository that hosts it.
🤖 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 15, Update the javasteam dependency
version in the version catalog to a resolvable published artifact, or ensure the
referenced gamenative-latest build is actually published and available through
the configured repositories; do not leave the dependency pointing to the
unavailable 1.8.0.1-26-SNAPSHOT artifact.
There was a problem hiding this comment.
6 issues found and verified against the latest diff
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="app/build.gradle.kts">
<violation number="1" location="app/build.gradle.kts:334">
P3: This edit bumps only the inactive `localBuild` branch (localBuild=false), so it has no effect on the actual dependency resolution — the active `else` branch uses `libs.javasteam` from the version catalog, which is already at 1.8.0.1-26-SNAPSHOT. The local-build paths are now one version behind the catalog, creating a confusing mismatch for anyone who flips localBuild on. Consider instead bumping the version in gradle/libs.versions.toml (the path that actually affects the build), or dropping this dead-branch edit to keep the diff scoped to functional changes.</violation>
</file>
<file name="app/src/main/java/app/gamenative/service/SteamService.kt">
<violation number="1" location="app/src/main/java/app/gamenative/service/SteamService.kt:731">
P2: Rendering multiple demo CTAs repeatedly loads the entire license table per app, which scales with the user's full library rather than the featured items. Use a cached/indexed entitlement lookup or batch these app IDs instead.
(Based on your team's feedback about scaling library lookups.) [FEEDBACK_USED]</violation>
<violation number="2" location="app/src/main/java/app/gamenative/service/SteamService.kt:736">
P2: requestFreeLicense awaits the Steam callback without a timeout, so if Steam never responds the coroutine hangs indefinitely. This call is triggered directly from a user tapping the featured CTA button, so consider wrapping the await in withTimeout(...) similar to getEncryptedAppTicket elsewhere in this file.</violation>
<violation number="3" location="app/src/main/java/app/gamenative/service/SteamService.kt:742">
P2: Leaving the screen while the license request is pending is treated as a failed request and can open the fallback URL from a cancelled UI coroutine. Rethrow `CancellationException` before handling ordinary failures.</violation>
</file>
<file name="app/src/main/java/app/gamenative/data/RecommendationRepository.kt">
<violation number="1" location="app/src/main/java/app/gamenative/data/RecommendationRepository.kt:22">
P3: This PR ships the demo scaffold inside production code: `MOCK_HERO_RESPONSE` is a hardcoded `false`, making the `parseHero(MOCK_HERO_JSON)` branch unreachable at runtime, so the large embedded payload (a real 'Whisk' campaign with its actual Steam appid, asset URLs, and a separate demo appid) is effectively a test fixture compiled into the app. It's only used by `MockHeroResponseTest`. This adds noise and embeds a real third-party/Steam campaign in the shipped binary just to power the demo. Consider moving the mock payload and a `demo`/debug toggle out of the production `RecommendationRepository` (e.g. into the test source set or behind a debug-only/flag-gated layer), so the demo doesn't ship with the app.</violation>
</file>
<file name="app/src/main/res/values-zh-rTW/strings.xml">
<violation number="1" location="app/src/main/res/values-zh-rTW/strings.xml:2084">
P3: The new featured_action_in_library string uses "收藏庫" while this file consistently uses "遊戲庫" elsewhere (e.g. app_library, destination_library) for the library concept. Using a different term here could confuse users into thinking this is a separate saved/wishlist collection rather than the owned-games library.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| } | ||
|
|
||
| suspend fun isAppInLibrary(appId: Int): Boolean = | ||
| instance?.licenseDao?.getAllLicenses()?.any { appId in it.appIds } == true |
There was a problem hiding this comment.
P2: Rendering multiple demo CTAs repeatedly loads the entire license table per app, which scales with the user's full library rather than the featured items. Use a cached/indexed entitlement lookup or batch these app IDs instead.
(Based on your team's feedback about scaling library lookups.)
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/src/main/java/app/gamenative/service/SteamService.kt, line 733:
<comment>Rendering multiple demo CTAs repeatedly loads the entire license table per app, which scales with the user's full library rather than the featured items. Use a cached/indexed entitlement lookup or batch these app IDs instead.
(Based on your team's feedback about scaling library lookups.) </comment>
<file context>
@@ -727,6 +727,27 @@ class SteamService : Service(), IChallengeUrlChanged {
+ /** Whether any owned license grants [appId]. Follows the license list, so a freshly
+ * granted free license flips this once Steam pushes the updated list. */
+ suspend fun isAppInLibrary(appId: Int): Boolean =
+ instance?.licenseDao?.getAllLicenses()?.any { appId in it.appIds } == true
+
+ /** Requests a free license (demos, F2P) for [appId] over the CM connection. */
</file context>
| "apps=${callback.grantedApps}, packages=${callback.grantedPackages}", | ||
| ) | ||
| callback.result == EResult.OK && appId in callback.grantedApps | ||
| } catch (e: Exception) { |
There was a problem hiding this comment.
P2: Leaving the screen while the license request is pending is treated as a failed request and can open the fallback URL from a cancelled UI coroutine. Rethrow CancellationException before handling ordinary failures.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/src/main/java/app/gamenative/service/SteamService.kt, line 745:
<comment>Leaving the screen while the license request is pending is treated as a failed request and can open the fallback URL from a cancelled UI coroutine. Rethrow `CancellationException` before handling ordinary failures.</comment>
<file context>
@@ -727,6 +727,27 @@ class SteamService : Service(), IChallengeUrlChanged {
+ "apps=${callback.grantedApps}, packages=${callback.grantedPackages}",
+ )
+ callback.result == EResult.OK && appId in callback.grantedApps
+ } catch (e: Exception) {
+ Timber.e(e, "requestFreeLicense($appId) failed")
+ false
</file context>
| } catch (e: Exception) { | |
| } catch (e: CancellationException) { | |
| throw e | |
| } catch (e: Exception) { |
| suspend fun requestFreeLicense(appId: Int): Boolean = withContext(Dispatchers.IO) { | ||
| val steamApps = instance?._steamApps ?: return@withContext false | ||
| try { | ||
| val callback = steamApps.requestFreeLicense(appId).toFuture().await() |
There was a problem hiding this comment.
P2: requestFreeLicense awaits the Steam callback without a timeout, so if Steam never responds the coroutine hangs indefinitely. This call is triggered directly from a user tapping the featured CTA button, so consider wrapping the await in withTimeout(...) similar to getEncryptedAppTicket elsewhere in this file.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/src/main/java/app/gamenative/service/SteamService.kt, line 739:
<comment>requestFreeLicense awaits the Steam callback without a timeout, so if Steam never responds the coroutine hangs indefinitely. This call is triggered directly from a user tapping the featured CTA button, so consider wrapping the await in withTimeout(...) similar to getEncryptedAppTicket elsewhere in this file.</comment>
<file context>
@@ -727,6 +727,27 @@ class SteamService : Service(), IChallengeUrlChanged {
+ suspend fun requestFreeLicense(appId: Int): Boolean = withContext(Dispatchers.IO) {
+ val steamApps = instance?._steamApps ?: return@withContext false
+ try {
+ val callback = steamApps.requestFreeLicense(appId).toFuture().await()
+ Timber.i(
+ "requestFreeLicense($appId) -> ${callback.result}, " +
</file context>
| val callback = steamApps.requestFreeLicense(appId).toFuture().await() | |
| val callback = withTimeout(15_000) { | |
| steamApps.requestFreeLicense(appId).toFuture().await() | |
| } |
| if (localBuild) { | ||
| implementation(files("../../JavaSteam/build/libs/javasteam-1.8.0.1-22-SNAPSHOT.jar")) | ||
| implementation(files("../../JavaSteam/javasteam-depotdownloader/build/libs/javasteam-depotdownloader-1.8.0.1-22-SNAPSHOT.jar")) | ||
| implementation(files("../../JavaSteam/build/libs/javasteam-1.8.0.1-25-SNAPSHOT.jar")) |
There was a problem hiding this comment.
P3: This edit bumps only the inactive localBuild branch (localBuild=false), so it has no effect on the actual dependency resolution — the active else branch uses libs.javasteam from the version catalog, which is already at 1.8.0.1-26-SNAPSHOT. The local-build paths are now one version behind the catalog, creating a confusing mismatch for anyone who flips localBuild on. Consider instead bumping the version in gradle/libs.versions.toml (the path that actually affects the build), or dropping this dead-branch edit to keep the diff scoped to functional changes.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/build.gradle.kts, line 334:
<comment>This edit bumps only the inactive `localBuild` branch (localBuild=false), so it has no effect on the actual dependency resolution — the active `else` branch uses `libs.javasteam` from the version catalog, which is already at 1.8.0.1-26-SNAPSHOT. The local-build paths are now one version behind the catalog, creating a confusing mismatch for anyone who flips localBuild on. Consider instead bumping the version in gradle/libs.versions.toml (the path that actually affects the build), or dropping this dead-branch edit to keep the diff scoped to functional changes.</comment>
<file context>
@@ -331,8 +331,8 @@ dependencies {
if (localBuild) {
- implementation(files("../../JavaSteam/build/libs/javasteam-1.8.0.1-22-SNAPSHOT.jar"))
- implementation(files("../../JavaSteam/javasteam-depotdownloader/build/libs/javasteam-depotdownloader-1.8.0.1-22-SNAPSHOT.jar"))
+ implementation(files("../../JavaSteam/build/libs/javasteam-1.8.0.1-25-SNAPSHOT.jar"))
+ implementation(files("../../JavaSteam/javasteam-depotdownloader/build/libs/javasteam-depotdownloader-1.8.0.1-25-SNAPSHOT.jar"))
implementation(libs.bundles.javasteam.dev)
</file context>
|
|
||
| private const val MOCK_HERO_RESPONSE = false | ||
|
|
||
| internal val MOCK_HERO_JSON = """ |
There was a problem hiding this comment.
P3: This PR ships the demo scaffold inside production code: MOCK_HERO_RESPONSE is a hardcoded false, making the parseHero(MOCK_HERO_JSON) branch unreachable at runtime, so the large embedded payload (a real 'Whisk' campaign with its actual Steam appid, asset URLs, and a separate demo appid) is effectively a test fixture compiled into the app. It's only used by MockHeroResponseTest. This adds noise and embeds a real third-party/Steam campaign in the shipped binary just to power the demo. Consider moving the mock payload and a demo/debug toggle out of the production RecommendationRepository (e.g. into the test source set or behind a debug-only/flag-gated layer), so the demo doesn't ship with the app.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/src/main/java/app/gamenative/data/RecommendationRepository.kt, line 25:
<comment>This PR ships the demo scaffold inside production code: `MOCK_HERO_RESPONSE` is a hardcoded `false`, making the `parseHero(MOCK_HERO_JSON)` branch unreachable at runtime, so the large embedded payload (a real 'Whisk' campaign with its actual Steam appid, asset URLs, and a separate demo appid) is effectively a test fixture compiled into the app. It's only used by `MockHeroResponseTest`. This adds noise and embeds a real third-party/Steam campaign in the shipped binary just to power the demo. Consider moving the mock payload and a `demo`/debug toggle out of the production `RecommendationRepository` (e.g. into the test source set or behind a debug-only/flag-gated layer), so the demo doesn't ship with the app.</comment>
<file context>
@@ -17,6 +17,40 @@ object RecommendationRepository {
+
+ // Verbatim /api/games/hero payload for a sponsored campaign with in-app CTAs; goes through
+ // parseHero like a real response, so it exercises deserialization, not just the UI.
+ internal val MOCK_HERO_JSON = """
+ {
+ "recommendation": null,
</file context>
| <string name="featured_action_wishlist_on">在 %1$s 加入願望清單</string> | ||
| <string name="featured_action_wishlisted">已加入願望清單</string> | ||
| <string name="featured_action_get_demo">取得試玩版</string> | ||
| <string name="featured_action_in_library">已在收藏庫中</string> |
There was a problem hiding this comment.
P3: The new featured_action_in_library string uses "收藏庫" while this file consistently uses "遊戲庫" elsewhere (e.g. app_library, destination_library) for the library concept. Using a different term here could confuse users into thinking this is a separate saved/wishlist collection rather than the owned-games library.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/src/main/res/values-zh-rTW/strings.xml, line 2084:
<comment>The new featured_action_in_library string uses "收藏庫" while this file consistently uses "遊戲庫" elsewhere (e.g. app_library, destination_library) for the library concept. Using a different term here could confuse users into thinking this is a separate saved/wishlist collection rather than the owned-games library.</comment>
<file context>
@@ -2079,6 +2079,10 @@
<string name="featured_action_wishlist_on">在 %1$s 加入願望清單</string>
+ <string name="featured_action_wishlisted">已加入願望清單</string>
+ <string name="featured_action_get_demo">取得試玩版</string>
+ <string name="featured_action_in_library">已在收藏庫中</string>
+ <string name="featured_action_failed">無法在應用程式內完成,正在開啟商店頁面</string>
<string name="featured_action_preorder">預購</string>
</file context>
| <string name="featured_action_in_library">已在收藏庫中</string> | |
| <string name="featured_action_in_library">已在遊戲庫中</string> |
Description
Added ability to wishlist games and add demos directly from the app
Recording
Type of Change
Checklist
#code-changes, I have discussed this change there and it has been green-lighted. If I do not have access, I have still provided clear context in this PR. If I skip both, I accept that this change may face delays in review, may not be reviewed at all, or may be closed.CONTRIBUTING.md.Summary by cubic
Adds in-app wishlist and demo CTAs for sponsored campaigns. Actions run over the live
javasteamsession, show “Wishlisted”/“In Library,” and fall back to the store page on failure.New Features
SteamWishlistService: add/remove/read wishlist via CM.SteamService:isAppInLibraryandrequestFreeLicensefor demos/F2P.FeaturedCtaButton: runsWISHLISTandGET_DEMOin-app; others deep-link; buttons stay focusable and dim when busy/done.FeaturedItem/FeaturedActiongainappId;FeaturedCtagainstypeandappId.ConversionTracker.featuredConversion./api/games/heroJSON andMockHeroResponseTestto validate payload decoding.Dependencies
javasteamto1.8.0.1-26-SNAPSHOT; localjavasteambuilds off by default.Written for commit dff5bc7. Summary will update on new commits.
Summary by CodeRabbit
New Features
Localization
Bug Fixes