Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/publish.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ jobs:
- name: Setup gradle
uses: gradle/gradle-build-action@v2

- name: Check for Invisible Characters
uses: hakz/hidden-characters@v1.0-beta04
with:
path: "./basic-ads"

- name: Check API
run: ./gradlew apiCheck

Expand Down
3 changes: 2 additions & 1 deletion VERSIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,5 @@ Here's a list of the Basic-Ads dependency versions for each release after 0.2.0:
| 1.1.0-beta02 | 2.3.0 | 1.9.3 | 1.9.1 | 24.9.0 / 12.14.0 | 4.0.0 / 3.1.0 |
| 1.1.0-beta03 | 2.3.0 | 1.9.3 | 1.9.1 | 24.9.0 / 12.14.0 | 4.0.0 / 3.1.0 |
| 1.1.0 | 2.3.0 | 1.9.3 | 1.9.1 | 24.9.0 / 12.14.0 | 4.0.0 / 3.1.0 |
| 1.1.1 | 2.3.0 | 1.10.0 | 1.9.1 | 24.9.0 / 12.14.0 | 4.0.0 / 3.1.0 |
| 1.1.1 | 2.3.0 | 1.10.0 | 1.9.1 | 24.9.0 / 12.14.0 | 4.0.0 / 3.1.0 |
| 1.2.0-beta01 | 2.3.21 | 1.10.3 | 1.9.1 | 25.2.0 / 13.3.0 | 4.0.0 / 3.1.0 |
3 changes: 3 additions & 0 deletions basic-ads/api/basic-ads.api
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,9 @@ public final class app/lexilabs/basic/ads/RewardedInterstitialAdHandler {
public static final field $stable I
public fun <init> (Ljava/lang/Object;)V
public final fun getState ()Lapp/lexilabs/basic/ads/AdState;
public final fun load (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;)V
public final fun load (Ljava/lang/String;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;)V
public static synthetic fun load$default (Lapp/lexilabs/basic/ads/RewardedInterstitialAdHandler;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
public static synthetic fun load$default (Lapp/lexilabs/basic/ads/RewardedInterstitialAdHandler;Ljava/lang/String;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
public final fun setListeners (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;)V
public static synthetic fun setListeners$default (Lapp/lexilabs/basic/ads/RewardedInterstitialAdHandler;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)V
Expand Down Expand Up @@ -347,6 +349,7 @@ public final class app/lexilabs/basic/ads/composable/RememberRewardedAdKt {

public final class app/lexilabs/basic/ads/composable/RememberRewardedInterstitialAdKt {
public static final fun rememberRewardedInterstitialAd (Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)Landroidx/compose/runtime/MutableState;
public static final fun rememberRewardedInterstitialAd (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)Landroidx/compose/runtime/MutableState;
public static final fun rememberRewardedInterstitialAd (Ljava/lang/String;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)Landroidx/compose/runtime/MutableState;
}

Expand Down
78 changes: 38 additions & 40 deletions basic-ads/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import com.android.build.api.dsl.androidLibrary
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget

Expand All @@ -13,43 +12,43 @@ plugins {
}

/** REMEDIATION **/
dependencies {
/** This patches all transitive dependencies that have vulnerabilities **/
constraints {
androidMainImplementation(libs.remediate.okhttp) {
version { strictly(libs.versions.remediate.okhttp.get()) }
because("CVE-2021-0341")
}
androidMainImplementation(libs.remediate.bitbucket) {
version { strictly(libs.versions.remediate.bitbucket.get())}
because("CVE-2024-29371")
}
androidMainImplementation(libs.remediate.netty.codec.http){
version { strictly(libs.versions.remediate.netty.get())}
because("CVE-2025-67735")
}
androidMainImplementation(libs.remediate.netty.codec.http2){
version { strictly(libs.versions.remediate.netty.get())}
because("CVE-2025-55163")
}
androidMainImplementation(libs.remediate.google.protobuf.kotlin){
version { strictly(libs.versions.remediate.google.protobuf.get())}
because("CVE-2024-7254")
}
androidMainImplementation(libs.remediate.google.protobuf.java){
version { strictly(libs.versions.remediate.google.protobuf.get())}
because("CVE-2024-7254")
}
androidMainImplementation(libs.remediate.jdom){
version { strictly(libs.versions.remediate.jdom.get())}
because("CVE-2021-33813")
}
androidMainImplementation(libs.remediate.apache.compress){
version { strictly(libs.versions.remediate.apache.compress.get())}
because("CVE-2024-26308")
}
}
}
//dependencies {
// /** This patches all transitive dependencies that have vulnerabilities **/
// constraints {
// androidMainImplementation(libs.remediate.okhttp) {
// version { strictly(libs.versions.remediate.okhttp.get()) }
// because("CVE-2021-0341")
// }
// androidMainImplementation(libs.remediate.bitbucket) {
// version { strictly(libs.versions.remediate.bitbucket.get())}
// because("CVE-2024-29371")
// }
// androidMainImplementation(libs.remediate.netty.codec.http){
// version { strictly(libs.versions.remediate.netty.get())}
// because("CVE-2025-67735")
// }
// androidMainImplementation(libs.remediate.netty.codec.http2){
// version { strictly(libs.versions.remediate.netty.get())}
// because("CVE-2025-55163")
// }
// androidMainImplementation(libs.remediate.google.protobuf.kotlin){
// version { strictly(libs.versions.remediate.google.protobuf.get())}
// because("CVE-2024-7254")
// }
// androidMainImplementation(libs.remediate.google.protobuf.java){
// version { strictly(libs.versions.remediate.google.protobuf.get())}
// because("CVE-2024-7254")
// }
// androidMainImplementation(libs.remediate.jdom){
// version { strictly(libs.versions.remediate.jdom.get())}
// because("CVE-2021-33813")
// }
// androidMainImplementation(libs.remediate.apache.compress){
// version { strictly(libs.versions.remediate.apache.compress.get())}
// because("CVE-2024-26308")
// }
// }
//}

kotlin {

Expand Down Expand Up @@ -112,8 +111,7 @@ kotlin {
freeCompilerArgs.add("-Xexpect-actual-classes")
}

@Suppress("UnstableApiUsage")
androidLibrary{
android {
namespace = "app.lexilabs.basic.ads"
compileSdk = libs.versions.build.sdk.compile.get().toInt()
minSdk = libs.versions.build.sdk.min.get().toInt()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ public actual class InterstitialAdHandler actual constructor(
Log.d(tag, "setListeners: Loading")
require(interstitialAd != null) {
_state.value = AdState.FAILING
"InterstitialAd not loaded yet. `InterstitialAd.load()` must be called first"
"The provided InterstitialAdHandler is not loaded yet. You must call .load() on your handler instance (e.g., interstitialAdHandler.load()) before displaying the InterstitialAd composable."
}
interstitialAd?.let {
interstitialAd?.fullScreenContentCallback = FullscreenContentDelegate(
Expand Down Expand Up @@ -133,7 +133,7 @@ public actual class InterstitialAdHandler actual constructor(
}
require(interstitialAd != null) {
_state.value = AdState.FAILING
"InterstitialAd not loaded yet. `InterstitialAd.load()` must be called first"
"The provided InterstitialAdHandler is not loaded yet. You must call .load() on your handler instance (e.g., interstitialAdHandler.load()) before displaying the InterstitialAd composable."
}
interstitialAd?.show(activity)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ public actual class RewardedAdHandler actual constructor(private val activity: A
Log.d(tag, "setListeners: Loading")
require(rewardedAd != null) {
_state.value = AdState.FAILING
"RewardedAd not loaded yet. `RewardedAd.load()` must be called first"
"The provided RewardedAdHandler is not loaded yet. You must call .load() on your handler instance (e.g., rewardedAdHandler.load()) before displaying the RewardedAd composable."
}
rewardedAd?.let {
rewardedAd?.fullScreenContentCallback = FullscreenContentDelegate(
Expand Down Expand Up @@ -187,7 +187,7 @@ public actual class RewardedAdHandler actual constructor(private val activity: A
}
require(rewardedAd != null) {
_state.value = AdState.FAILING
"RewardedAd not loaded yet. `RewardedAd.load()` must be called first"
"The provided RewardedAdHandler is not loaded yet. You must call .load() on your handler instance (e.g., rewardedAdHandler.load()) before displaying the RewardedAd composable."
}
rewardedAd?.show(activity) { reward ->
Log.d(tag, "A reward was earned")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import androidx.compose.runtime.mutableStateOf
import app.lexilabs.basic.logging.Log
import com.google.android.gms.ads.AdRequest
import com.google.android.gms.ads.LoadAdError
import com.google.android.gms.ads.rewarded.ServerSideVerificationOptions
import com.google.android.gms.ads.rewardedinterstitial.RewardedInterstitialAdLoadCallback
import com.google.android.gms.ads.rewardedinterstitial.RewardedInterstitialAd as AndroidRewardedInterstitialAd

Expand Down Expand Up @@ -70,6 +71,61 @@ public actual class RewardedInterstitialAdHandler actual constructor(
)
}

/**
* Loads a rewarded interstitial ad.
*
* @param adUnitId The ad unit ID.
* @param userId Used for Server-Side Verification
* @param customData Used for Server-Side Verification
* @param onLoad A callback invoked when the ad is loaded.
* @param onFailure A callback invoked when the ad fails to load.
*/
public actual fun load(
adUnitId: String,
userId: String,
customData: String,
onLoad: () -> Unit,
onFailure: (Exception) -> Unit
) {
_state.value = AdState.LOADING
Log.d(tag, "loadRewardedAd: Loading")
require(activity != null) {
_state.value = AdState.FAILING
"Activity Context must be set to non-null value in Android"
}
require(activity is Activity) {
_state.value = AdState.FAILING
"activity variable must be of the Android `Activity` type"
}
AndroidRewardedInterstitialAd.load(
activity,
adUnitId,
AdRequest.Builder().build(),
object : RewardedInterstitialAdLoadCallback() {
override fun onAdFailedToLoad(adError: LoadAdError) {
super.onAdFailedToLoad(adError)
Log.d(tag, "loadRewardedAd:failure:$adError")
_state.value = AdState.FAILING
onFailure(AdException(adError.message))
}

override fun onAdLoaded(ad: AndroidRewardedInterstitialAd) {
super.onAdLoaded(ad)
Log.d(tag, "loadRewardedAd:success")
rewardedInterstitialAd = ad
val options =
ServerSideVerificationOptions.Builder().apply {
setUserId(userId)
setCustomData(customData)
}.build()
rewardedInterstitialAd?.setServerSideVerificationOptions(options)
_state.value = AdState.READY
onLoad()
}
}
)
}

/**
* Sets the listeners for the rewarded interstitial ad.
*
Expand All @@ -89,7 +145,7 @@ public actual class RewardedInterstitialAdHandler actual constructor(
Log.d(tag, "setListeners: Loading")
require(rewardedInterstitialAd != null) {
_state.value = AdState.FAILING
"RewardedAd not loaded yet. `RewardedAd.load()` must be called first"
"The provided RewardedInterstitialAdHandler is not loaded yet. You must call .load() on your handler instance (e.g., rewardedInterstitialAdHandler.load()) before displaying the RewardedInterstitialAd composable."
}
rewardedInterstitialAd?.let {
rewardedInterstitialAd?.fullScreenContentCallback = FullscreenContentDelegate(
Expand Down Expand Up @@ -129,7 +185,7 @@ public actual class RewardedInterstitialAdHandler actual constructor(
}
require(rewardedInterstitialAd != null) {
_state.value = AdState.FAILING
"RewardedAd not loaded yet. `RewardedAd.load()` must be called first"
"The provided RewardedInterstitialAdHandler is not loaded yet. You must call .load() on your handler instance (e.g., rewardedInterstitialAdHandler.load()) before displaying the RewardedInterstitialAd composable."
}
rewardedInterstitialAd?.show(activity) {
Log.d(tag, "A reward was earned")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import androidx.compose.ui.platform.LocalContext
import app.lexilabs.basic.ads.AdState
import app.lexilabs.basic.ads.AdUnitId
import app.lexilabs.basic.ads.DependsOnGoogleMobileAds
import app.lexilabs.basic.ads.RewardedAdHandler
import app.lexilabs.basic.ads.RewardedInterstitialAdHandler
import app.lexilabs.basic.ads.getActivity

Expand Down Expand Up @@ -51,6 +52,46 @@ public actual fun rememberRewardedInterstitialAd(
return ad
}

/**
* Remembers a [RewardedAdHandler], which is used to load and show rewarded ads.
*
* This function will automatically attempt to load an ad when the [RewardedAdHandler.state]
* is [AdState.NONE] or [AdState.DISMISSED].
*
* @param userId Used for Server-Side Verification
* @param customData Used for Server-Side Verification
* @param adUnitId The ad unit ID to use for loading the ad. Defaults to [AdUnitId.REWARDED_DEFAULT].
* @param onLoad A callback that will be invoked when the ad has successfully loaded.
* @param onFailure A callback that will be invoked if the ad fails to load, providing an [Exception] with details of the failure.
* @return A [MutableState] holding the [RewardedAdHandler]. You can observe this state to react to changes in the ad's lifecycle.
*/
@DependsOnGoogleMobileAds
@Composable
public actual fun rememberRewardedInterstitialAd(
userId: String,
customData: String,
adUnitId: String,
onLoad: () -> Unit,
onFailure: (Exception) -> Unit
): MutableState<RewardedAdHandler> {
val activity = LocalContext.current.getActivity()
val ad = remember(activity) { mutableStateOf(RewardedAdHandler(activity)) }
when(ad.value.state){
AdState.DISMISSED,
AdState.NONE -> {
ad.value.load(
adUnitId = adUnitId,
userId = userId,
customData = customData,
onLoad = onLoad,
onFailure = onFailure
)
}
else -> { /** DO NOTHING **/ }
}
return ad
}

/**
* A composable function that remembers and manages a RewardedInterstitialAdHandler.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ public actual class NativeAdHandler actual constructor(private val activity: Any
@MainThread
public actual fun render(): NativeAdData {
require(ad?.android != null){
"NativeAd has not loaded"
"The provided NativeAdHandler is not loaded yet. You must call .load() on your handler instance (e.g., nativeAdHandler.load()) before displaying the NativeAd composable."
}
return ad!!
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,10 @@ public expect class RewardedAdHandler(activity: Any?) {
public val state: AdState

/**
* Loads an Rewarded Ad.
* Loads a Rewarded Ad.
* Note: Make all calls to the Mobile Ads SDK on the main thread.
*
* To load an Rewarded ad, call [RewardedAdHandler.load] method
* To load a Rewarded ad, call [RewardedAdHandler.load] method
* and pass in an [AdUnitId] as a [String] to receive the loaded ad, the [onLoad]
* callback, and any possible [Exception] from the [onFailure] callback.
* @param adUnitId Your Rewarded Ad AdUnitId [String] from AdMob
Expand All @@ -66,10 +66,10 @@ public expect class RewardedAdHandler(activity: Any?) {
)

/**
* Loads an Rewarded Ad.
* Loads a Rewarded Ad.
* Note: Make all calls to the Mobile Ads SDK on the main thread.
*
* To load an Rewarded ad, call [RewardedAdHandler.load] method
* To load a Rewarded ad, call [RewardedAdHandler.load] method
* and pass in an [AdUnitId] as a [String] to receive the loaded ad, the [onLoad]
* callback, and any possible [Exception] from the [onFailure] callback.
* @param adUnitId Your Rewarded Ad AdUnitId [String] from AdMob
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,10 @@ public expect class RewardedInterstitialAdHandler(activity: Any?) {
public val state: AdState

/**
* Loads an RewardedInterstitial Ad.
* Loads a RewardedInterstitial Ad.
* Note: Make all calls to the Mobile Ads SDK on the main thread.
*
* To load an RewardedInterstitial ad, call [RewardedInterstitialAdHandler.load] method
* To load a RewardedInterstitial ad, call [RewardedInterstitialAdHandler.load] method
* and pass in an [AdUnitId] as a [String] to receive the loaded ad, the [onLoad]
* callback, and any possible [Exception] from the [onFailure] callback.
* @param adUnitId Your RewardedInterstitial Ad AdUnitId [String] from AdMob
Expand All @@ -64,6 +64,29 @@ public expect class RewardedInterstitialAdHandler(activity: Any?) {
onFailure: (Exception) -> Unit
)

/**
* Loads a RewardedInterstitial Ad.
* Note: Make all calls to the Mobile Ads SDK on the main thread.
*
* To load a Rewarded ad, call [RewardedAdHandler.load] method
* and pass in an [AdUnitId] as a [String] to receive the loaded ad, the [onLoad]
* callback, and any possible [Exception] from the [onFailure] callback.
* @param adUnitId Your Rewarded Ad AdUnitId [String] from AdMob
* @param userId Used for Server-Side Verification
* @param customData Used for Server-Side Verification
* @param onLoad Callback after the ad loads
* @param onFailure Callback sharing the [Exception] when the ad fail to load
* @see [AdUnitId.autoSelect]
* @see [AdUnitId.REWARDED_INTERSTITIAL_DEFAULT]
*/
public fun load(
adUnitId: String = AdUnitId.REWARDED_INTERSTITIAL_DEFAULT,
userId: String,
customData: String,
onLoad: () -> Unit,
onFailure: (Exception) -> Unit
)

/**
* Sets the FullScreenContentCallback for the RewardedInterstitial Ad.
*
Expand Down
Loading
Loading