admanagekit-mcp-server 1.0.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/content/README.md +411 -177
  2. package/content/docs/API_REFERENCE.md +298 -2
  3. package/content/docs/APP_PURCHASE_GUIDE.md +343 -2
  4. package/content/docs/BANNER_AD_IMPROVEMENTS.md +49 -0
  5. package/content/docs/COMPOSE_INTEGRATION.md +24 -0
  6. package/content/docs/LOADING_STRATEGY_EXAMPLES.md +1 -1
  7. package/content/docs/MULTI_PROVIDER_WATERFALL.md +238 -0
  8. package/content/docs/NATIVE_TEMPLATE_VIEW.md +67 -0
  9. package/content/docs/V4_API_PLAN.md +482 -0
  10. package/content/docs/YANDEX_INTEGRATION.md +175 -0
  11. package/content/docs/app-open-ads.md +70 -14
  12. package/content/docs/release-notes/RELEASE_NOTES_v3.3.6.md +114 -0
  13. package/content/docs/release-notes/RELEASE_NOTES_v3.3.7.md +82 -0
  14. package/content/docs/release-notes/RELEASE_NOTES_v3.3.8.md +116 -0
  15. package/content/docs/release-notes/RELEASE_NOTES_v3.3.9.md +87 -0
  16. package/content/docs/release-notes/RELEASE_NOTES_v3.4.0.md +124 -0
  17. package/content/docs/release-notes/RELEASE_NOTES_v3.4.1.md +137 -0
  18. package/content/docs/release-notes/RELEASE_NOTES_v3.4.2.md +66 -0
  19. package/content/docs/release-notes/RELEASE_NOTES_v3.4.3.md +80 -0
  20. package/content/docs/release-notes/RELEASE_NOTES_v3.4.4.md +85 -0
  21. package/content/docs/release-notes/RELEASE_NOTES_v3.4.5.md +85 -0
  22. package/content/docs/release-notes/RELEASE_NOTES_v3.4.6.md +129 -0
  23. package/content/docs/release-notes/RELEASE_NOTES_v3.5.7.md +130 -0
  24. package/content/docs/release-notes/RELEASE_NOTES_v3.5.8.md +95 -0
  25. package/content/docs/release-notes/RELEASE_NOTES_v3.5.9.md +55 -0
  26. package/content/docs/release-notes/RELEASE_NOTES_v3.6.0.md +76 -0
  27. package/content/docs/release-notes/RELEASE_NOTES_v4.2.0.md +62 -0
  28. package/content/docs/release-notes/RELEASE_NOTES_v4.3.0.md +137 -0
  29. package/content/docs/release-notes/RELEASE_NOTES_v4.3.1.md +52 -0
  30. package/content/docs/release-notes/RELEASE_NOTES_v4.3.2.md +46 -0
  31. package/content/docs/release-notes/RELEASE_NOTES_v4.3.3.md +73 -0
  32. package/content/docs/release-notes/RELEASE_NOTES_v4.3.4.md +66 -0
  33. package/content/docs/release-notes/RELEASE_NOTES_v4.3.5.md +54 -0
  34. package/content/docs/release-notes/RELEASE_NOTES_v4.4.0.md +202 -0
  35. package/content/wiki/App-Open-Ads.md +207 -52
  36. package/content/wiki/Banner-Ads.md +203 -36
  37. package/content/wiki/Billing-Integration.md +90 -3
  38. package/content/wiki/Configuration.md +13 -5
  39. package/content/wiki/Home.md +4 -0
  40. package/content/wiki/Jetpack-Compose.md +16 -0
  41. package/content/wiki/Multi-Provider-Waterfall.md +187 -0
  42. package/content/wiki/Subscription-Offers.md +229 -0
  43. package/content/wiki/Subscriptions.md +89 -2
  44. package/content/wiki/Yandex-Integration.md +159 -0
  45. package/content/wiki/_Sidebar.md +5 -0
  46. package/dist/tools/documentation.js +40 -16
  47. package/dist/tools/documentation.js.map +1 -1
  48. package/dist/types.d.ts +0 -3
  49. package/dist/types.js +19 -37
  50. package/dist/types.js.map +1 -1
  51. package/dist/utils/doc-loader.d.ts +24 -0
  52. package/dist/utils/doc-loader.js +77 -1
  53. package/dist/utils/doc-loader.js.map +1 -1
  54. package/package.json +3 -2
package/content/README.md CHANGED
@@ -1,203 +1,162 @@
1
1
  # AdManageKit
2
2
  [![JitPack](https://jitpack.io/v/i2hammad/AdManageKit.svg)](https://jitpack.io/#i2hammad/AdManageKit)
3
- ![API](https://img.shields.io/badge/API-21%2B-brightgreen.svg?style=flat)
3
+ ![API](https://img.shields.io/badge/API-24%2B-brightgreen.svg?style=flat)
4
4
  ![License](https://img.shields.io/badge/License-MIT-blue.svg)
5
5
 
6
6
  AdManageKit is a comprehensive Android library designed to simplify the integration and management of Google AdMob ads, Google Play Billing, and User Messaging Platform (UMP) consent.
7
7
 
8
- **Latest Version `3.3.5`** adds **App Open Loading Strategies** - proper loading strategy support (ON_DEMAND, ONLY_CACHE, HYBRID) with ad freshness tracking and auto-reload configuration.
8
+ **Latest Version `4.4.0`** is a minor release focused on the billing module. Subscription offers can now be **purchased individually** — `subscribe(activity, offer)` buys exactly the plan the user tapped, where previously the library always resolved the token itself and could charge for whichever offer Play listed last. It also adds offer lookup by id/base plan/tag, cross-cadence price normalization (`BillingPeriod`, `getSavingsPercent`, `getFormattedPricePerMonth`), trial-eligibility checks, Play Billing 9 one-time product offers (discounts, rentals, pre-orders, limited quantity), client-side **account hold** detection, Play payment-recovery messaging, and product-details diagnostics. Purely additive, with one behavior change: an on-hold subscription now reports `ON_HOLD` and is no longer `isSubscriptionActive()`. See [Release Notes v4.4.0](docs/release-notes/RELEASE_NOTES_v4.4.0.md).
9
9
 
10
- ---
10
+ **Version `4.3.5`** is a patch release that makes `appOpenAdFreshnessThreshold` (default 4 hours) actually enforced on every app open show path. Previously only `ON_DEMAND` consulted it — `HYBRID`, `ONLY_CACHE`, and the multi-provider waterfall checked only whether an ad object existed and could show a day-old cached ad. Stale ads are now discarded and replaced instead of shown. It also settles the `appOpenAdTimeout` default at **10 seconds** (the property said 4s while `resetToDefaults()` said 10s — 4s was too short for the fetch-with-dialog paths). No API changes. See [Release Notes v4.3.5](docs/release-notes/RELEASE_NOTES_v4.3.5.md).
11
11
 
12
- ## Next-Gen GMA SDK Version
12
+ **Version `4.3.4`** is a patch release that restores the pre-4.2.0 default adaptive banner height: `ADAPTIVE` again requests the standard anchored adaptive size (~50-90dp) instead of the taller *large anchored adaptive* format the Next-Gen migration had switched to. The taller format is now opt-in via `BannerAdSize.ADAPTIVE_LARGE` (`app:bannerAdSize="adaptive_large"` in XML). See [Release Notes v4.3.4](docs/release-notes/RELEASE_NOTES_v4.3.4.md).
13
13
 
14
- > **Beta Notice:** The Next-Gen GMA SDK (`com.google.android.libraries.ads.mobile.sdk`) is currently in **beta by Google**, and AdManageKit's nextgen branch is also in **beta**. The underlying Google SDK may receive breaking changes until it reaches stable release. For production use, the **main branch** is the stable option.
14
+ **Version `4.3.3`** is a patch release that improves native ad media quality: every native request now sends a media-aspect-ratio hint matched to each template's `MediaView` slot shape and carries global `VideoOptions` (start-muted by default). New `AdManageKitConfig` fields and a per-view `NativeTemplateView.setMediaAspect(...)` override expose the behavior; all defaults preserve prior request behavior. See [Release Notes v4.3.3](docs/release-notes/RELEASE_NOTES_v4.3.3.md).
15
15
 
16
- AdManageKit offers a **Next-Gen GMA SDK** version on the `nextgen` branch, featuring Google's modern preloader-based ad loading system.
16
+ **Version `4.3.2`** is a patch release that fixes banner shimmer sizing — the placeholder now reserves the real adaptive-banner height from the first frame and in the layout preview instead of collapsing to ~50dp — and stops the Compose native ad from clipping its call-to-action button. Also bumps the Android Gradle Plugin to 9.3.0. See [Release Notes v4.3.2](docs/release-notes/RELEASE_NOTES_v4.3.2.md).
17
17
 
18
- ### Why Next-Gen?
18
+ **Version `4.3.1`** is a patch release fixing crashes from Next-Gen SDK callbacks delivered on background threads — interstitial `onNextAction`, banner/native waterfall chains, and `NativeTemplateView.onFailedToLoad` are now marshalled to the main thread — plus a load-site guard for app open ads. See [Release Notes v4.3.1](docs/release-notes/RELEASE_NOTES_v4.3.1.md).
19
19
 
20
- | Feature | Main Branch (GMS SDK) | Next-Gen Branch |
21
- |---------|----------------------|-----------------|
22
- | SDK | `play-services-ads` (stable) | `ads-mobile-sdk` (beta) |
23
- | Ad Loading | Traditional load/show | Preloader-based with auto-refill |
24
- | Threading | Manual main thread dispatch | Automatic background thread safety |
25
- | Buffer System | N/A | Configurable ad buffers per type |
26
- | Background Handling | Basic | Smart pending ad queue |
20
+ **Version `4.3.0`** adds all standard AdMob banner sizes (`BannerAdSize`), fully custom native ad templates on `NativeTemplateView`, and a redesigned size-adaptive banner shimmer with night-mode support. See [What's New in 4.3.0](#whats-new-in-430).
27
21
 
28
- ### Next-Gen Features
22
+ Since **4.2.0** the library runs on the Google Mobile Ads **Next-Gen SDK** (`ads-mobile-sdk`, stable) and Google Play Billing Library **9.1.0** — see [Next-Gen GMA SDK](#next-gen-gma-sdk) below and [Migrating to 4.2.0](#migrating-to-420) if you're upgrading from 3.x/4.1.
29
23
 
30
- - **Preloader System**: SDK automatically loads next ad after one is consumed
31
- - **Background-Aware Ads**: App open ads won't show when app is in background
32
- - **Pending Ad Queue**: Ads that load while backgrounded are saved for return
33
- - **Configurable Buffers**: Set how many ads to keep ready per type
24
+ ---
34
25
 
35
- ```kotlin
36
- // Next-Gen preloader configuration
37
- AdManageKitConfig.apply {
38
- enableInterstitialPreloader = true
39
- enableAppOpenPreloader = true
40
- interstitialPreloaderBufferSize = 2
41
- }
42
- ```
26
+ ## Next-Gen GMA SDK
43
27
 
44
- ### Migration Compatibility
28
+ As of v4.2.0, AdManageKit runs on Google's **Next-Gen Google Mobile Ads SDK** (`com.google.android.libraries.ads.mobile.sdk`, stable `1.2.1`) instead of the legacy `com.google.android.gms:play-services-ads`. This isn't a branch or an opt-in — it's the only version of AdManageKit going forward.
45
29
 
46
- Both branches use the same callback signatures via type aliases:
47
- - `AdKitError` → resolves to appropriate SDK error type
48
- - `AdKitLoadError` → resolves to appropriate SDK load error type
49
- - `AdKitValue` → resolves to appropriate SDK value type
30
+ ### Why the move
50
31
 
51
- Your callback implementations work on both branches without changes.
32
+ The legacy Google Mobile Ads SDK is in maintenance mode; new AdMob features (Ad Inspector improvements, mediation updates, in-app price-increase messaging, etc.) land on the Next-Gen SDK first, and Google's own guidance is to move new integrations there. AdManageKit had a separate `nextgen` branch exploring this since it was in beta and diverging from `main`'s waterfall/Yandex/billing work — now that the SDK is stable, that split is gone.
52
33
 
53
- ### Which Version Should I Use?
34
+ ### What's different under the hood
54
35
 
55
- | Use Case | Recommended |
56
- |----------|-------------|
57
- | Production apps (stable) | **Main branch** (v3.3.5) |
58
- | New projects wanting latest features | **Nextgen branch** (v4.1.1) |
59
- | Testing preloader system | **Nextgen branch** |
60
- | Risk-averse production | **Main branch** |
36
+ - **Threading**: Next-Gen SDK callbacks fire on a background thread (the legacy SDK guaranteed main thread). Every ad manager, provider, and view in this library already wraps its callback bodies in `Handler(Looper.getMainLooper()).post {}` — this is transparent to you as a consumer.
37
+ - **Initialization**: `MobileAds.initialize()` must be called explicitly once, before any ad request — the legacy SDK's silent lazy-init on first use no longer exists. AdManageKit doesn't call this for you (it doesn't own your app's consent flow); see the sample app's `MyApplication.kt` for the recommended pattern.
38
+ - **Callback types**: Where AdManageKit exposes ad-network error/value types through `AdKitError`, `AdKitLoadError`, and `AdKitValue` (used by `AdManagerCallback`, `AdLoadCallback`, `AdCallback`, and friends), those aliases now resolve to Next-Gen SDK types instead of legacy ones. Your callback *implementations* (`onFailedToLoad(error)`, `onPaidEvent(value)`, etc.) don't need to change — only code that reads legacy-only members of those objects does. See [Migrating to 4.2.0](#migrating-to-420).
61
39
 
62
- ---
40
+ ## What's New in 4.4.0
41
+
42
+ A billing-focused minor release. Everything is additive — no existing method signature changed — with one deliberate behavior change around account hold.
63
43
 
64
- ## What's New in 3.3.5
44
+ ### Buy the Offer the User Actually Tapped
65
45
 
66
- ### App Open Loading Strategies
67
- - **Proper Loading Strategy Support**: AppOpenManager now fully supports `AdLoadingStrategy` (ON_DEMAND, ONLY_CACHE, HYBRID)
68
- - **Ad Freshness Tracking**: Cached ads track load time to prevent showing stale ads
69
- - **Smart Cache Usage**: ON_DEMAND strategy uses cached ads if still fresh (within `appOpenAdFreshnessThreshold`)
70
- - **Auto-Reload Config**: New `appOpenAutoReload` setting to control automatic reloading after ad dismissal
71
- - **Deprecated**: `appOpenFetchFreshAd` - migrate to `appOpenLoadingStrategy`
46
+ `AppPurchase` could already *describe* subscription offers (`getOffers`, `getTrialOffer`, `getBaseOffer` since 3.5.7), but it could not *act* on them. `subscribe(activity, subsId)` always resolved the offer token itself — the configured `trialId`, and failing that **whichever offer Play listed last**. On a product with three plans, a user tapping "Yearly" could be charged for monthly, purely because of list order.
72
47
 
73
48
  ```kotlin
74
- AdManageKitConfig.apply {
75
- // Loading strategy (ON_DEMAND, ONLY_CACHE, HYBRID)
76
- appOpenLoadingStrategy = AdLoadingStrategy.HYBRID
49
+ val offers = AppPurchase.getInstance().getOffers("premium_sub")
50
+ // …render them, then buy exactly what the user chose:
51
+ AppPurchase.getInstance().subscribe(activity, offers[selectedIndex])
52
+ ```
77
53
 
78
- // Freshness threshold for cached ads (default: 4 hours)
79
- appOpenAdFreshnessThreshold = 4.hours
54
+ The same applies to one-time products (`purchase(activity, offer)`) and upgrades (`updateSubscription(activity, newSubsId, offerToken, oldToken, mode)`). The original no-token overloads are unchanged.
80
55
 
81
- // Auto-reload after ad dismissal (default: true)
82
- appOpenAutoReload = true
83
- }
56
+ ### Offer Lookup by Id, Base Plan or Tag
57
+
58
+ ```kotlin
59
+ billing.getIntroOffer("premium_sub") // first with an intro price
60
+ billing.getOfferByBasePlanId("premium_sub", "yearly")
61
+ billing.getOfferByTag("premium_sub", "popular") // Play Console offer tag
62
+ billing.getBestValueOffer("premium_sub") // lowest cost per month
63
+ billing.getCheapestFirstCycleOffer("premium_sub") // cheapest way in
84
64
  ```
85
65
 
86
- ## What's New in 3.3.4
66
+ Offer **tags** are the useful one here: tag offers in the Play Console and select them by tag, and a paywall can be re-targeted without shipping an app update.
87
67
 
88
- ### Subscription Expiry Verification
89
- - **Server-Side Verification**: New API to verify subscriptions and get accurate expiry dates from your backend
90
- - **Expiry Methods**: `getExpiryTimeFormatted()`, `getRemainingDays()`, `isExpired()` on PurchaseResult
91
- - **AppPurchase Helpers**: `getSubscriptionExpiryTime()`, `getSubscriptionRemainingDays()`, `isSubscriptionExpired()`
68
+ ### Price Comparison Without Parsing ISO-8601
69
+
70
+ Play returns billing periods as raw strings (`"P1M"`, `"P1Y"`). The new `BillingPeriod` parses them and normalizes prices so plans of different cadences compare fairly:
92
71
 
93
72
  ```kotlin
94
- // Set up verification callback
95
- AppPurchase.getInstance().setSubscriptionVerificationCallback { packageName, subscriptionId, purchaseToken, listener ->
96
- yourApi.verifySubscription(purchaseToken) { expiryMillis ->
97
- val details = SubscriptionVerificationCallback.SubscriptionDetails.Builder()
98
- .setExpiryTimeMillis(expiryMillis)
99
- .build()
100
- listener.onVerified(details)
101
- }
102
- }
73
+ val period = BillingPeriod.parse("P1Y")!!
74
+ period.unit // Unit.YEAR
75
+ period.count // 1 → getQuantityString(R.plurals.years, count, count)
76
+ period.totalMonths // 12.0
103
77
 
104
- // Verify and get expiry
105
- AppPurchase.getInstance().verifySubscription("premium_monthly",
106
- object : AppPurchase.SubscriptionVerificationListener {
107
- override fun onVerified(subscription: PurchaseResult) {
108
- val expiryDate = subscription.getExpiryTimeFormatted("dd MMM yyyy")
109
- val daysLeft = subscription.getRemainingDays()
110
- }
111
- override fun onVerificationFailed(error: String?) { }
112
- }
113
- )
78
+ billing.getSavingsPercent("premium_monthly", "premium_yearly") // 50 "Save 50%"
79
+ billing.getFormattedPricePerMonth("premium_yearly") // "$5.00"
114
80
  ```
115
81
 
116
- ## What's New in 3.3.3
82
+ `OfferInfo` also gained `firstCyclePrice` (what the user pays *today* — "Free", "$1.99" or "$9.99"), `pricePerMonthMicros`, `introDiscountPercent`, `trialDays`, `introTotalDays`, `isBaseOffer` and `hasTag(...)`.
117
83
 
118
- ### SDK-Agnostic Type Aliases
119
- - **Migration Compatibility**: Callbacks use `AdKitError`, `AdKitLoadError`, `AdKitValue` type aliases
120
- - **Same Signatures**: Your callback implementations work across both SDK versions
121
- - **Easy Migration**: Switch between main (GMS SDK) and nextgen (Next-Gen SDK) branches without code changes
84
+ ### Trial Eligibility
85
+
86
+ Google Play filters offers per account a user who already used a trial simply doesn't receive that offer. So offer presence *is* the eligibility signal, now named explicitly:
122
87
 
123
88
  ```kotlin
124
- // Callbacks now use type aliases that resolve to the appropriate SDK types
125
- object : AdLoadCallback() {
126
- override fun onFailedToLoad(error: AdKitError?) { // Works on both branches
127
- Log.e("Ads", "Failed: ${error?.message}")
128
- }
129
- override fun onPaidEvent(adValue: AdKitValue) { // Works on both branches
130
- trackRevenue(adValue.valueMicros)
131
- }
132
- }
89
+ button.text = if (billing.isEligibleForFreeTrial("premium_sub")) "Start free trial" else "Subscribe"
133
90
  ```
134
91
 
135
- ## What's New in 3.3.2
92
+ This stops a paywall promising a trial the user cannot claim and then having Play charge them immediately.
136
93
 
137
- ### InterstitialAdBuilder Fixes
138
- - **Ad Unit Assignment**: Fixed ad unit not being assigned to AdManager on first HYBRID fetch
139
- - **Immediate Availability**: `adUnit()` now sets AdManager.adUnitId immediately for first-call reliability
94
+ ### Play Billing 9 Features That Were Unused
140
95
 
141
- ### everyNthTime Feature Fix
142
- - **Counter Persistence**: Call counter now persists across builder instances in AdManager
143
- - **Counter API**: New methods to manage counters: `getCallCount()`, `resetCallCount()`, `resetAllCallCounts()`
96
+ - **One-time product offers** — a single INAPP product can carry several offers (a full price plus a launch discount, a rental, a pre-order, a limited-quantity drop). New `OneTimeOfferInfo` + `getOneTimeOffers()` / `getBestOneTimeOffer()`; the legacy `getOneTimePurchaseOfferDetails()` exposes only one of them
97
+ - **Payment recovery** `showInAppMessages(activity, listener)` shows Play's fix-your-payment flow for declined subscriptions
98
+ - **Product-details diagnostics** `setProductDetailsListener(...)` and `getUnfetchedProducts()` report *which* ids Play declined and why, so an empty paywall is no longer silent
99
+ - **Installment plans** — `OfferInfo.isInstallmentPlan` and commitment payment counts
100
+ - **Fraud prevention & EU disclosure** — `setObfuscatedAccountId()`, `setObfuscatedProfileId()`, `setOfferPersonalized()`, applied to every flow the library launches
144
101
 
145
- ```kotlin
146
- // everyNthTime now works correctly
147
- InterstitialAdBuilder.with(activity)
148
- .adUnit(adUnitId)
149
- .everyNthTime(3) // Shows on 3rd, 6th, 9th calls, etc.
150
- .show { navigateNext() }
102
+ ### Account Hold Detected Client-Side (behavior change)
151
103
 
152
- // Reset counters when user upgrades
153
- AdManager.getInstance().resetAllCallCounts()
154
- ```
104
+ When Play cannot charge a subscriber, the subscription enters **account hold**. This previously required server-side verification; Play Billing 9 reports it client-side, so `PurchaseResult.isSuspended()` is now populated and `getSubscriptionState()` returns `ON_HOLD`.
155
105
 
156
- ### New Native Templates
157
- - **flexible**: Adaptive layout that adjusts to available space
158
- - **icon_left**: Icon on left side with MediaView at top for GridView display
159
- - **top_icon_media**: Icon at top, MediaView in middle, CTA at bottom
106
+ > **`isSubscriptionActive()` therefore returns `false` during account hold.** That matches Google's requirement — the user's payment was declined and they must not keep premium access. If your app deliberately keeps serving on-hold users, check `isSuspended()` explicitly.
160
107
 
161
108
  ```kotlin
162
- nativeTemplateView.setTemplate(NativeAdTemplate.FLEXIBLE)
163
- nativeTemplateView.setTemplate(NativeAdTemplate.ICON_LEFT)
164
- nativeTemplateView.setTemplate(NativeAdTemplate.TOP_ICON_MEDIA)
109
+ if (billing.hasSubscriptionOnHold()) {
110
+ billing.showInAppMessages(activity, object : InAppMessageListener {
111
+ override fun onSubscriptionRecovered(purchaseToken: String) { refreshPremiumUi() }
112
+ override fun onNoActionNeeded() { }
113
+ })
114
+ }
165
115
  ```
166
116
 
167
- ## What's New in 3.1.0
117
+ See [Release Notes v4.4.0](docs/release-notes/RELEASE_NOTES_v4.4.0.md) and the [Subscription Offers wiki page](https://github.com/i2hammad/AdManageKit/wiki/Subscription-Offers) for full details.
118
+
119
+ ## What's New in 4.3.0
120
+
121
+ ### All Standard Banner Sizes
122
+ `BannerAdView` and `BannerAdCompose` now support every standard AdMob banner size via the new `BannerAdSize` enum — `ADAPTIVE` (default, unchanged behavior), `BANNER` (320x50), `LARGE_BANNER` (320x100), `MEDIUM_RECTANGLE` (300x250), `FULL_BANNER` (468x60), `LEADERBOARD` (728x90). Select programmatically (`loadBanner(activity, adUnitId, BannerAdSize.MEDIUM_RECTANGLE)`), in XML (`app:bannerAdSize="medium_rectangle"`), or in Compose (`adSize = BannerAdSize.MEDIUM_RECTANGLE`). The size carries through retries, auto-refresh, and the multi-provider waterfall. See the [Banner Ad Guide](docs/BANNER_AD_IMPROVEMENTS.md).
123
+
124
+ ### Custom Native Ad Templates
125
+ If none of the 37 built-in `NativeTemplateView` presets fit your design, supply your own layout: `setCustomTemplate(layoutResId, shimmerResId, sizeHint)` programmatically, `app:customAdLayout`/`app:customAdShimmerLayout` in XML, or `customLayoutResId` on `NativeTemplateCompose`. The layout's root must be a Next-Gen SDK `NativeAdView` reusing the standard asset ids. See the [NativeTemplateView guide](docs/NATIVE_TEMPLATE_VIEW.md#custom-templates).
126
+
127
+ ### Redesigned Banner Shimmer + Night Mode
128
+ The banner loading placeholder now adapts to the requested size (a 300x250 request shows a proper media-block placeholder, not a lone 50dp row), reserves the exact ad size on every path including waterfalls (no layout jump), centers to match the loaded ad, and follows the system dark theme instead of rendering a hardcoded white card.
129
+
130
+ ### App Open Ads: Late MobileAds Initialization Support
131
+ `AppOpenManager` no longer crashes if constructed before `MobileAds.initialize()` completes: every load path is guarded, callback-driven flows (splash screens) wait for the SDK within their existing timeout and always receive a terminal callback, and the new `isMobileAdsReady()` confirms the SDK can accept requests. See the [App Open Ads guide](docs/app-open-ads.md#initialization-order--late-initialization).
132
+
133
+ > **Compatibility:** source-compatible; `ADAPTIVE` keeps pre-4.3.0 behavior everywhere. Recompile against 4.3.0 rather than hot-swapping the AAR (Compose composables gained default parameters).
168
134
 
169
- ### FRESH_WITH_CACHE_FALLBACK Strategy Fix
170
- - **Auto-Caching**: Successfully loaded ads are now properly cached for future fallback
171
- - **RecyclerView Optimized**: Fresh ads build up the cache over time for better fallback availability
172
- - **Complete Implementation**: Strategy now works as documented
135
+ See [Release Notes v4.3.0](docs/release-notes/RELEASE_NOTES_v4.3.0.md) for full details.
173
136
 
174
- ### New Native Template
175
- - **MEDIUM_HORIZONTAL**: 55% media (left) / 45% content (right) horizontal split layout
176
- - **27 Total Templates**: 21 standard + 6 video templates
137
+ ## What's New in 4.2.0
177
138
 
178
- ## What's New in 3.0.0
139
+ ### Google Mobile Ads Next-Gen SDK
140
+ `com.google.android.gms:play-services-ads` is replaced by the stable **Next-Gen SDK** (`ads-mobile-sdk:1.2.1`) across every ad manager, provider, native view, and the Compose module. This is the change described in [Next-Gen GMA SDK](#next-gen-gma-sdk) above — read that section first if you're upgrading.
179
141
 
180
- ### Ad Pool System
181
- - **Multiple Ad Units**: Load multiple interstitial ad units into a pool for maximum show rate
182
- - **Auto-Selection**: Shows ANY available ad from the pool when requested
183
- - **Duplicate Prevention**: Automatically skips duplicate load requests
142
+ ### Google Play Billing Library 9.1.0
143
+ Upgraded from 8.3.0. No code changes were needed in `AppPurchase` the wrapper was already on v8+ patterns. Source- and behavior-compatible.
184
144
 
185
- ### Smart Splash Ads
186
- - **showOrWaitForAd()**: Single method handles all splash scenarios automatically
187
- - **Intelligent Behavior**: Shows cached ad immediately, waits if loading, or fetches fresh
145
+ ### compileSdk 37
146
+ Required transitively by `androidx-core-ktx` 1.19.0+. Bump your app's `compileSdk` to 37+.
188
147
 
189
- ### App Open Ad Prefetching
190
- - **prefetchNextAd()**: Prefetch ads before external intents for instant display on return
191
- - **isAdLoading()**: Check if ad is currently being fetched
148
+ ### Fixes
149
+ - Native ad `MediaView` rendering blank in `NativeTemplateView` and the programmatic native provider (a leftover legacy-SDK pattern was interfering with the Next-Gen SDK's automatic media rendering)
150
+ - `NativeAdView.mediaView` returning `null` for dynamically-inflated templates (the getter's auto-discovery needs the view to be window-attached; affected views now use `findViewById()` directly)
151
+ - A Native Validator false-positive risk on deeply-nested templates like `CARD_MODERN`, caused by registering the ad before the view had been through a layout pass
192
152
 
193
- ### Enhanced Analytics
194
- - **Session Tracking**: Fill rate, show rate, and impression tracking
195
- - **getAdStats()**: Access session-level ad performance metrics
153
+ > **Compatibility:** **not** fully source-compatible — see [Migrating to 4.2.0](#migrating-to-420). Most consumers (anyone who only calls methods on AdManageKit's own callback types, without custom native XML layouts) are unaffected.
196
154
 
197
- ### Technical Improvements
198
- - Modern `WindowInsetsController` API (replaces deprecated systemUiVisibility)
199
- - Thread-safe ad pool with `ConcurrentHashMap`
200
- - Cross-ad-unit fallback for native ads
155
+ See [Release Notes v4.2.0](docs/release-notes/RELEASE_NOTES_v4.2.0.md) for full details.
156
+
157
+ For the 3.6.0 release (Yandex template matching, waterfall-aware programmatic loader), see [Release Notes v3.6.0](docs/release-notes/RELEASE_NOTES_v3.6.0.md).
158
+
159
+ For previous versions, see the [Changelog](CHANGELOG.md) or individual [release notes](docs/release-notes/).
201
160
 
202
161
  ## Screenshots
203
162
 
@@ -229,40 +188,21 @@ dependencyResolutionManagement {
229
188
 
230
189
  **Step 2:** Add dependencies to your app's `build.gradle`:
231
190
 
232
- <table>
233
- <tr>
234
- <th>Main Branch (Stable GMS SDK)</th>
235
- <th>Next-Gen Branch (Beta GMA SDK)</th>
236
- </tr>
237
- <tr>
238
- <td>
239
-
240
191
  ```groovy
241
- implementation 'com.github.i2hammad.AdManageKit:ad-manage-kit:v3.3.5'
242
- implementation 'com.github.i2hammad.AdManageKit:ad-manage-kit-billing:v3.3.5'
243
- implementation 'com.github.i2hammad.AdManageKit:ad-manage-kit-core:v3.3.5'
192
+ implementation 'com.github.i2hammad.AdManageKit:ad-manage-kit:v4.4.0'
193
+ implementation 'com.github.i2hammad.AdManageKit:ad-manage-kit-billing:v4.4.0'
194
+ implementation 'com.github.i2hammad.AdManageKit:ad-manage-kit-core:v4.4.0'
244
195
 
245
196
  // For Jetpack Compose support
246
- implementation 'com.github.i2hammad.AdManageKit:ad-manage-kit-compose:v3.3.5'
247
- ```
248
-
249
- </td>
250
- <td>
197
+ implementation 'com.github.i2hammad.AdManageKit:ad-manage-kit-compose:v4.4.0'
251
198
 
252
- ```groovy
253
- implementation 'com.github.i2hammad.AdManageKit:ad-manage-kit-nextgen:v4.1.1'
254
- implementation 'com.github.i2hammad.AdManageKit:ad-manage-kit-billing-nextgen:v4.1.1'
255
- implementation 'com.github.i2hammad.AdManageKit:ad-manage-kit-core-nextgen:v4.1.1'
256
-
257
- // For Jetpack Compose support
258
- implementation 'com.github.i2hammad.AdManageKit:ad-manage-kit-compose-nextgen:v4.1.1'
199
+ // For Yandex Ads multi-provider support
200
+ implementation 'com.github.i2hammad.AdManageKit:ad-manage-kit-yandex:v4.4.0'
259
201
  ```
260
202
 
261
- </td>
262
- </tr>
263
- </table>
203
+ **Step 3:** Ensure your app's `compileSdk` is **37 or higher** (required transitively as of 4.2.0).
264
204
 
265
- **Step 3:** Sync your project with Gradle.
205
+ **Step 4:** Sync your project with Gradle.
266
206
 
267
207
  ## Features
268
208
 
@@ -286,7 +226,7 @@ implementation 'com.github.i2hammad.AdManageKit:ad-manage-kit-compose-nextgen:v4
286
226
  - ConditionalAd, CacheWarmingEffect utilities
287
227
 
288
228
  ### AdMob Ads Management
289
- - **Banner Ads**: Auto-refresh, collapsible banners, smart retry
229
+ - **Banner Ads**: All standard sizes (adaptive, 320x50, 320x100, 300x250, 468x60, 728x90), auto-refresh, collapsible banners, smart retry
290
230
  - **Native Ads**: Small, Medium, Large formats with caching
291
231
  - **Interstitial Ads**: Time/count-based triggers, dialog support
292
232
  - **App Open Ads**: Lifecycle-aware with activity exclusion
@@ -306,14 +246,32 @@ implementation 'com.github.i2hammad.AdManageKit:ad-manage-kit-compose-nextgen:v4
306
246
  - Circuit breaker for failing ad units
307
247
  - Memory leak prevention with WeakReference
308
248
 
249
+ ### Billing & Monetization
250
+ - **Subscriptions & One-Time Products**: Categories (consumable, feature unlock, remove ads, lifetime premium) with automatic acknowledgment
251
+ - **Offers**: Enumerate every trial / introductory / base offer and purchase a *specific* one (v4.4.0+)
252
+ - **Price Comparison**: ISO-8601 period parsing, per-month normalization, "Save 40%" badges (v4.4.0+)
253
+ - **Trial Eligibility**: Ask whether *this* account can still claim a trial before promising one (v4.4.0+)
254
+ - **Payment Recovery**: Client-side account-hold detection plus Play's in-app recovery flow (v4.4.0+)
255
+ - [In-App Purchase Guide](docs/APP_PURCHASE_GUIDE.md)
256
+
309
257
  ### Privacy & Compliance
310
258
  - UMP consent management (GDPR/CCPA)
311
259
  - Automatic ad hiding for purchased users
260
+ - Obfuscated account/profile identifiers and EU personalized-pricing disclosure (v4.4.0+)
261
+
262
+ ### Multi-Provider Waterfall (New)
263
+ - **Multiple Ad Networks**: Load ads from AdMob, Yandex, and more with automatic fallback
264
+ - **Zero Code Changes**: Configure provider chains once; all existing API calls use waterfall automatically
265
+ - **Per-Ad-Type Chains**: Configure different provider orders for each ad format
266
+ - **Region-Based**: Prioritize providers by user locale (e.g., Yandex first for Russia)
267
+ - [View Waterfall Documentation](docs/MULTI_PROVIDER_WATERFALL.md)
268
+ - [Yandex Integration Guide](docs/YANDEX_INTEGRATION.md)
312
269
 
313
270
  ### Multi-Module Architecture
314
271
  - **Core Module**: Shared interfaces and configuration
315
272
  - **Compose Module**: Jetpack Compose integration
316
- - **Billing Module**: Google Play Billing Library v8
273
+ - **Billing Module**: Google Play Billing Library v9
274
+ - **Yandex Module**: Yandex Ads SDK provider
317
275
 
318
276
  ---
319
277
 
@@ -356,6 +314,33 @@ class MyApp : Application() {
356
314
  }
357
315
  ```
358
316
 
317
+ ### Multi-Provider Waterfall (Optional)
318
+
319
+ Add Yandex (or other providers) as fallback ad networks with zero changes to your existing ad loading code:
320
+
321
+ ```groovy
322
+ // Add Yandex module
323
+ implementation 'com.github.i2hammad.AdManageKit:ad-manage-kit-yandex:v3.6.0'
324
+ ```
325
+
326
+ ```kotlin
327
+ // In Application.onCreate(), after AdManageKitConfig setup:
328
+ YandexProviderRegistration.initialize(this)
329
+ val admob = AdMobProviderRegistration.create()
330
+ val yandex = YandexProviderRegistration.create()
331
+
332
+ // Map your AdMob ad unit IDs to Yandex equivalents
333
+ AdUnitMapping.register("ca-app-pub-xxx/your-interstitial", mapOf("yandex" to "R-M-XXXXXX-Y"))
334
+ AdUnitMapping.register("ca-app-pub-xxx/your-native", mapOf("yandex" to "R-M-XXXXXX-Y"))
335
+
336
+ // Set provider chains (order = priority)
337
+ AdProviderConfig.setInterstitialChain(listOf(admob.interstitialProvider, yandex.interstitialProvider))
338
+ AdProviderConfig.setNativeChain(listOf(admob.nativeProvider, yandex.nativeProvider))
339
+ // ... same for banner, app open, rewarded
340
+ ```
341
+
342
+ See [Multi-Provider Waterfall](docs/MULTI_PROVIDER_WATERFALL.md) and [Yandex Integration](docs/YANDEX_INTEGRATION.md) for the full guide.
343
+
359
344
  ### NativeTemplateView (v2.6.0+)
360
345
 
361
346
  #### XML Usage
@@ -417,15 +402,24 @@ nativeTemplateView.loadNativeAd(activity, adUnitId, callback, AdLoadingStrategy.
417
402
  <com.i2hammad.admanagekit.admob.BannerAdView
418
403
  android:id="@+id/bannerAdView"
419
404
  android:layout_width="match_parent"
420
- android:layout_height="wrap_content" />
405
+ android:layout_height="wrap_content"
406
+ app:bannerAdSize="adaptive" />
421
407
  ```
422
408
 
423
409
  ```kotlin
424
410
  bannerAdView.loadBanner(this, "ca-app-pub-xxx/yyy")
425
- // Collapsible banner
411
+ // Fixed size (BANNER, LARGE_BANNER, MEDIUM_RECTANGLE, FULL_BANNER, LEADERBOARD)
412
+ bannerAdView.loadBanner(this, "ca-app-pub-xxx/yyy", BannerAdSize.MEDIUM_RECTANGLE)
413
+ // Collapsible banner (adaptive size only)
426
414
  bannerAdView.loadCollapsibleBanner(this, "ca-app-pub-xxx/yyy", true)
427
415
  ```
428
416
 
417
+ All standard AdMob banner sizes are supported via `BannerAdSize` (v4.3.0); the
418
+ default `ADAPTIVE` is the Google-recommended full-width anchored adaptive banner
419
+ (~50-90dp), and `ADAPTIVE_LARGE` (v4.3.4) opts into the taller Next-Gen *large
420
+ anchored adaptive* format.
421
+ See [Banner Ad Guide](docs/BANNER_AD_IMPROVEMENTS.md) for the full size table.
422
+
429
423
  ### Native Ads (Traditional Views)
430
424
 
431
425
  ```xml
@@ -645,6 +639,62 @@ AppPurchase.getInstance().setPurchaseHistoryListener(object : PurchaseHistoryLis
645
639
  })
646
640
  ```
647
641
 
642
+ #### Subscription Offers & Paywalls (v4.4.0+)
643
+
644
+ A single subscription product usually carries several offers — a base plan, a free trial, an introductory discount. Play returns only the ones this account is eligible for.
645
+
646
+ ```kotlin
647
+ val billing = AppPurchase.getInstance()
648
+
649
+ // Enumerate every offer, then buy exactly the one the user tapped.
650
+ // (subscribe(activity, subsId) picks for you, and may not pick what they chose.)
651
+ val offers = billing.getOffers("premium_sub")
652
+ billing.subscribe(activity, offers[selectedIndex])
653
+
654
+ // Or find a specific one
655
+ billing.getIntroOffer("premium_sub") // first with an intro price
656
+ billing.getOfferByBasePlanId("premium_sub", "yearly")
657
+ billing.getOfferByTag("premium_sub", "popular") // Play Console offer tag
658
+ billing.getBestValueOffer("premium_sub") // lowest cost per month
659
+ ```
660
+
661
+ Render an offer without hand-parsing `ProductDetails` or ISO-8601 periods:
662
+
663
+ ```kotlin
664
+ val offer = billing.getBaseOffer("premium_yearly") ?: return
665
+
666
+ priceLabel.text = offer.basePrice // "$59.99"
667
+ cycleLabel.text = BillingPeriod.formatOf(offer.billingPeriod) // "1 year"
668
+ perMonthLabel.text = AppPurchase.formatPrice(offer.pricePerMonthMicros, offer.currencyCode)
669
+ todayLabel.text = offer.firstCyclePrice // "Free", "$1.99" or "$59.99"
670
+
671
+ // "Save 50%" badge — compares base offers, so trials don't distort it
672
+ val savings = billing.getSavingsPercent("premium_monthly", "premium_yearly")
673
+ savingsBadge.isVisible = savings > 0
674
+ savingsBadge.text = "Save $savings%"
675
+
676
+ // Only promise a trial the user can actually claim
677
+ subscribeButton.text = if (billing.isEligibleForFreeTrial("premium_sub")) {
678
+ "Start free trial"
679
+ } else {
680
+ "Subscribe"
681
+ }
682
+ ```
683
+
684
+ > **Localization:** `BillingPeriod.formatOf()` is an English convenience. For shipped UI use `BillingPeriod.parse(iso)` and pair `unit` + `count` with your own plurals resources.
685
+
686
+ One-time products can carry multiple offers too (discounts, rentals, pre-orders, limited quantity):
687
+
688
+ ```kotlin
689
+ billing.getBestOneTimeOffer("remove_ads")?.let { offer ->
690
+ price.text = offer.formattedPrice
691
+ badge.isVisible = offer.isDiscounted
692
+ badge.text = "-${offer.effectiveDiscountPercent}%"
693
+ buyButton.isEnabled = !offer.isSoldOut && offer.isValidAt()
694
+ buyButton.setOnClickListener { billing.purchase(activity, offer) }
695
+ }
696
+ ```
697
+
648
698
  #### Subscription Management (v2.9.0+)
649
699
 
650
700
  ```kotlin
@@ -652,7 +702,8 @@ AppPurchase.getInstance().setPurchaseHistoryListener(object : PurchaseHistoryLis
652
702
  val state = AppPurchase.getInstance().getSubscriptionState("premium_monthly")
653
703
  when (state) {
654
704
  SubscriptionState.ACTIVE -> showPremiumUI()
655
- SubscriptionState.CANCELLED -> showRenewalPrompt() // Still has access
705
+ SubscriptionState.CANCELLED -> showRenewalPrompt() // Still has access
706
+ SubscriptionState.ON_HOLD -> showFixPaymentPrompt() // Payment declined — no access (v4.4.0+)
656
707
  SubscriptionState.EXPIRED -> showSubscribeButton()
657
708
  }
658
709
 
@@ -671,6 +722,41 @@ AppPurchase.getInstance().changeSubscription(
671
722
  )
672
723
  ```
673
724
 
725
+ #### Account Hold & Payment Recovery (v4.4.0+)
726
+
727
+ When Play cannot charge a subscriber, the subscription enters **account hold**. As of 4.4.0 this is detected client-side — earlier versions needed a server round-trip.
728
+
729
+ ```kotlin
730
+ if (billing.hasSubscriptionOnHold()) {
731
+ // Let Play walk the user through fixing their payment method.
732
+ billing.showInAppMessages(activity, object : InAppMessageListener {
733
+ override fun onSubscriptionRecovered(purchaseToken: String) {
734
+ refreshPremiumUi() // isPurchased() is already up to date here
735
+ }
736
+ override fun onNoActionNeeded() { }
737
+ })
738
+ }
739
+ ```
740
+
741
+ > **Behavior change:** `isSubscriptionActive()` returns `false` during account hold, matching Google's requirement. If your app deliberately keeps serving these users, check `isSuspended()` explicitly.
742
+
743
+ #### Debugging an Empty Paywall (v4.4.0+)
744
+
745
+ ```kotlin
746
+ billing.setProductDetailsListener(object : ProductDetailsListener {
747
+ override fun onProductDetailsLoaded(
748
+ productType: String,
749
+ loaded: List<ProductDetails>,
750
+ unfetched: List<UnfetchedProduct>,
751
+ ) {
752
+ unfetched.forEach { Log.e("Billing", "${it.productId}: status ${it.statusCode}") }
753
+ }
754
+ override fun onProductDetailsFailed(productType: String, responseCode: Int, debugMessage: String?) { }
755
+ })
756
+ ```
757
+
758
+ Register it **before** `initBilling`. Products land in `unfetched` when the id is misspelled, the product is inactive in Play Console, or the account can't see the release track.
759
+
674
760
  ---
675
761
 
676
762
  ## Documentation
@@ -682,7 +768,32 @@ AppPurchase.getInstance().changeSubscription(
682
768
  - [Interstitial Ads](docs/interstitial-ads.md)
683
769
  - [Rewarded Ads](docs/rewarded-ads.md)
684
770
  - [App Open Ads](docs/app-open-ads.md)
771
+ - [Multi-Provider Waterfall](docs/MULTI_PROVIDER_WATERFALL.md)
772
+ - [Yandex Integration](docs/YANDEX_INTEGRATION.md)
685
773
  - [Billing Integration Guide](docs/APP_PURCHASE_GUIDE.md)
774
+ - [Release Notes v4.4.0](docs/release-notes/RELEASE_NOTES_v4.4.0.md)
775
+ - [Release Notes v4.3.5](docs/release-notes/RELEASE_NOTES_v4.3.5.md)
776
+ - [Release Notes v4.3.4](docs/release-notes/RELEASE_NOTES_v4.3.4.md)
777
+ - [Release Notes v4.3.3](docs/release-notes/RELEASE_NOTES_v4.3.3.md)
778
+ - [Release Notes v4.3.2](docs/release-notes/RELEASE_NOTES_v4.3.2.md)
779
+ - [Release Notes v4.3.1](docs/release-notes/RELEASE_NOTES_v4.3.1.md)
780
+ - [Release Notes v4.3.0](docs/release-notes/RELEASE_NOTES_v4.3.0.md)
781
+ - [Release Notes v4.2.0](docs/release-notes/RELEASE_NOTES_v4.2.0.md)
782
+ - [Release Notes v3.6.0](docs/release-notes/RELEASE_NOTES_v3.6.0.md)
783
+ - [Release Notes v3.5.9](docs/release-notes/RELEASE_NOTES_v3.5.9.md)
784
+ - [Release Notes v3.5.8](docs/release-notes/RELEASE_NOTES_v3.5.8.md)
785
+ - [Release Notes v3.5.7](docs/release-notes/RELEASE_NOTES_v3.5.7.md)
786
+ - [Release Notes v3.4.6](docs/release-notes/RELEASE_NOTES_v3.4.6.md)
787
+ - [Release Notes v3.4.5](docs/release-notes/RELEASE_NOTES_v3.4.5.md)
788
+ - [Release Notes v3.4.4](docs/release-notes/RELEASE_NOTES_v3.4.4.md)
789
+ - [Release Notes v3.4.3](docs/release-notes/RELEASE_NOTES_v3.4.3.md)
790
+ - [Release Notes v3.4.2](docs/release-notes/RELEASE_NOTES_v3.4.2.md)
791
+ - [Release Notes v3.4.1](docs/release-notes/RELEASE_NOTES_v3.4.1.md)
792
+ - [Release Notes v3.4.0](docs/release-notes/RELEASE_NOTES_v3.4.0.md)
793
+ - [Release Notes v3.3.9](docs/release-notes/RELEASE_NOTES_v3.3.9.md)
794
+ - [Release Notes v3.3.8](docs/release-notes/RELEASE_NOTES_v3.3.8.md)
795
+ - [Release Notes v3.3.7](docs/release-notes/RELEASE_NOTES_v3.3.7.md)
796
+ - [Release Notes v3.3.6](docs/release-notes/RELEASE_NOTES_v3.3.6.md)
686
797
  - [Release Notes v3.3.5](docs/release-notes/RELEASE_NOTES_v3.3.5.md)
687
798
  - [Release Notes v3.3.4](docs/release-notes/RELEASE_NOTES_v3.3.4.md)
688
799
  - [Release Notes v3.3.3](docs/release-notes/RELEASE_NOTES_v3.3.3.md)
@@ -693,11 +804,14 @@ AppPurchase.getInstance().changeSubscription(
693
804
  - [API Reference](docs/API_REFERENCE.md)
694
805
 
695
806
  ### Wiki
807
+ - [Multi-Provider Waterfall](wiki/Multi-Provider-Waterfall.md)
808
+ - [Yandex Integration](wiki/Yandex-Integration.md)
696
809
  - [Rewarded Ads](wiki/Rewarded-Ads.md)
697
810
  - [Billing Integration](wiki/Billing-Integration.md)
698
811
  - [Purchase Categories](wiki/Purchase-Categories.md)
699
812
  - [Consumable Products](wiki/Consumable-Products.md)
700
813
  - [Subscriptions](wiki/Subscriptions.md)
814
+ - [Subscription Offers](wiki/Subscription-Offers.md)
701
815
  - [Subscription Upgrades](wiki/Subscription-Upgrades.md)
702
816
 
703
817
  ### API Documentation (Dokka)
@@ -707,15 +821,135 @@ AppPurchase.getInstance().changeSubscription(
707
821
  Generate locally:
708
822
 
709
823
  ```bash
710
- ./gradlew dokkaHtmlMultiModule
824
+ ./gradlew dokkaGenerateHtml
711
825
  ```
712
826
 
713
- Output: `build/dokka/htmlMultiModule/index.html`
827
+ Output: `build/dokka/html/index.html`
828
+
829
+ ### MCP Server (AI Integration)
830
+
831
+ [![npm](https://img.shields.io/npm/v/admanagekit-mcp-server)](https://www.npmjs.com/package/admanagekit-mcp-server)
832
+
833
+ AdManageKit provides an [MCP (Model Context Protocol)](https://modelcontextprotocol.io/) server that integrates with AI tools like Claude Code, Claude Desktop, Cursor, and other MCP-compatible clients. It provides **10 tools** for documentation lookup and code generation.
834
+
835
+ #### Tools
836
+
837
+ | Category | Tools |
838
+ |----------|-------|
839
+ | **Documentation** | `search_docs`, `get_doc_by_topic`, `get_api_reference`, `get_release_notes`, `get_migration_guide`, `list_documentation` |
840
+ | **Code Generation** | `generate_config`, `generate_ad_integration`, `generate_billing_code`, `generate_compose_code` |
841
+
842
+ #### Setup
843
+
844
+ **Claude Desktop / Cursor** (`claude_desktop_config.json`):
845
+
846
+ ```json
847
+ {
848
+ "mcpServers": {
849
+ "admanagekit": {
850
+ "command": "npx",
851
+ "args": ["-y", "admanagekit-mcp-server"]
852
+ }
853
+ }
854
+ }
855
+ ```
856
+
857
+ **Claude Code** (auto-configured via `.mcp.json` when working in this repo):
858
+
859
+ ```json
860
+ {
861
+ "mcpServers": {
862
+ "admanagekit": {
863
+ "command": "node",
864
+ "args": ["mcp-server/dist/index.js"]
865
+ }
866
+ }
867
+ }
868
+ ```
869
+
870
+ #### What It Does
871
+
872
+ - **Search documentation** across all docs, wiki pages, and API references
873
+ - **Look up API references** for any class (AdManager, AppOpenManager, NativeAdManager, etc.)
874
+ - **Generate integration code** for any ad type with display modes, loading strategies, and callbacks
875
+ - **Generate billing code** for purchases, subscriptions, consumables, and expiry verification
876
+ - **Generate Compose code** for all Compose ad components
877
+ - Supports both **Kotlin** and **Java** output
878
+
879
+ See [mcp-server/](mcp-server/) for more details.
714
880
 
715
881
  ---
716
882
 
717
883
  ## Migration Guide
718
884
 
885
+ ### Migrating to 4.2.0
886
+
887
+ The Next-Gen GMA SDK swap (see [Next-Gen GMA SDK](#next-gen-gma-sdk)) is **source-compatible for most consumers**. It only affects you if your code does one of the following:
888
+
889
+ **1. You read specific members of an error/value object, not just call the callback method.**
890
+
891
+ `AdKitError`/`AdKitLoadError` now resolve to the Next-Gen SDK's `LoadAdError`, which drops the old `domain`/`cause` fields and replaces the free-form `Int` error code with a closed `LoadAdError.ErrorCode` enum:
892
+
893
+ ```kotlin
894
+ // Before (4.1.x and earlier)
895
+ override fun onFailedToLoad(error: AdKitError?) {
896
+ Log.e(TAG, "Failed: ${error?.code} / ${error?.domain}") // .domain no longer exists
897
+ }
898
+
899
+ // After (4.2.0)
900
+ override fun onFailedToLoad(error: AdKitError?) {
901
+ Log.e(TAG, "Failed: ${error?.code}") // .code is now a LoadAdError.ErrorCode enum, still fine to log
902
+ }
903
+ ```
904
+
905
+ **2. You use the raw-callback overload of `AdManager.loadInterstitialAd`.**
906
+
907
+ ```kotlin
908
+ // Before
909
+ AdManager.getInstance().loadInterstitialAd(context, adUnitId,
910
+ object : com.google.android.gms.ads.interstitial.InterstitialAdLoadCallback() {
911
+ override fun onAdLoaded(ad: com.google.android.gms.ads.interstitial.InterstitialAd) { }
912
+ override fun onAdFailedToLoad(error: com.google.android.gms.ads.LoadAdError) { }
913
+ })
914
+
915
+ // After
916
+ AdManager.getInstance().loadInterstitialAd(context, adUnitId,
917
+ object : com.google.android.libraries.ads.mobile.sdk.common.AdLoadCallback<
918
+ com.google.android.libraries.ads.mobile.sdk.interstitial.InterstitialAd> {
919
+ override fun onAdLoaded(ad: InterstitialAd) { }
920
+ override fun onAdFailedToLoad(adError: LoadAdError) { }
921
+ })
922
+ ```
923
+
924
+ **3. You supply a custom native ad layout XML.**
925
+
926
+ ```xml
927
+ <!-- Before -->
928
+ <com.google.android.gms.ads.nativead.NativeAdView ...>
929
+ <com.google.android.gms.ads.nativead.MediaView android:id="@+id/ad_media" ... />
930
+ <com.google.android.gms.ads.nativead.AdChoicesView android:id="@+id/ad_choices_view" ... />
931
+ </com.google.android.gms.ads.nativead.NativeAdView>
932
+
933
+ <!-- After -->
934
+ <com.google.android.libraries.ads.mobile.sdk.nativead.NativeAdView ...>
935
+ <com.google.android.libraries.ads.mobile.sdk.nativead.MediaView android:id="@+id/ad_media" ... />
936
+ <com.google.android.libraries.ads.mobile.sdk.common.AdChoicesView android:id="@+id/ad_choices_view" ... />
937
+ </com.google.android.libraries.ads.mobile.sdk.nativead.NativeAdView>
938
+ ```
939
+
940
+ **4. Your Compose callback lambdas' parameter types are explicitly typed.**
941
+
942
+ If you wrote `onAdFailedToLoad: (com.google.android.gms.ads.LoadAdError?) -> Unit = { ... }` explicitly rather than letting Kotlin infer the type from the composable's signature, update the import to `com.google.android.libraries.ads.mobile.sdk.common.LoadAdError`.
943
+
944
+ **Everything else:**
945
+
946
+ - Call `MobileAds.initialize()` once at app startup if you haven't already — see [Next-Gen GMA SDK](#next-gen-gma-sdk). AdManageKit doesn't do this for you.
947
+ - Bump `compileSdk` to **37+**.
948
+ - If you relied on `BannerAdView`/`BannerAdProvider`'s `pause()`/`resume()` actually pausing ad refresh, that's now a no-op — the Next-Gen SDK's `AdView` doesn't expose it.
949
+ - Google Play Billing 9.1.0 needs no changes if you only use `AppPurchase`/`PurchaseItem`.
950
+
951
+ See [Release Notes v4.2.0](docs/release-notes/RELEASE_NOTES_v4.2.0.md) for the full technical writeup.
952
+
719
953
  ### Migrating to 3.0.0
720
954
 
721
955
  Version 3.0.0 is **fully backward compatible**. Optionally adopt new features: