App Monetization — Ads, In-App Purchases & Subscriptions Strategy Guide
In this tutorial, you'll learn about App Monetization. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
App monetization is the strategy of generating revenue from mobile applications through advertising, in-app purchases, subscriptions, and premium features, with the global app economy exceeding 500 billion dollars in annual consumer spending.
What You'll Learn & Why It Matters
In this tutorial you will learn how to monetize mobile apps using AdMob banner and interstitial ads, consumable and non-consumable in-app purchases, auto-renewable subscriptions with introductory offers, and premium feature paywalls using RevenueCat. Choosing the right monetization model can increase lifetime value (LTV) by 3-5 times compared to a single strategy.
Real-world use: DodaZIP offers a freemium model where basic file compression is free, advanced format support (RAR, 7z) requires a subscription, and the ad-free experience is a one-time purchase — generating 80 percent of revenue from subscriptions.
Prerequisites
- An Android or iOS app with a user base (or a test app)
- A Google Play Console and/or App Store Connect account
- Basic familiarity with Kotlin or Swift
Learning Path
flowchart LR A[App Development Overview] --> B[App Monetization] B --> C[App Store Optimization] B --> D[Mobile Analytics] B:::current classDef current fill:#f90,color:#fff,stroke:#333,stroke-width:2px
In-App Advertising with AdMob
AdMob is Google's mobile ad platform supporting banner, interstitial, rewarded, and native ads.
Android Banner Ad
class MainActivity : AppCompatActivity() {
private lateinit var adView: AdView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
MobileAds.initialize(this) { }
adView = AdView(this).apply {
adUnitId = "ca-app-pub-3940256099942544/6300978111" // Test ID
adSize = AdSize.BANNER
layoutParams = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.WRAP_CONTENT
).apply { gravity = Gravity.BOTTOM }
}
val adContainer = findViewById<FrameLayout>(R.id.ad_container)
adContainer.addView(adView)
val adRequest = AdRequest.Builder().build()
adView.loadAd(adRequest)
}
override fun onDestroy() {
adView.destroy()
super.onDestroy()
}
}
Expected behavior: A banner ad appears at the bottom of the screen. The test ad unit returns a placeholder ad. Replace with your production ad unit ID before releasing.
iOS Interstitial Ad
import GoogleMobileAds
class InterstitialManager: NSObject, GADFullScreenContentDelegate {
private var interstitial: GADInterstitialAd?
func loadAd() {
let request = GADRequest()
GADInterstitialAd.load(
withAdUnitID: "ca-app-pub-3940256099942544/4411468910",
request: request
) { [weak self] ad, error in
if let error = error {
print("Failed to load interstitial: \(error)")
return
}
self?.interstitial = ad
self?.interstitial?.fullScreenContentDelegate = self
}
}
func showAd(from viewController: UIViewController) {
guard let interstitial = interstitial else {
print("Ad not ready")
return
}
interstitial.present(fromRootViewController: viewController)
}
func adDidDismissFullScreenContent(_ ad: GADFullScreenPresentingAd) {
loadAd() // Preload the next ad
}
}
Expected behavior: An interstitial ad covers the full screen. The adDidDismissFullScreenContent callback fires when the user closes it, triggering preloading of the next ad.
In-App Purchases (Consumable and Non-Consumable)
In-app purchases let users buy digital goods. Consumables (coins, hints) can be bought repeatedly. Non-consumables (premium unlock, ad removal) are purchased once.
Android Billing Client
class BillingManager(private val context: Context) : PurchasesUpdatedListener {
private val billingClient = BillingClient.newBuilder(context)
.setListener(this)
.enablePendingPurchases()
.build()
fun startConnection() {
billingClient.startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(result: BillingResult) {
if (result.responseCode == BillingClient.BillingResponseCode.OK) {
queryProducts()
}
}
override fun onBillingServiceDisconnected() {
// Retry connection
}
})
}
private fun queryProducts() {
val params = QueryProductDetailsParams.newBuilder()
.setProductList(
listOf(
QueryProductDetailsParams.Product.newBuilder()
.setProductId("remove_ads")
.setProductType(ProductType.INAPP)
.build(),
QueryProductDetailsParams.Product.newBuilder()
.setProductId("premium_monthly")
.setProductType(ProductType.SUBS)
.build()
)
)
.build()
billingClient.queryProductDetailsAsync(params) { result, productList ->
// Store product details for display
}
}
fun launchPurchase(activity: Activity, product: QueryProductDetailsParams.Product) {
val flowParams = BillingFlowParams.newBuilder()
.setProductDetailsParamsList(
listOf(
BillingFlowParams.ProductDetailsParams.newBuilder()
.setProductDetails(product)
.build()
)
)
.build()
billingClient.launchBillingFlow(activity, flowParams)
}
override fun onPurchasesUpdated(result: BillingResult, purchases: List<Purchase>?) {
if (result.responseCode == BillingClient.BillingResponseCode.OK && purchases != null) {
for (purchase in purchases) {
handlePurchase(purchase)
}
}
}
private fun handlePurchase(purchase: Purchase) {
if (purchase.purchaseState == Purchase.PurchaseState.PURCHASED) {
// Acknowledge the purchase
val acknowledgeParams = AcknowledgePurchaseParams.newBuilder()
.setPurchaseToken(purchase.purchaseToken)
.build()
billingClient.acknowledgePurchase(acknowledgeParams) { /* done */ }
// Unlock the feature
when (purchase.products[0]) {
"remove_ads" -> Preferences.setAdsRemoved(true)
"premium_monthly" -> Preferences.setPremium(true)
}
}
}
}
Expected behavior: The Google Play billing dialog appears. After successful payment, the purchase must be acknowledged within 3 days or it is refunded. The feature unlocks immediately after acknowledgment.
iOS StoreKit 2 (Swift)
import StoreKit
@MainActor
class PurchaseManager: ObservableObject {
@Published var products: [Product] = []
@Published var purchasedProductIDs: Set<String> = []
func loadProducts() async {
do {
let products = try await Product.products(for: [
"com.dodatech.remove_ads",
"com.dodatech.premium_monthly",
"com.dodatech.premium_yearly",
])
self.products = products.sorted(by: { $0.price < $1.price })
} catch {
print("Failed to load products: \(error)")
}
}
func purchase(_ product: Product) async throws {
let result = try await product.purchase()
switch result {
case .success(let verification):
let transaction = try checkVerified(verification)
await transaction.finish()
purchasedProductIDs.insert(product.id)
case .userCancelled:
break
case .pending:
break
@unknown default:
break
}
}
func checkVerified<T>(_ result: VerificationResult<T>) throws -> T {
switch result {
case .unverified:
throw StoreError.failedVerification
case .verified(let safe):
return safe
}
}
}
enum StoreError: Error {
case failedVerification
}
Expected behavior: Products load from App Store Connect. The purchase() method presents the system payment sheet. After successful verification, the Transaction finishes and the product is marked as purchased.
Subscription Management with RevenueCat
RevenueCat simplifies subscription management across Android and iOS with a unified API.
class RevenueCatManager {
fun configure() {
Purchases.configure(PurchaseConfiguration.Builder(context)
.apiKey("your_revenuecat_api_key")
.build()
)
}
fun checkSubscriptionStatus() {
Purchases.sharedInstance.getCustomerInfo { customerInfo, error ->
if (customerInfo?.entitlements?.get("pro")?.isActive == true) {
// User has active Pro subscription
unlockProFeatures()
}
}
}
fun purchasePackage(activity: Activity, package: Package) {
Purchases.sharedInstance.purchasePackage(activity, package) { customerInfo, _, error ->
if (customerInfo?.entitlements?.get("pro")?.isActive == true) {
unlockProFeatures()
}
}
}
fun restorePurchases() {
Purchases.sharedInstance.restorePurchases { customerInfo, error ->
if (customerInfo?.entitlements?.get("pro")?.isActive == true) {
unlockProFeatures()
}
}
}
private fun unlockProFeatures() {
// Enable premium features, remove ads, etc.
}
}
Expected behavior: RevenueCat creates a single source of truth for subscription status across platforms. Restore purchases works across devices. The SDK handles receipt validation and expiration tracking.
Monetization Architecture
flowchart TD
A[User opens app] --> B{Free or Premium?}
B -->|Free| C[Show banner ads]
B -->|Premium| D[Hide all ads]
C --> E{Engaged user?}
E -->|High intent| F[Show interstitial ad]
E -->|Needs feature| G[Show paywall]
F --> H[Reward: continue]
G --> I{User purchases?}
I -->|Subscription| J[Unlock all premium]
I -->|One-time| K[Unlock specific feature]
I -->|Decline| L[Continue with ads]
J --> M[Receipt validation]
K --> M
Common Errors & Mistakes
1. Not Handling Purchase Cancellation Properly
Mistake: Only checking subscription status at app launch, not listening for real-time changes via the purchase listener.
Fix: Register a purchase update listener and revoke access immediately when a subscription expires or is cancelled.
2. Silencing AdMob Errors
Mistake: Logging ad load failures with Log.e but not showing a fallback UI, leaving an empty ad container.
Fix: Add an AdListener with onAdFailedToLoad callback that hides the ad container when no ad is available.
3. Offering Consumables Without Proper Inventory Management
Mistake: Letting users buy consumables (coins, gems) without persisting the purchase to a server, enabling offline cheating.
Fix: Track consumable balances on your backend. Validate every purchase server-side before granting the item.
4. Not Testing Sandbox Purchases
Mistake: Releasing subscription code without testing the full lifecycle (purchase, renewal, cancellation, refund) in the sandbox environment.
Fix: Use App Store Connect sandbox testers and Google Play license testers. Test all edge cases before production.
5. Ignoring Introductory Pricing
Mistake: Setting up subscriptions without free trials or introductory offers, reducing conversion by 40-60 percent.
Fix: Use RevenueCat or platform APIs to offer a 3-day free trial or first-month discount. Display the trial prominently on the paywall.
Practice Questions
Question 1
What is the difference between a consumable and a non-consumable purchase?
Show answer
Consumables (coins, hints) can be purchased multiple times and are used up. Non-consumables (premium unlock, ad removal) are purchased once and permanently associated with the user's account.Question 2
Why must purchases be acknowledged on Android?
Show answer
Google Play requires purchase acknowledgment within 3 days or the payment is refunded. Acknowledgment confirms the app has delivered the purchased item and prevents automatic refunds.Question 3
What is RevenueCat and why use it?
Show answer
RevenueCat is a subscription management platform that abstracts Android and iOS billing APIs into a unified SDK. It handles receipt validation, status tracking, and cross-platform synchronization, reducing integration time by weeks.Question 4
How does an auto-renewable subscription work?
Show answer
An auto-renewable subscription bills the user at regular intervals (monthly, yearly) until the user cancels. The app store handles billing, receipt delivery, and renewal notifications. The app checks the subscription status on launch and via webhooks.Challenge
Implement a complete freemium model: a free tier with banner ads and limited features, a one-time purchase to remove ads, and a monthly subscription for all premium features. Use RevenueCat for cross-platform management. Write server-side receipt validation for the subscription. Implement a paywall screen that shows trial offer and pricing tiers.
Mini Project: Subscription Analytics Dashboard
Build a dashboard that displays subscription metrics using RevenueCat webhooks: current MRR, active subscribers, trial conversion rate, churn rate, and LTV by cohort. Use a backend webhook endpoint to receive RevenueCat events and store them in PostgreSQL. Display charts with a 7-day and 30-day trend using Chart.js.
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Author: DodaTech | Last updated: June 22, 2026
DodaTech tutorials are built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro — security tools used by millions worldwide.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro