Skip to content

feat(android): add getRegion/getJurisdiction with config caching#129

Merged
ethanpschoen merged 3 commits into
justin/feat/cross-activity-lifecyclefrom
ethan/feat/headless-region-jurisdiction
Jul 15, 2026
Merged

feat(android): add getRegion/getJurisdiction with config caching#129
ethanpschoen merged 3 commits into
justin/feat/cross-activity-lifecyclefrom
ethan/feat/headless-region-jurisdiction

Conversation

@ethanpschoen

@ethanpschoen ethanpschoen commented Jul 15, 2026

Copy link
Copy Markdown

Summary

  • Confirmed getLocation() returns GeoIP region data (IPInfo), not a jurisdiction — HeadlessApiClient.kt hits GET /ip, which is purely geographic. Fixed the misleading doc comment.
  • Added getRegion() (combined ISO code, e.g. "US-CA") and getJurisdiction() (resolved jurisdiction.code ?: defaultJurisdictionCode) as clear, intention-revealing headless accessors.
  • Added a getFullConfiguration() cache on the Ketch instance, keyed on the config URL path (org/property/env/jurisdiction/language/hash). setJurisdiction()/setLanguage() invalidate it since they change that path; setRegion() does not, since region isn't part of it. getLocation()/getRegion() results are cached the same way (no path params to key on).
  • Sample app: added a "Get Region" card, simplified "Get Jurisdiction" to call the new getJurisdiction().

Test plan

  • ./gradlew clean :ketchsdk:assembleDebug :ketchsdk:testDebugUnitTest :sample-app-compose:assembleDebug — clean build, all tests pass (incl. new HeadlessModelsTest covering toRegionCode()).
  • Ran sample-app-compose on an emulator:
    • "Get Jurisdiction" → california (real resolved value)
    • Tapped again → identical call/result timestamp, confirming the cache serves the second call instead of re-fetching
    • "Get Region" → US-CA, consistent with the resolved jurisdiction
    • No crashes/exceptions in logcat

🤖 Generated with Claude Code


Note

Medium Risk
Caching can serve stale jurisdiction/config after external changes until setJurisdiction/setLanguage or a new request key; low blast radius but integrators should know cache semantics.

Overview
Adds instance-level caching for headless getLocation() and getFullConfiguration() on Ketch, plus convenience APIs getRegion() (ISO-style code from GeoIP via IPInfo.toRegionCode()) and getJurisdiction() (resolved code from full config).

getFullConfiguration cache keys on org/property/env/jurisdiction/language/hash; setJurisdiction / setLanguage clear that cache; setRegion does not (region is not part of the config URL path). Location is cached for the lifetime of the instance with no invalidation. Docs for getLocation() are clarified as GeoIP, not jurisdiction.

Sample Compose app wires Get Region and switches Get Jurisdiction to getJurisdiction(). Unit tests cover toRegionCode() edge cases.

Reviewed by Cursor Bugbot for commit ceda824. Configure here.

getLocation() returns GeoIP region data (IPInfo), not a jurisdiction —
add getRegion() as a clear accessor for that ("US-CA" combined form).

getJurisdiction() resolves the jurisdiction code from the full config.
Since getFullConfiguration() previously re-fetched over the network on
every call, cache the config on the Ketch instance keyed on the fields
that affect its URL path (org/property/env/jurisdiction/language/hash).
setJurisdiction()/setLanguage() invalidate the cache since they change
that path; setRegion() does not, since region isn't part of it.
getLocation() results are cached the same way (no path params to key on).

Sample app: added a "Get Region" card and simplified "Get Jurisdiction"
to call the new getJurisdiction() method.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit ceda824. Configure here.

Comment thread ketchsdk/src/main/java/com/ketch/android/Ketch.kt
bugbot: buildConfigCacheKey collapsed null and "" into the same key
component, but HeadlessApiClient.getFullConfiguration only checked
`!= null` when deciding whether to append the env/jurisdiction/
language path segment. A blank (non-null) value and a null value hit
different URLs there, so two requests could share a cache key while
calling different endpoints.

Fix at the source: introduce FullConfigurationRequest.configPathSegment()
and .normalizedHash(), which treat blank the same as absent (matching
ketch-tag). Both HeadlessApiClient's path builder and Ketch's cache key
now derive from these same helpers, so they can never disagree about
what "the same request" means.
…iction

getRegion() already wrapped getLocation().location?.toRegionCode() with
no other reason for an integrator to want the raw GeoIP payload off the
Ketch instance, so fold the location cache/fetch directly into
getRegion() and remove the redundant public accessor.

KetchSdk.getLocation() (static) and HeadlessApiClient.getLocation() are
unchanged — they're a different layer, predate this change, and
KetchHeadlessIntegrationTest exercises the raw endpoint through them
directly.
@ethanpschoen
ethanpschoen merged commit d63ed21 into justin/feat/cross-activity-lifecycle Jul 15, 2026
3 checks passed
@ethanpschoen
ethanpschoen deleted the ethan/feat/headless-region-jurisdiction branch July 15, 2026 21:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants