Chapter 11: Subscription States
Tracking subscription state from scratch means mapping seven distinct states (ACTIVE, IN_GRACE_PERIOD, ON_HOLD, PAUSED, CANCELED, EXPIRED, PENDING), knowing which grant access and which do not, and building a state machine that processes RTDNs and makes correct entitlement decisions.
With RevenueCat, you do not implement a state machine. You read one boolean.
The Entitlement Check
isActive is true when:
- The subscription is
ACTIVE - The subscription is
IN_GRACE_PERIOD(grace period grants access) - The subscription is
CANCELEDbutexpirationDateis in the future
isActive is false when:
- The subscription is
ON_HOLD - The subscription is
PAUSED - The subscription is
EXPIRED - The subscription is
PENDING(no payment yet)
RevenueCat computes this on the backend using the SubscriptionPurchaseV2 resource from the Google Play Developer API. You do not write the seven-state access decision function.
Reading EntitlementInfo
The EntitlementInfo object gives you additional context if you need it:
billingIssueDetectedAt being non-null means the user is in grace period or account hold. Show them a payment update prompt.
unsubscribeDetectedAt being non-null means the user has canceled but may still have active access until expirationDate.
Showing State-Aware UI
Use EntitlementInfo to drive UI decisions beyond the basic access gate. The same isActive boolean that grants access also lets you surface contextual prompts, such as a renewal reminder for users who have canceled but are still within their paid period.
User Identification
For multi-device support and linking purchases to your user accounts, pass your user ID at login:
awaitLogIn() merges any anonymous purchases made before login with the identified user. created is true if this is a new RevenueCat user.
CustomerInfo Caching
CustomerInfo is cached on disk. Subsequent calls to awaitCustomerInfo() return the cache immediately, then fetch from the network in the background if the cache is stale. This means your entitlement checks are fast even offline.
For scenarios where you need guaranteed fresh data (for example, after a support interaction that granted a promotional subscription), use:
Listening for Changes
The UpdatedCustomerInfoListener fires whenever the SDK updates its cache:
This fires after purchases, restores, and background refreshes. It does not fire if the SDK starts with a cache hit and nothing changed. Always call awaitCustomerInfo() on launch as well.