Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ description: "Target users based on their device performance capabilities to opt
The `deviceTier` variable allows you to create targeted audiences based on device performance capabilities. This helps optimize paywall experiences by showing resource-appropriate content to different device types. You can reference this in campaign filters, dynamic values, or in paywall text via the `device.deviceTier` variable.

<Note>
Device tier targeting is available starting in Android SDK version `2.2.2`. Make sure you're using this version or later to access this feature.
Device tier targeting is available starting in Android SDK version `2.2.3`. Make sure you're using this version or later to access this feature.
</Note>

## How device tier works
Expand All @@ -19,6 +19,15 @@ Device tier classification is based on several hardware factors:

This automatic classification helps you deliver paywalls that perform well across the full spectrum of Android devices.

### Matching device ranges

When creating device tier filters, you can use `contains` or `equals` for narrower matching:

- **`contains`** - Broader matching that includes partial matches. For example, `deviceTier contains high` matches both `high` and `ultra_high` devices.
- **`equals`** - Exact matching for precise targeting. For example, `deviceTier equals high` matches only `high` tier devices, not `ultra_high`.

Use `contains` when you want to target a range of similar device capabilities, and `equals` when you need precise control over which specific tier to target.

## Device tier values

The `device.deviceTier` attribute returns one of these values:
Expand Down
246 changes: 244 additions & 2 deletions content/docs/android/quickstart/tracking-subscription-state.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,247 @@
---
title: Tracking Subscription State
title: "Tracking Subscription State"
description: "Here's how to view whether or not a user is on a paid plan in Android."
---

<include>../../../shared/tracking-subscription-state.mdx</include>
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:

```kotlin
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 determined
- `SubscriptionStatus.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:

```kotlin
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:

```kotlin
@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:

```kotlin
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:

```kotlin
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:

```kotlin
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`:

```kotlin
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](/campaigns-audience#matching-to-entitlements) for determining when to show paywalls. You generally don't need to wrap your calls to register placements with subscription status checks:

```kotlin
// ❌ 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](/campaigns-audience#matching-to-entitlements), 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.
15 changes: 15 additions & 0 deletions content/docs/dashboard/dashboard-campaigns/campaigns-audience.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,21 @@ You can combine rules together, too. In the following example, if we only wanted

<Frame>![](/images/campaigns-audience-combo-condition.png)</Frame>

#### Using user properties or placement parameters
You can reference [user attributes](/sdk/quickstart/setting-user-properties) and [placement parameters](/docs/using-placement-parameters) in campaign filters. For example, if you were to set `hasLoggedCoffee` on your user, you could use that in a filter.

**Adding user properties**
1. **Click** on Add Filter, and then click the **+** icon:
<Frame>![](/images/campaign-filter-add-user-prop.png)</Frame>
2. Select **User** and name the property, then save it:
<Frame>![](/images/campaign-filter-add-user-prop-named.png)</Frame>
3. Now you can select **User** (or type the property name) and the new property is available for user in your filter. Here, it's at the bottom:
<Frame>![](/images/campaign-filter-add-user-prop-filter.png)</Frame>

**Adding placement parameters**<br />
This works exactly the same as above, just choose "Placement" instead:
<Frame>![](/images/campaign-filter-add-placement-param.png)</Frame>

#### Using rule groups
You can combine rules together in groups. For example, you can mix **AND** and **OR** operators in the same group. To create a rule group, **click** on **+ Add Group** in the filter editor.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,11 @@ By default, Superwall has three different type of variables for use:

While those will allow you to cover several cases, you can also add your own custom variables too.

<Note>
<Tip>
Variables dealing with any product period, such as `someProduct.period`, `someProduct.periodly`,
and other similar variables, do _not_ localize automatically. This is because different languages
use varying orders and sentence structures for such terms. If you need them localized, add them as
you would any other term and enter localized values for each. Learn more about localization
[here](/paywall-editor-localization).
</Note>
and other similar variables, can localize automatically now. Learn more [here](/paywall-editor-localization#localizing-period-lengths).
</Tip>

### Using Variables

You primarily use variables in the **component editor** and with [dynamic values](/paywall-editor-dynamic-values). When using a variable in text, follow the Liquid syntax to reference one: `{{ theVariable }}`. For example, to reference a variable in some text, you would:
Expand Down

This file was deleted.

Loading