Skip to content

Commit

Permalink
Merge pull request #163 from teogor/fix/ads-expired-reload-issue
Browse files Browse the repository at this point in the history
Ad Caching and Loading Enhancements
  • Loading branch information
teogor authored Oct 27, 2023
2 parents 21575f4 + b0cabcb commit dadf756
Show file tree
Hide file tree
Showing 10 changed files with 194 additions and 84 deletions.
64 changes: 41 additions & 23 deletions monetisation/admob/api/admob.api
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ public abstract class dev/teogor/ceres/monetisation/admob/formats/Ad : dev/teogo
public abstract fun loadContinuously ()Z
public fun log (Ljava/lang/String;)V
public final fun onListener (Ldev/teogor/ceres/monetisation/admob/formats/AdEvent;)V
public fun reloadExpiredAd ()V
protected final fun setLoading (Z)V
protected final fun setShowing (Z)V
public fun show ()V
Expand All @@ -89,8 +90,8 @@ public abstract class dev/teogor/ceres/monetisation/admob/formats/Ad : dev/teogo
public final class dev/teogor/ceres/monetisation/admob/formats/AdCache {
public static final field $stable I
public static final field INSTANCE Ldev/teogor/ceres/monetisation/admob/formats/AdCache;
public final fun cacheAd (Ljava/lang/String;Ldev/teogor/ceres/monetisation/admob/formats/CacheAdModel;)V
public final fun getAd (Ljava/lang/String;)Ldev/teogor/ceres/monetisation/admob/formats/CacheAdModel;
public final fun cacheAd (Ljava/lang/String;Ldev/teogor/ceres/monetisation/admob/formats/CachedAd;)V
public final fun getAd (Ljava/lang/String;)Ldev/teogor/ceres/monetisation/admob/formats/CachedAd;
public final fun getAdCount (Ljava/lang/String;)I
public final fun getAds (Ljava/lang/String;I)Ljava/util/List;
public final fun removeAd (Ljava/lang/String;)V
Expand Down Expand Up @@ -213,6 +214,11 @@ public final class dev/teogor/ceres/monetisation/admob/formats/AdType : java/lan
public static fun values ()[Ldev/teogor/ceres/monetisation/admob/formats/AdType;
}

public final class dev/teogor/ceres/monetisation/admob/formats/AdTypeKt {
public static final fun toFriendlyName (Ldev/teogor/ceres/monetisation/admob/formats/AdType;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
public static synthetic fun toFriendlyName$default (Ldev/teogor/ceres/monetisation/admob/formats/AdType;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Ljava/lang/String;
}

public abstract class dev/teogor/ceres/monetisation/admob/formats/AppOpenAd : dev/teogor/ceres/monetisation/admob/formats/Ad {
public static final field $stable I
public fun <init> ()V
Expand All @@ -234,66 +240,78 @@ public abstract class dev/teogor/ceres/monetisation/admob/formats/BannerAd : dev
public fun useCache ()Z
}

public abstract class dev/teogor/ceres/monetisation/admob/formats/CacheAdModel {
public abstract class dev/teogor/ceres/monetisation/admob/formats/CachedAd {
public static final field $stable I
public synthetic fun <init> (JLkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun getLoadTime ()J
}

public final class dev/teogor/ceres/monetisation/admob/formats/CacheAdModel$AppOpen : dev/teogor/ceres/monetisation/admob/formats/CacheAdModel {
public final class dev/teogor/ceres/monetisation/admob/formats/CachedAd$AppOpen : dev/teogor/ceres/monetisation/admob/formats/CachedAd {
public static final field $stable I
public fun <init> (Lcom/google/android/gms/ads/appopen/AppOpenAd;)V
public fun <init> (Lcom/google/android/gms/ads/appopen/AppOpenAd;J)V
public final fun component1 ()Lcom/google/android/gms/ads/appopen/AppOpenAd;
public final fun copy (Lcom/google/android/gms/ads/appopen/AppOpenAd;)Ldev/teogor/ceres/monetisation/admob/formats/CacheAdModel$AppOpen;
public static synthetic fun copy$default (Ldev/teogor/ceres/monetisation/admob/formats/CacheAdModel$AppOpen;Lcom/google/android/gms/ads/appopen/AppOpenAd;ILjava/lang/Object;)Ldev/teogor/ceres/monetisation/admob/formats/CacheAdModel$AppOpen;
public final fun component2 ()J
public final fun copy (Lcom/google/android/gms/ads/appopen/AppOpenAd;J)Ldev/teogor/ceres/monetisation/admob/formats/CachedAd$AppOpen;
public static synthetic fun copy$default (Ldev/teogor/ceres/monetisation/admob/formats/CachedAd$AppOpen;Lcom/google/android/gms/ads/appopen/AppOpenAd;JILjava/lang/Object;)Ldev/teogor/ceres/monetisation/admob/formats/CachedAd$AppOpen;
public fun equals (Ljava/lang/Object;)Z
public final fun getAd ()Lcom/google/android/gms/ads/appopen/AppOpenAd;
public fun getLoadTime ()J
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public final class dev/teogor/ceres/monetisation/admob/formats/CacheAdModel$Interstitial : dev/teogor/ceres/monetisation/admob/formats/CacheAdModel {
public final class dev/teogor/ceres/monetisation/admob/formats/CachedAd$Interstitial : dev/teogor/ceres/monetisation/admob/formats/CachedAd {
public static final field $stable I
public fun <init> (Lcom/google/android/gms/ads/interstitial/InterstitialAd;)V
public fun <init> (Lcom/google/android/gms/ads/interstitial/InterstitialAd;J)V
public final fun component1 ()Lcom/google/android/gms/ads/interstitial/InterstitialAd;
public final fun copy (Lcom/google/android/gms/ads/interstitial/InterstitialAd;)Ldev/teogor/ceres/monetisation/admob/formats/CacheAdModel$Interstitial;
public static synthetic fun copy$default (Ldev/teogor/ceres/monetisation/admob/formats/CacheAdModel$Interstitial;Lcom/google/android/gms/ads/interstitial/InterstitialAd;ILjava/lang/Object;)Ldev/teogor/ceres/monetisation/admob/formats/CacheAdModel$Interstitial;
public final fun component2 ()J
public final fun copy (Lcom/google/android/gms/ads/interstitial/InterstitialAd;J)Ldev/teogor/ceres/monetisation/admob/formats/CachedAd$Interstitial;
public static synthetic fun copy$default (Ldev/teogor/ceres/monetisation/admob/formats/CachedAd$Interstitial;Lcom/google/android/gms/ads/interstitial/InterstitialAd;JILjava/lang/Object;)Ldev/teogor/ceres/monetisation/admob/formats/CachedAd$Interstitial;
public fun equals (Ljava/lang/Object;)Z
public final fun getAd ()Lcom/google/android/gms/ads/interstitial/InterstitialAd;
public fun getLoadTime ()J
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public final class dev/teogor/ceres/monetisation/admob/formats/CacheAdModel$Native : dev/teogor/ceres/monetisation/admob/formats/CacheAdModel {
public final class dev/teogor/ceres/monetisation/admob/formats/CachedAd$Native : dev/teogor/ceres/monetisation/admob/formats/CachedAd {
public static final field $stable I
public fun <init> (Lcom/google/android/gms/ads/nativead/NativeAd;)V
public fun <init> (Lcom/google/android/gms/ads/nativead/NativeAd;J)V
public final fun component1 ()Lcom/google/android/gms/ads/nativead/NativeAd;
public final fun copy (Lcom/google/android/gms/ads/nativead/NativeAd;)Ldev/teogor/ceres/monetisation/admob/formats/CacheAdModel$Native;
public static synthetic fun copy$default (Ldev/teogor/ceres/monetisation/admob/formats/CacheAdModel$Native;Lcom/google/android/gms/ads/nativead/NativeAd;ILjava/lang/Object;)Ldev/teogor/ceres/monetisation/admob/formats/CacheAdModel$Native;
public final fun component2 ()J
public final fun copy (Lcom/google/android/gms/ads/nativead/NativeAd;J)Ldev/teogor/ceres/monetisation/admob/formats/CachedAd$Native;
public static synthetic fun copy$default (Ldev/teogor/ceres/monetisation/admob/formats/CachedAd$Native;Lcom/google/android/gms/ads/nativead/NativeAd;JILjava/lang/Object;)Ldev/teogor/ceres/monetisation/admob/formats/CachedAd$Native;
public fun equals (Ljava/lang/Object;)Z
public final fun getAd ()Lcom/google/android/gms/ads/nativead/NativeAd;
public fun getLoadTime ()J
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public final class dev/teogor/ceres/monetisation/admob/formats/CacheAdModel$Rewarded : dev/teogor/ceres/monetisation/admob/formats/CacheAdModel {
public final class dev/teogor/ceres/monetisation/admob/formats/CachedAd$Rewarded : dev/teogor/ceres/monetisation/admob/formats/CachedAd {
public static final field $stable I
public fun <init> (Lcom/google/android/gms/ads/rewarded/RewardedAd;)V
public fun <init> (Lcom/google/android/gms/ads/rewarded/RewardedAd;J)V
public final fun component1 ()Lcom/google/android/gms/ads/rewarded/RewardedAd;
public final fun copy (Lcom/google/android/gms/ads/rewarded/RewardedAd;)Ldev/teogor/ceres/monetisation/admob/formats/CacheAdModel$Rewarded;
public static synthetic fun copy$default (Ldev/teogor/ceres/monetisation/admob/formats/CacheAdModel$Rewarded;Lcom/google/android/gms/ads/rewarded/RewardedAd;ILjava/lang/Object;)Ldev/teogor/ceres/monetisation/admob/formats/CacheAdModel$Rewarded;
public final fun component2 ()J
public final fun copy (Lcom/google/android/gms/ads/rewarded/RewardedAd;J)Ldev/teogor/ceres/monetisation/admob/formats/CachedAd$Rewarded;
public static synthetic fun copy$default (Ldev/teogor/ceres/monetisation/admob/formats/CachedAd$Rewarded;Lcom/google/android/gms/ads/rewarded/RewardedAd;JILjava/lang/Object;)Ldev/teogor/ceres/monetisation/admob/formats/CachedAd$Rewarded;
public fun equals (Ljava/lang/Object;)Z
public final fun getAd ()Lcom/google/android/gms/ads/rewarded/RewardedAd;
public fun getLoadTime ()J
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public final class dev/teogor/ceres/monetisation/admob/formats/CacheAdModel$RewardedInterstitial : dev/teogor/ceres/monetisation/admob/formats/CacheAdModel {
public final class dev/teogor/ceres/monetisation/admob/formats/CachedAd$RewardedInterstitial : dev/teogor/ceres/monetisation/admob/formats/CachedAd {
public static final field $stable I
public fun <init> (Lcom/google/android/gms/ads/rewardedinterstitial/RewardedInterstitialAd;)V
public fun <init> (Lcom/google/android/gms/ads/rewardedinterstitial/RewardedInterstitialAd;J)V
public final fun component1 ()Lcom/google/android/gms/ads/rewardedinterstitial/RewardedInterstitialAd;
public final fun copy (Lcom/google/android/gms/ads/rewardedinterstitial/RewardedInterstitialAd;)Ldev/teogor/ceres/monetisation/admob/formats/CacheAdModel$RewardedInterstitial;
public static synthetic fun copy$default (Ldev/teogor/ceres/monetisation/admob/formats/CacheAdModel$RewardedInterstitial;Lcom/google/android/gms/ads/rewardedinterstitial/RewardedInterstitialAd;ILjava/lang/Object;)Ldev/teogor/ceres/monetisation/admob/formats/CacheAdModel$RewardedInterstitial;
public final fun component2 ()J
public final fun copy (Lcom/google/android/gms/ads/rewardedinterstitial/RewardedInterstitialAd;J)Ldev/teogor/ceres/monetisation/admob/formats/CachedAd$RewardedInterstitial;
public static synthetic fun copy$default (Ldev/teogor/ceres/monetisation/admob/formats/CachedAd$RewardedInterstitial;Lcom/google/android/gms/ads/rewardedinterstitial/RewardedInterstitialAd;JILjava/lang/Object;)Ldev/teogor/ceres/monetisation/admob/formats/CachedAd$RewardedInterstitial;
public fun equals (Ljava/lang/Object;)Z
public final fun getAd ()Lcom/google/android/gms/ads/rewardedinterstitial/RewardedInterstitialAd;
public fun getLoadTime ()J
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,14 @@ abstract class Ad(

open fun show() = Unit

open fun reloadExpiredAd() {
val adTypeName = type().toFriendlyName(suffix = " Ad")
log("Loading a new $adTypeName to replace the expired ad.")
AdCache.removeAd(id)

load()
}

fun onListener(event: AdEvent) {
log("onListener::$event")
when (event) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,39 +17,39 @@
package dev.teogor.ceres.monetisation.admob.formats

object AdCache {
private val adCache: MutableMap<String, MutableList<CacheAdModel>> = mutableMapOf()
private val cachedAds: MutableMap<String, MutableList<CachedAd>> = mutableMapOf()

@Synchronized
fun cacheAd(adId: String, ad: CacheAdModel) {
adCache.getOrPut(adId) {
fun cacheAd(adId: String, ad: CachedAd) {
cachedAds.getOrPut(adId) {
mutableListOf()
}.apply {
add(ad)
}
}

@Synchronized
fun getAd(adId: String): CacheAdModel? {
return adCache[adId]?.firstOrNull()
fun getAd(adId: String): CachedAd? {
return cachedAds[adId]?.firstOrNull()
}

@Synchronized
fun getAds(adId: String, count: Int): List<CacheAdModel> {
return adCache[adId]?.take(count) ?: emptyList()
fun getAds(adId: String, count: Int): List<CachedAd> {
return cachedAds[adId]?.take(count) ?: emptyList()
}

@Synchronized
fun removeAd(adId: String) {
adCache[adId]?.let { adList ->
cachedAds[adId]?.let { adList ->
if (adList.isNotEmpty()) {
adList.removeAt(0)
}
if (adList.isEmpty()) {
adCache.remove(adId)
cachedAds.remove(adId)
}
}
}

@Synchronized
fun getAdCount(adId: String) = adCache[adId]?.size ?: 0
fun getAdCount(adId: String) = cachedAds[adId]?.size ?: 0
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,29 @@ enum class AdType {

fun type(): AdType = this
}

/**
* Converts an AdType to a user-friendly name with an optional prefix and suffix.
*
* @param prefix The prefix to add to the friendly name.
* @param suffix The suffix to add to the friendly name.
* @return The user-friendly name of the AdType.
*/
fun AdType.toFriendlyName(
prefix: String = "",
suffix: String = "",
): String {
return this.name
.split(Regex("(?=[A-Z])"))
.joinToString(" ") {
it.replaceFirstChar { char ->
if (char.isLowerCase()) {
char.titlecase()
} else {
char.toString()
}
}
}
.trim()
.let { "$prefix$it$suffix" }
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,14 @@ import com.google.android.gms.ads.LoadAdError
import com.google.android.gms.ads.appopen.AppOpenAd
import dev.teogor.ceres.monetisation.admob.CurrentActivityHolder
import java.util.Date
import java.util.concurrent.TimeUnit

abstract class AppOpenAd : Ad() {

final override fun type() = AdType.AppOpen

override fun useCache() = false

private var loadTime: Long = 0

override fun load(): Boolean {
if (!super.load()) {
return false
Expand All @@ -54,10 +53,9 @@ abstract class AppOpenAd : Ad() {
log("onAdLoaded.")
AdCache.cacheAd(
adId = id,
ad = CacheAdModel.AppOpen(ad),
ad = CachedAd.AppOpen(ad, Date().time),
)
onListener(AdEvent.AdLoaded)
loadTime = Date().time
}

/**
Expand Down Expand Up @@ -94,15 +92,13 @@ abstract class AppOpenAd : Ad() {
return
}

if (!wasLoadTimeLessThanNHoursAgo(4)) {
log("Loading the app open ad because the previously loaded ad has expired.")
load()
val appOpenAd = (ad as CachedAd.AppOpen).ad

if (!wasLoadTimeLessThanNHoursAgo(ad.loadTime, 4)) {
reloadExpiredAd()
return
} else {
log("App open ad is still valid; no need to reload.")
}

val appOpenAd = (ad as CacheAdModel.AppOpen).ad
if (isShowing) {
log("The app open ad is already showing.")
return
Expand Down Expand Up @@ -152,9 +148,12 @@ abstract class AppOpenAd : Ad() {
CurrentActivityHolder.activity?.let { appOpenAd.show(it) }
}

private fun wasLoadTimeLessThanNHoursAgo(numHours: Long): Boolean {
val dateDifference: Long = Date().time - loadTime
val numMilliSecondsPerHour: Long = 3600000
return dateDifference < numMilliSecondsPerHour * numHours
private fun wasLoadTimeLessThanNHoursAgo(
adLoadTime: Long,
hoursThreshold: Long,
): Boolean {
val dateDifference: Long = Date().time - adLoadTime
val numMillisecondsPerHour: Long = TimeUnit.HOURS.toMillis(1)
return dateDifference < numMillisecondsPerHour * hoursThreshold
}
}

This file was deleted.

Loading

0 comments on commit dadf756

Please sign in to comment.