Tracking Subscription State
Here's how to view whether or not a user is on a paid plan in Android.
Superwall tracks the subscription state of a user for you. However, there are times in your app where you need to know if a user is on a paid plan or not. For example, you might want to conditionally show certain UI elements or enable premium features based on their subscription status.
Using subscriptionStatus
The easiest way to track subscription status in Android is by accessing the subscriptionStatus StateFlow:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Get current status
val status = Superwall.instance.subscriptionStatus.value
when (status) {
is SubscriptionStatus.Active -> {
Log.d("Superwall", "User has active entitlements: ${status.entitlements}")
showPremiumContent()
}
is SubscriptionStatus.Inactive -> {
Log.d("Superwall", "User is on free plan")
showFreeContent()
}
is SubscriptionStatus.Unknown -> {
Log.d("Superwall", "Subscription status unknown")
showLoadingState()
}
}
}
}The SubscriptionStatus sealed class has three possible states:
SubscriptionStatus.Unknown- Status is not yet determinedSubscriptionStatus.Active(Set<String>)- User has active entitlements (set of entitlement identifiers)SubscriptionStatus.Inactive- User has no active entitlements
Observing subscription status changes
You can observe real-time subscription status changes using Kotlin's StateFlow:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycleScope.launch {
Superwall.instance.subscriptionStatus.collect { status ->
when (status) {
is SubscriptionStatus.Active -> {
Log.d("Superwall", "User upgraded to pro!")
updateUiForPremiumUser()
}
is SubscriptionStatus.Inactive -> {
Log.d("Superwall", "User is on free plan")
updateUiForFreeUser()
}
is SubscriptionStatus.Unknown -> {
Log.d("Superwall", "Loading subscription status...")
showLoadingState()
}
}
}
}
}
}Using with Jetpack Compose
If you're using Jetpack Compose, you can observe subscription status reactively:
@Composable
fun ContentScreen() {
val subscriptionStatus by Superwall.instance.subscriptionStatus
.collectAsState()
Column {
when (subscriptionStatus) {
is SubscriptionStatus.Active -> {
val entitlements = (subscriptionStatus as SubscriptionStatus.Active).entitlements
Text("Premium user with: ${entitlements.joinToString()}")
PremiumContent()
}
is SubscriptionStatus.Inactive -> {
Text("Free user")
FreeContent()
}
is SubscriptionStatus.Unknown -> {
Text("Loading...")
LoadingIndicator()
}
}
}
}Checking for specific entitlements
If your app has multiple subscription tiers (e.g., Bronze, Silver, Gold), you can check for specific entitlements:
val status = Superwall.instance.subscriptionStatus.value
when (status) {
is SubscriptionStatus.Active -> {
if (status.entitlements.contains("gold")) {
// Show gold-tier features
showGoldFeatures()
} else if (status.entitlements.contains("silver")) {
// Show silver-tier features
showSilverFeatures()
}
}
else -> showFreeFeatures()
}Setting subscription status
When using Superwall with a custom purchase controller or third-party billing service, you need to manually update the subscription status. Here's how to sync with RevenueCat:
class RevenueCatPurchaseController : PurchaseController {
override suspend fun purchase(
activity: Activity,
product: StoreProduct
): PurchaseResult {
return try {
val result = Purchases.sharedInstance.purchase(activity, product.sku)
// Update Superwall subscription status based on RevenueCat result
if (result.isSuccessful) {
val entitlements = result.customerInfo.entitlements.active.keys
Superwall.instance.setSubscriptionStatus(
SubscriptionStatus.Active(entitlements)
)
PurchaseResult.Purchased
} else {
PurchaseResult.Failed(Exception("Purchase failed"))
}
} catch (e: Exception) {
PurchaseResult.Failed(e)
}
}
override suspend fun restorePurchases(): RestorationResult {
return try {
val customerInfo = Purchases.sharedInstance.restorePurchases()
val activeEntitlements = customerInfo.entitlements.active.keys
if (activeEntitlements.isNotEmpty()) {
Superwall.instance.setSubscriptionStatus(
SubscriptionStatus.Active(activeEntitlements)
)
} else {
Superwall.instance.setSubscriptionStatus(SubscriptionStatus.Inactive)
}
RestorationResult.Restored
} catch (e: Exception) {
RestorationResult.Failed(e)
}
}
}You can also listen for subscription changes from your billing service:
class SubscriptionManager {
fun syncSubscriptionStatus() {
Purchases.sharedInstance.getCustomerInfoWith { customerInfo ->
val activeEntitlements = customerInfo.entitlements.active.keys
if (activeEntitlements.isNotEmpty()) {
Superwall.instance.setSubscriptionStatus(
SubscriptionStatus.Active(activeEntitlements)
)
} else {
Superwall.instance.setSubscriptionStatus(SubscriptionStatus.Inactive)
}
}
}
}Using SuperwallDelegate
You can also listen for subscription status changes using the SuperwallDelegate:
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
Superwall.configure(
applicationContext = this,
apiKey = "YOUR_API_KEY",
options = SuperwallOptions().apply {
delegate = object : SuperwallDelegate() {
override fun subscriptionStatusDidChange(
from: SubscriptionStatus,
to: SubscriptionStatus
) {
when (to) {
is SubscriptionStatus.Active -> {
Log.d("Superwall", "User is now premium")
}
is SubscriptionStatus.Inactive -> {
Log.d("Superwall", "User is now free")
}
is SubscriptionStatus.Unknown -> {
Log.d("Superwall", "Status unknown")
}
}
}
}
}
)
}
}Superwall checks subscription status for you
Remember that the Superwall SDK uses its audience filters for determining when to show paywalls. You generally don't need to wrap your calls to register placements with subscription status checks:
// ❌ Unnecessary
if (Superwall.instance.subscriptionStatus.value !is SubscriptionStatus.Active) {
Superwall.instance.register("campaign_trigger")
}
// ✅ Just register the placement
Superwall.instance.register("campaign_trigger")In your audience filters, you can specify whether the subscription state should be considered, which keeps your codebase cleaner and puts the "Should this paywall show?" logic where it belongs—in the Superwall dashboard.
How is this guide?