Core app review & feedback API (#3665)#5268
Conversation
Introduces com.codename1.appreview.AppReview: requests the platform's native store-review prompt (SKStoreReviewController on iOS, Play In-App Review on Android) when available and falls back to a Codename One drawn rating widget elsewhere. Adds an opt-in engagement scheduler (launch / days-installed / cool-down thresholds persisted via Preferences) plus a manual requestReview() trigger, and a smart feedback split that routes high ratings to the store and low ratings to a pluggable FeedbackListener (e-mail helper provided). Plumbing follows the existing share()/dial() pattern: - CodenameOneImplementation.isNativeInAppReviewSupported() / requestNativeInAppReview(), exposed via Display and CN. - iOS: IOSNative.requestAppStoreReview() bridges to SKStoreReviewController guarded by a new CN1_USE_APPREVIEW macro (symbol always emitted, StoreKit body compiled only when used). - Android: AppReviewSupport drives the Play review flow via reflection so the port compiles without the extra dependency. Native dependencies are added implicitly (no build hint): the Android/iOS builders detect com.codename1.appreview usage during their class scan and only then inject com.google.android.play:review / link StoreKit.framework. Tests & docs: - maven/core-unittests: AppReviewTest covers the scheduler decision logic. - docs/developer-guide: new App-Review chapter. - scripts/hellocodenameone: AppReviewDialogScreenshotTest for the widget. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Developer Guide build artifacts are available for download from this workflow run:
Developer Guide quality checks: |
Android screenshot updatesCompared 136 screenshots: 135 matched, 1 updated.
Native Android coverage
Benchmark ResultsDetailed Performance Metrics
|
Cloudflare Preview
|
|
Compared 134 screenshots: 134 matched. |
|
Compared 134 screenshots: 134 matched. |
|
Compared 132 screenshots: 132 matched. Benchmark ResultsDetailed Performance Metrics
|
JavaScript port screenshot updatesCompared 128 screenshots: 127 matched, 1 updated. |
|
Compared 134 screenshots: 134 matched. Benchmark Results
Detailed Performance Metrics
|
Apple Watch (watchOS / Core Graphics)Compared 211 screenshots: 210 matched, 1 updated. |
iOS Metal screenshot updatesCompared 135 screenshots: 134 matched, 1 updated.
Benchmark Results
Build and Run Timing
Detailed Performance Metrics
|
- RatingDialog now presents as a bottom Sheet (swipe to dismiss) instead of a blocking modal Dialog, addressing the "too intrusive" feedback. - Stars use a single-row GridLayout so they no longer wrap on narrow displays (the Android screenshot showed the flow-layout wrap). - Fix SpotBugs LI_LAZY_INIT_STATIC by eager-initializing the AppReview singleton. - Rewrite the developer-guide chapter to satisfy the Vale prose gate (contractions, adverbs, quote punctuation) and describe the Sheet UI. - Screenshot test mirrors the single-row star grid. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Make RatingDialog final (ClassWithOnlyPrivateConstructorsShouldBeFinal). - Simplify shouldPrompt() boolean return (SimplifyBooleanReturns). - Add @OverRide to anonymous Runnable/SuccessCallback/ActionListener implementations (MissingOverride). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
✅ Continuous Quality ReportTest & Coverage
Static Analysis
Generated automatically by the PR CI workflow. |
Seeds scripts/ios/screenshots-watch/AppReviewDialog.png from the watch suite's streamed render of the rating sheet. The watch runner gates on missing_expected (CN1SS_ALLOWED_MISSING=0); the other platforms treat a new golden as non-gating, and build-ios-metal is intentionally left without a baseline since that job is flaky. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The test previously rendered an inline Dialog-styled container; it now builds the same content as RatingDialog and calls Sheet.show() (following the SheetScreenshotTest pattern) so the capture shows the actual sheet chrome. The native store review widgets are OS-drawn, throttled overlays outside Display.screenshot(), so they can't be captured by the harness; this covers the Codename One fallback sheet that renders elsewhere. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The committed watch golden was the old inline-Dialog render; the test now shows the real Sheet, so the golden must be regenerated. Removing it both avoids a false "different" and (being a screenshots-watch change) re-runs the full screenshot suite against the new test so the fresh render can be captured. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Regenerate scripts/ios/screenshots-watch/AppReviewDialog.png from the real-Sheet render of the updated test. - Developer guide: embed the fallback rating Sheet screenshot (a cropped desktop render showing the bottom sheet with the single-row star strip) and describe the native iOS/Android prompts in prose, since those OS-drawn widgets are outside Display.screenshot() and can't be captured. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Captures the real fallback-Sheet render from each platform's screenshot run (Android, iOS, iOS-metal, JavaScript, Linux x64, Linux arm64, mac-native, Windows; watch already committed) so the screenshot bot no longer reports the test as a new/missing golden on every pipeline. Each golden is the platform's own render so the comparison is exact. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the GridLayout star strip (which spread the five stars across the full width) with a horizontal BoxLayout so they sit together at the leading edge -- left-aligned, or right-aligned under RTL -- on a single row without wrapping. Mirrored in the screenshot test; goldens to follow. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>






Resolves #3665.
Adds a core app-review & feedback API (
com.codename1.appreview) instead of a cn1lib. It uses the platform's native store-review prompt when available and falls back to a built-in Codename One widget everywhere else, while giving the app full control over when feedback is requested.What's included
Public API —
com.codename1.appreviewAppReview— singleton facade with two usage styles:AppReview.getInstance().requestReview()— ask whenever it makes sense.minimumLaunches/minimumDaysInstalled/daysBetweenPrompts) and callregisterSession()each launch; state is persisted inPreferencesand the user is never re-prompted after rating or opting out.highRatingThreshold) to the store and low ratings to a pluggableFeedbackListener(e-mail helper provided), so unhappy users reach you privately instead of leaving a 1-star public review.RatingDialog— the built-in fallback widget (stars + feedback), package-private.Native plumbing (mirrors
share()/dial())CodenameOneImplementation.isNativeInAppReviewSupported()/requestNativeInAppReview(SuccessCallback<Boolean>), exposed viaDisplayandCN.IOSNative.requestAppStoreReview()→SKStoreReviewController.requestReview(iOS ≥ 10.3), guarded by a newCN1_USE_APPREVIEWmacro (symbol always emitted so ParparVM links; StoreKit body compiled only when used).AppReviewSupportdrives the Play In-App Review flow via reflection, so the port compiles without the extra dependency;isNativeInAppReviewSupported()returns false when the lib wasn't bundled.Implicit dependency inclusion (no build hint)
The Android/iOS builders already scan the app's classes to auto-enable native deps. This PR extends that scan so referencing
com.codename1.appreview(or theCN/Displayreview methods) automatically injectscom.google.android.play:reviewon Android and linksStoreKit.framework+ flipsCN1_USE_APPREVIEWon iOS. Apps that never ask for a review carry no extra weight.Tests & docs
maven/core-unittests/.../appreview/AppReviewTest.java— 8 JUnit5 tests covering the scheduler decision logic (run:mvn -DunitTests -pl core-unittests test -Dtest=AppReviewTest, all green).docs/developer-guide/App-Review.asciidoc, registered in the master document.AppReviewDialogScreenshotTestinscripts/hellocodenameone, registered inCn1ssDeviceRunner. It is a brand-new golden, so each platform's screenshot job reports it asmissing_expected(non-gating) until the baselines are recorded via the usual flow.Verified
mvn compileof the maven-plugin module (both builders) → BUILD SUCCESS.AppReviewSupportcompiles againstandroid.jar; the screenshot test compiles against core.Note for maintainers
The same implicit-detection edits were applied to the out-of-repo cloud build server copies (
~/dev/CodenameOne/BuildDaemon,-analytics,-winpkg) since those aren't part of this repo — they need to be deployed separately for the native dep to be injected on cloud builds.🤖 Generated with Claude Code