Native Ad Implementation in Mobile Application
Native ad is not "ad that doesn't look like ad." Google and Apple require explicit marking ("Ad", "Ad", "Sponsored"), and violating this leads to app rejection. Native ad is advertisement that fits design visually, but honestly identifies itself. CTR of properly implemented native ad in feed — 2–4 times higher than banner.
Where Implementation Most Often Breaks
Wrong registerView.
AdMob requires registering all clickable elements in NativeAdView. If not calling nativeAdView.mediaView = ... and not passing all view components, clicks won't be tracked:
val nativeAdView = inflater.inflate(R.layout.native_ad_layout, null) as NativeAdView
nativeAdView.mediaView = nativeAdView.findViewById(R.id.ad_media)
nativeAdView.headlineView = nativeAdView.findViewById(R.id.ad_headline)
nativeAdView.bodyView = nativeAdView.findViewById(R.id.ad_body)
nativeAdView.callToActionView = nativeAdView.findViewById(R.id.ad_call_to_action)
nativeAdView.iconView = nativeAdView.findViewById(R.id.ad_icon)
// After assigning all views — bind ad
nativeAdView.setNativeAd(nativeAd)
Forgotten nativeAdView.setNativeAd(nativeAd) at end — ad doesn't show at all. No error in logcat, just empty layout.
Reuse in RecyclerView.
On scroll cells with native ads reuse. If not calling previousNativeAd.destroy() before binding new ad — old NativeAd continues holding resources. On long feeds with native ads every 5–10 positions this shows as ~15–20 MB memory growth per session.
override fun onViewRecycled(holder: NativeAdViewHolder) {
holder.nativeAdView.setNativeAd(null) // unbinds previous NativeAd
// or save reference and call nativeAd.destroy()
}
On iOS similar problem: GADNativeAd must explicitly null in cell's prepareForReuse().
Templates vs Custom Design
AdMob offers GADNativeAdView (iOS) and NativeAdView (Android) as containers with ready-made click tracking logic. Must layout inside these containers — can't just overlay your views and expect clicks to count.
Design template — free within platform guidelines. Restrictions: advertiser logo/icon must be visible, "Ad" marking mandatory, CTA button must be clickable (not just text).
Google forbids native ads visually indistinguishable from editorial content — meaning without marking. Strike for this and app removed from AdMob.
Loading and Caching
Native ads load via AdLoader with forNativeAd(). Can request up to 5 ads at once via withNativeAdOptions(NativeAdOptions.Builder().setRequestMultipleImages(true).build()) — useful for feeds where native inserted at different positions.
Caching ads longer than 1 hour makes no sense — AdMob invalidates them server-side.
val adLoader = AdLoader.Builder(context, AD_UNIT_ID)
.forNativeAd { nativeAd ->
nativeAdsList.add(nativeAd)
if (!adLoader.isLoading && nativeAdsList.size == requestCount) {
insertAdsIntoList()
}
}
.withAdListener(object : AdListener() {
override fun onAdFailedToLoad(error: LoadAdError) {
// log, don't show user
}
})
.build()
adLoader.loadAds(AdRequest.Builder().build(), requestCount)
Timelines for native ad implementation: template in one place — 1–2 days, custom design with feed/RecyclerView integration — 2–3 days. Cost calculated after discussing design and placement points.







