Use this skill when gating access based on RevenueCat subscription state on Android. Covers reading CustomerInfo, checking entitlement.isActive as the single source of truth for access, and drilling into willRenew, periodType, and expirationDate when deeper logic is needed.
72
90%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Decide whether a user has access, and drive state aware UI, using CustomerInfo and EntitlementInfo on Android.
Confirm what you are actually checking before you write code.
pro_access)If you only need access on/off, you only need isActive. Everything else is optional context.
Rolling your own tracker with the Google Play Developer API means mapping seven subscription states and deciding which grant access.
| Google state | Grants access? |
|---|---|
| ACTIVE | yes |
| IN_GRACE_PERIOD | yes |
CANCELED (before expirationDate) | yes |
| ON_HOLD | no |
| PAUSED | no |
| EXPIRED | no |
| PENDING | no |
RevenueCat computes this on the backend from the Google SubscriptionPurchaseV2 resource and exposes the result as one field: EntitlementInfo.isActive. You read a boolean instead of implementing the state machine.
| Need | Field |
|---|---|
| Does the user have access right now? | entitlement.isActive |
| Will the subscription renew at period end? | entitlement.willRenew |
| When does paid access end? | entitlement.expirationDate |
| Is this a trial, intro, prepaid, or normal period? | entitlement.periodType |
| Is there a payment problem? | entitlement.billingIssueDetectedAt |
| Has the user canceled but still has time left? | entitlement.unsubscribeDetectedAt |
| Which store issued the entitlement? | entitlement.store |
isActive == true. Nothing else.billingIssueDetectedAt != null to show a fix payment prompt.unsubscribeDetectedAt != null with expirationDate to show a renewal reminder while access is still valid.!willRenew (when no billing issue and no explicit cancel timestamp) to show a non renewing notice.val customerInfo = Purchases.sharedInstance.awaitCustomerInfo()
val hasAccess = customerInfo.entitlements["pro_access"]?.isActive == trueawaitCustomerInfo() returns the disk cache immediately, then refreshes from the network in the background. Entitlement checks stay fast, even offline.
Use this after a server side grant (for example, a support agent issued a promo).
val fresh = Purchases.sharedInstance.awaitCustomerInfo(
fetchPolicy = CacheFetchPolicy.FETCH_CURRENT
)isActive gates access; the other fields explain context.
fun updateUI(entitlement: EntitlementInfo?) {
if (entitlement == null || !entitlement.isActive) {
showSubscribeScreen(); return
}
showPremiumContent()
when {
entitlement.billingIssueDetectedAt != null -> showBillingIssueWarning()
entitlement.unsubscribeDetectedAt != null ->
entitlement.expirationDate?.let { showExpiryNotice(it) }
!entitlement.willRenew -> showNonRenewingNotice()
}
}| Signal on an active entitlement | Message to show |
|---|---|
billingIssueDetectedAt != null | Payment problem, update method |
unsubscribeDetectedAt != null | Access ends on expirationDate |
willRenew == false (no other signal) | Will not renew this period |
periodType == TRIAL or INTRO | Trial or intro pricing in effect |
val result = Purchases.sharedInstance.awaitLogIn("your_user_id")
val customerInfo = result.customerInfo
val createdNewUser = result.createdawaitLogIn() merges anonymous purchases with the identified user. logOut() starts a fresh anonymous session.
Register a listener so UI reacts to purchases, restores, and background refreshes without manual polling.
Purchases.sharedInstance.updatedCustomerInfoListener =
UpdatedCustomerInfoListener { info ->
val active = info.entitlements["pro_access"]?.isActive == true
updateAccessGate(active)
}The listener does not fire when the SDK starts with a cache hit and nothing changed. Always call awaitCustomerInfo() on launch in addition to setting the listener.
Walk through each case and confirm the UI responds correctly.
| Case | Expected isActive | Expected UI |
|---|---|---|
| Fresh purchase | true | Premium content |
| Grace period (billing issue, still granted) | true | Premium + billing warning |
Canceled, still before expirationDate | true | Premium + expiry notice |
| On hold | false | Subscribe screen |
| Paused | false | Subscribe screen |
| Expired | false | Subscribe screen |
| Pending (no payment yet) | false | Subscribe screen |
FETCH_CURRENT updates CustomerInfo after a backend grant (promo, refund, support action).logOut(), CustomerInfo reflects an anonymous user and isActive resets accordingly.CustomerInfo and gates access without a network call.ccfc038
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.