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.
- package/content/README.md +411 -177
- package/content/docs/API_REFERENCE.md +298 -2
- package/content/docs/APP_PURCHASE_GUIDE.md +343 -2
- package/content/docs/BANNER_AD_IMPROVEMENTS.md +49 -0
- package/content/docs/COMPOSE_INTEGRATION.md +24 -0
- package/content/docs/LOADING_STRATEGY_EXAMPLES.md +1 -1
- package/content/docs/MULTI_PROVIDER_WATERFALL.md +238 -0
- package/content/docs/NATIVE_TEMPLATE_VIEW.md +67 -0
- package/content/docs/V4_API_PLAN.md +482 -0
- package/content/docs/YANDEX_INTEGRATION.md +175 -0
- package/content/docs/app-open-ads.md +70 -14
- package/content/docs/release-notes/RELEASE_NOTES_v3.3.6.md +114 -0
- package/content/docs/release-notes/RELEASE_NOTES_v3.3.7.md +82 -0
- package/content/docs/release-notes/RELEASE_NOTES_v3.3.8.md +116 -0
- package/content/docs/release-notes/RELEASE_NOTES_v3.3.9.md +87 -0
- package/content/docs/release-notes/RELEASE_NOTES_v3.4.0.md +124 -0
- package/content/docs/release-notes/RELEASE_NOTES_v3.4.1.md +137 -0
- package/content/docs/release-notes/RELEASE_NOTES_v3.4.2.md +66 -0
- package/content/docs/release-notes/RELEASE_NOTES_v3.4.3.md +80 -0
- package/content/docs/release-notes/RELEASE_NOTES_v3.4.4.md +85 -0
- package/content/docs/release-notes/RELEASE_NOTES_v3.4.5.md +85 -0
- package/content/docs/release-notes/RELEASE_NOTES_v3.4.6.md +129 -0
- package/content/docs/release-notes/RELEASE_NOTES_v3.5.7.md +130 -0
- package/content/docs/release-notes/RELEASE_NOTES_v3.5.8.md +95 -0
- package/content/docs/release-notes/RELEASE_NOTES_v3.5.9.md +55 -0
- package/content/docs/release-notes/RELEASE_NOTES_v3.6.0.md +76 -0
- package/content/docs/release-notes/RELEASE_NOTES_v4.2.0.md +62 -0
- package/content/docs/release-notes/RELEASE_NOTES_v4.3.0.md +137 -0
- package/content/docs/release-notes/RELEASE_NOTES_v4.3.1.md +52 -0
- package/content/docs/release-notes/RELEASE_NOTES_v4.3.2.md +46 -0
- package/content/docs/release-notes/RELEASE_NOTES_v4.3.3.md +73 -0
- package/content/docs/release-notes/RELEASE_NOTES_v4.3.4.md +66 -0
- package/content/docs/release-notes/RELEASE_NOTES_v4.3.5.md +54 -0
- package/content/docs/release-notes/RELEASE_NOTES_v4.4.0.md +202 -0
- package/content/wiki/App-Open-Ads.md +207 -52
- package/content/wiki/Banner-Ads.md +203 -36
- package/content/wiki/Billing-Integration.md +90 -3
- package/content/wiki/Configuration.md +13 -5
- package/content/wiki/Home.md +4 -0
- package/content/wiki/Jetpack-Compose.md +16 -0
- package/content/wiki/Multi-Provider-Waterfall.md +187 -0
- package/content/wiki/Subscription-Offers.md +229 -0
- package/content/wiki/Subscriptions.md +89 -2
- package/content/wiki/Yandex-Integration.md +159 -0
- package/content/wiki/_Sidebar.md +5 -0
- package/dist/tools/documentation.js +40 -16
- package/dist/tools/documentation.js.map +1 -1
- package/dist/types.d.ts +0 -3
- package/dist/types.js +19 -37
- package/dist/types.js.map +1 -1
- package/dist/utils/doc-loader.d.ts +24 -0
- package/dist/utils/doc-loader.js +77 -1
- package/dist/utils/doc-loader.js.map +1 -1
- package/package.json +3 -2
package/content/README.md
CHANGED
|
@@ -1,203 +1,162 @@
|
|
|
1
1
|
# AdManageKit
|
|
2
2
|
[](https://jitpack.io/#i2hammad/AdManageKit)
|
|
3
|
-

|
|
4
4
|

|
|
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 `
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
34
|
+
### What's different under the hood
|
|
54
35
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
44
|
+
### Buy the Offer the User Actually Tapped
|
|
65
45
|
|
|
66
|
-
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
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
|
-
|
|
82
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
//
|
|
105
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
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
|
-
|
|
92
|
+
This stops a paywall promising a trial the user cannot claim and then having Play charge them immediately.
|
|
136
93
|
|
|
137
|
-
###
|
|
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
|
-
|
|
142
|
-
- **
|
|
143
|
-
- **
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
181
|
-
|
|
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
|
-
###
|
|
186
|
-
-
|
|
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
|
-
###
|
|
190
|
-
-
|
|
191
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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:
|
|
242
|
-
implementation 'com.github.i2hammad.AdManageKit:ad-manage-kit-billing:
|
|
243
|
-
implementation 'com.github.i2hammad.AdManageKit:ad-manage-kit-core:
|
|
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:
|
|
247
|
-
```
|
|
248
|
-
|
|
249
|
-
</td>
|
|
250
|
-
<td>
|
|
197
|
+
implementation 'com.github.i2hammad.AdManageKit:ad-manage-kit-compose:v4.4.0'
|
|
251
198
|
|
|
252
|
-
|
|
253
|
-
implementation 'com.github.i2hammad.AdManageKit:ad-manage-kit-
|
|
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
|
-
|
|
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
|
|
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**:
|
|
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
|
|
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
|
-
//
|
|
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()
|
|
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
|
|
824
|
+
./gradlew dokkaGenerateHtml
|
|
711
825
|
```
|
|
712
826
|
|
|
713
|
-
Output: `build/dokka/
|
|
827
|
+
Output: `build/dokka/html/index.html`
|
|
828
|
+
|
|
829
|
+
### MCP Server (AI Integration)
|
|
830
|
+
|
|
831
|
+
[](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:
|