Skip to content

feat(surveys): collect partial responses and resume unfinished surveys - #768

Open
lucasheriques wants to merge 6 commits into
mainfrom
lucas/surveys-partial-responses
Open

feat(surveys): collect partial responses and resume unfinished surveys#768
lucasheriques wants to merge 6 commits into
mainfrom
lucas/surveys-partial-responses

Conversation

@lucasheriques

@lucasheriques lucasheriques commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

💡 Motivation and Context

Add partial responses and persistent resume to Android surveys, moving toward survey feature parity across all PostHog SDKs, with posthog-js as the reference. Closes #390. Auto-submit is separate in #769.

After submitting an answer, users can restart and resume at the next branching destination with saved answers and the same $survey_submission_id. With enable_partial_responses=true, each answer emits a cumulative survey sent event; false/absent emits only on completion. Resume works in both modes.

Key behavior:

  • Completion, dismissal, reset, removed/ended surveys and incompatible questions clear progress. Unavailable configuration preserves it; a confirmed empty list clears it.
  • Compose retains unsent input across Activity replacement. Reset clears stored/retained answers and rejects stale callbacks, including when the anonymous ID is reused; old attempts cannot affect newer ones.
  • Historical question wording is retained. Completion uses the current answer language; dismissal uses the last saved answer language.
  • Resuming bypasses seen/internal targeting checks; other targeting still applies. Custom renderers must honor initialQuestionIndex. Existing constructor/copy signatures remain compatible; custom delegates can opt into the separate reset capability.

Review path

  1. Restart test: fresh SDK/preferences, cumulative answers and language attribution.
  2. Progress store and integration: persistence, eligibility and event lifecycle.
  3. Compose host and reset capability: Activity replacement, session ownership and stale-work rejection.

💚 How did you test it?

Passed: CI=true make compile testSurveyUI (zero failures; 3 skipped tests), formatting, API checks and CodeScene. Coverage includes fresh SDK resume, mounted draft restoration, reset races and Kotlin/Java compatibility. Removing session invalidation or the submit guard made the corresponding regressions fail.

Restart tests use real SDK/preferences instances within Robolectric and capture through beforeSend; they do not exercise physical-device process death or network ingestion.

📝 Checklist

  • Regression tests and independent review completed.
  • Delegate documentation and changesets included.

🤖 Agent context

Autonomy: Human-driven (agent-assisted). Codex implemented the changes with independent QA agents; human review is required.

Decode enable_partial_responses and emit cumulative answers after each
submitted question when enabled. Keep a submission UUID across sent/dismissed
events and set $survey_completed using branching. Preserve completion-only
behavior by default and legacy response keys.

Verified regression tests fail before and pass after implementation. Passed
CI=true make compile, SDK/Compose module builds, API and formatting checks.
Core: 943 tests; Android debug/release: 359 each (3 skipped); Compose: 9.
CodeScene passed with stable legacy integration-file health.
@lucasheriques
lucasheriques requested a review from a team as a code owner September 8, 2026 19:54
@greptile-apps

greptile-apps Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor
Prompt To Fix All With AI
### Issue 1
posthog/src/main/java/com/posthog/surveys/Survey.kt:32-33
**Public JVM ABI breaks**

Adding `enablePartialResponses` to this public data class replaces the previous JVM constructor and `copy` method descriptors, as shown in the API snapshot. Because `posthog` is declared as a minor update, an application compiled against either old signature can upgrade without recompiling and then fail at runtime with `NoSuchMethodError`. Please preserve the old ABI or publish this as a breaking release.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "feat(surveys): collect partial responses..." | Re-trigger Greptile

Comment thread posthog/src/main/java/com/posthog/surveys/Survey.kt
@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

posthog-android Compliance Report

Date: 2026-09-09 18:40:06 UTC
Duration: 118412ms

✅ All Tests Passed!

46/46 tests passed


Capture Tests

29/29 tests passed

View Details
Test Status Duration
Format Validation.Event Has Required Fields 379ms
Format Validation.Event Has Uuid 37ms
Format Validation.Event Has Lib Properties 40ms
Format Validation.Distinct Id Is String 25ms
Format Validation.Token Is Present 26ms
Format Validation.Custom Properties Preserved 27ms
Format Validation.Event Has Timestamp 24ms
Retry Behavior.Retries On 503 7033ms
Retry Behavior.Does Not Retry On 400 4023ms
Retry Behavior.Does Not Retry On 401 4024ms
Retry Behavior.Respects Retry After Header 7028ms
Retry Behavior.Implements Backoff 17035ms
Retry Behavior.Retries On 500 7019ms
Retry Behavior.Retries On 502 7020ms
Retry Behavior.Retries On 504 7018ms
Retry Behavior.Max Retries Respected 17035ms
Deduplication.Generates Unique Uuids 39ms
Deduplication.Preserves Uuid On Retry 7017ms
Deduplication.Preserves Uuid And Timestamp On Retry 12031ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry 7019ms
Deduplication.No Duplicate Events In Batch 36ms
Deduplication.Different Events Have Different Uuids 24ms
Compression.Sends Gzip When Enabled 17ms
Batch Format.Uses Proper Batch Structure 18ms
Batch Format.Flush With No Events Sends Nothing 13ms
Batch Format.Multiple Events Batched Together 36ms
Error Handling.Does Not Retry On 403 4019ms
Error Handling.Does Not Retry On 413 4019ms
Error Handling.Retries On 408 5027ms

Feature_Flags Tests

17/17 tests passed

View Details
Test Status Duration
Request Payload.Request With Person Properties Device Id 35ms
Request Payload.Flags Request Uses V2 Query Param 22ms
Request Payload.Flags Request Hits Flags Path Not Decide 19ms
Request Payload.Flags Request Omits Authorization Header 21ms
Request Payload.Token In Flags Body Matches Init 18ms
Request Payload.Groups Round Trip 37ms
Request Payload.Groups Default To Empty Object 22ms
Request Payload.Disable Geoip False Propagates As Geoip Disable False 22ms
Request Payload.Disable Geoip Omitted Defaults To False 22ms
Request Payload.Flag Keys To Evaluate Contains Only Requested Key 20ms
Request Lifecycle.No Flags Request On Init Alone 11ms
Request Lifecycle.No Flags Request On Normal Capture 21ms
Request Lifecycle.Two Flag Calls Produce Two Remote Requests 38ms
Request Lifecycle.Mock Response Value Is Returned To Caller 23ms
Retry Behavior.Retries Flags On 502 324ms
Retry Behavior.Retries Flags On 504 322ms
Side Effect Events.Get Feature Flag Captures Feature Flag Called Event 22ms

Preserve the previous JVM constructor, copy, and Kotlin default-argument
signatures when adding enablePartialResponses. Forward copies with the new
setting intact. Keep overloads visible for Java source compatibility too.

Verification: reflection regression tests exercise all four old descriptors;
Kotlin copy calls preserve/override the new field. Format/API snapshot and
focused core/event-payload tests pass.

CodeScene exception: the 17-argument copy compatibility overload triggers
the argument-count gate. Its exact signature is required for binary
compatibility; shortening it would restore the runtime failure.
Save progress after nonterminal answers and restore the submission ID,
answers, answer-time text/language and branching destination. Clear state
on completion, dismissal, reset and incompatible survey updates. Keep
unfinished attempts eligible and honor initialQuestionIndex in Compose.

Preserve legacy display-model constructor/copy descriptors and API 23
hash compatibility. Fix jumps to the final question completing too early.

Verified: CI=true make compile, full Android debug tests after final
callback simplification, make format, make checkFormat, make api, legacy
ABI regression tests and API descriptor comparison.
CodeScene exceptions: legacy integration size/aggregate complexity
remains degraded despite lower showSurvey complexity; broader extraction
is deferred. Compatibility copy argument counts preserve existing ABI.
@lucasheriques lucasheriques changed the title feat(surveys): collect partial responses incrementally feat(surveys): collect partial responses and resume unfinished surveys Sep 8, 2026
Invalidate delayed callbacks on reset, serialize preference mutations,
and reject stale snapshots after reentrant preference reads. Defer
reconciliation while credential-protected storage is unavailable.
Prepare event snapshots before dispatch and keep client callbacks outside
state locks; stale shown callbacks cannot close a newer renderer.

Keep Compose survey state across host Activity destruction and restore on
the next foreground Activity. Explicit close still dismisses exactly once.
Add mounted lifecycle coverage and an explicit CI debug-host test target.

Validation: deterministic reset/store/callback regressions, mounted
lifecycle tests, full CI=true make compile, format/API checks, and
CodeScene pre-commit review.
@veria-ai

veria-ai Bot commented Sep 9, 2026

Copy link
Copy Markdown

PR overview

All previously flagged issues have been addressed. No open security concerns remain on this pull request.

Security review

No open security issues remain on this pull request.

Fixed/addressed: 1 · PR risk: 0/10

Notify the actual Compose renderer after SDK reset, including the
renderer discovered automatically. Bind UI state to an installed session,
configuration and reset generation so stale callbacks, pending shows,
retained Activity input and late cleanup cannot reach a new presentation.

Keep custom delegate callbacks outside SDK monitors. Preserve the existing
delegate interface and add an internal optional session/presentation
capability. Atomically bind the owner and reset generation to avoid missing
a concurrent reset, and preserve delegate reuse with a new configuration.

Verification: full CI=true make compile; focused ABI/Java/reset, Android
survey and mounted Compose tests; final bind/reset regression; format,
API snapshot, checkFormat and CodeScene safeguard. Existing size penalties
remain unchanged. Mounted UI uses Robolectric. The broad build precedes
the final focused bind/reset adjustment.
Only deliver cached survey configuration during setup when it is available.
An unknown cache must not erase unfinished answers through reconciliation;
successful empty configuration remains authoritative.

Exercise fresh SDK setup with production preferences, cumulative saved and
new answers, submission IDs, and completion/dismissal language attribution.
Strengthen mounted draft restoration, zero stale-response forwarding,
session retirement and cleanup ownership, and legacy argument forwarding.
Consolidate overlapping cases and move the restart journey into a focused
test class. Replace private-monitor assertions with bounded behavior.

Validation: 41 focused tests passed. CI=true make compile testSurveyUI
passed: core 948, Android release 374 (3 skipped), Compose release 9,
server 540, Compose debug 15; zero failures. Format, API, checkFormat, and
CodeScene passed; no API snapshot changes. Removing session invalidation
and the actual inline submit guard each failed the intended assertion;
both mutations were restored before final validation.

Fresh SDK recreation runs within Robolectric and captures via beforeSend;
physical-device process death and network ingestion are outside this test
scope. Native null-answer omission remains unchanged.
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.

Surveys: Support partial response collection

1 participant