Skip to content

Commit

Permalink
Added support for app open ads (#44)
Browse files Browse the repository at this point in the history
* First version

* Added iOS support

* Updated documentation

* Added new script apis

* Final fixes

* Update admob_ios.mm

* Update admob_android.cpp

* Update ext.manifest

* Show app open on first load on iOS
  • Loading branch information
britzl committed Jul 2, 2024
1 parent 6bf5fe2 commit d11bf56
Show file tree
Hide file tree
Showing 13 changed files with 759 additions and 168 deletions.
41 changes: 37 additions & 4 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ The extension can be configured by adding the following fields to `game.project`
[admob]
app_id_ios = ca-app-pub-3940256099942544~1458002511
app_id_android = ca-app-pub-3940256099942544~3347511713
app_open_android = ca-app-pub-3940256099942544/9257395921
app_open_ios = ca-app-pub-3940256099942544/5575463023
ios_tracking_usage_description = Your data will be used to provide you a better and personalized ad experience.
```

Expand All @@ -37,6 +39,12 @@ This is your iOS AdMob app ID. An app ID is a unique ID number assigned to your
### app_id_android
This is your Android AdMob app ID. An app ID is a unique ID number assigned to your apps when they're added to AdMob. The app ID is used to identify your apps.

### app_open_android
Ad unit to use for App Open ads on Android. If this value is set App Open Ads will be shown when the app is brought to the foreground.

### app_open_ios
Ad unit to use for App Open ads on iOS. If this value is set App Open Ads will be shown when the app is brought to the foreground.

### ios_tracking_usage_description

Before requesting the unique IDFA string for a device on iOS 14 the application must request user authorization to access app-related data for tracking the user or the device. This is done automatically when `admob.request_idfa()` is called. The string set in `admob.ios_tracking_usage_description` will be shown to the user.
Expand Down Expand Up @@ -120,16 +128,26 @@ local function admob_callback(self, message_id, message)
end
```

### Ad formats

The extension supports the following ad formats:

* App Open Ads - App open ads are a special ad format intended for publishers wishing to monetize their app load screens. App open ads can be closed by your users at any time. App open ads can be shown when users bring your app to the foreground.
* Banner Ads - Banner ads are rectangular ads that occupy a portion of an app's layout. They stay on screen while users are interacting with the app.
* Interstitial Ads - Interstitial ads are full-screen ads that cover the interface of an app until closed by the user.
* Rewarded Ads - Rewarded ads are ads that users have the option of interacting with in exchange for in-app rewards.
* Rewarded Interstitial Ads - Rewarded interstitial is a type of incentivized ad format that allows you offer rewards for ads that appear automatically during natural app transitions. Unlike rewarded ads, users aren't required to opt-in to view a rewarded interstitial.


### Loading ads

Before an ad unit can be displayed it has to be loaded:

```lua
admob.load_banner(ad_unit, size)
admob.load_interstitial(ad_unit)
admob.load_rewarded(ad_unit)
admob.load_rewarded_interstitial(ad_unit)
admob.load_banner(ad_unit, size)
```

The callback function will be invoked when the ad unit is ready:
Expand All @@ -150,6 +168,8 @@ local function admob_callback(self, message_id, message)
-- same as above
elseif message_id == admob.MSG_BANNER then
-- same as above
elseif message_id == admob.MSG_APPOPEN then
-- same as above
end
```

Expand All @@ -162,15 +182,14 @@ admob.is_rewarded_loaded()
admob.is_rewarded_interstitial_loaded()
```


### Showing ads
Once an ad unit has been loaded it is ready to be shown:

```lua
admob.show_banner(position)
admob.show_interstitial()
admob.show_rewarded()
admob.show_rewarded_interstitial()
admob.show_banner(position)
```

The callback function will be invoked when the ad unit is shown:
Expand All @@ -197,10 +216,24 @@ local function admob_callback(self, message_id, message)
-- same as above
elseif message_id == admob.MSG_BANNER then
-- same as above
elseif message_id == admob.MSG_APPOPEN then
-- same as above
end
```

Banner ads can be hidden and destroyed:
### App Open Ads

App Open Ads will automatically be loaded and shown if an ad unit id is provided in the `game.project` configuration (see above). If you wish to manually load and show App Open Ads you can leave the `game.project` configuration blank and instead use the following functions:

```lua
admob.load_appopen(ad_unit)
admob.show_appopen()
admob.is_appopen_loaded()
```

### Banner ads

In addition to loading and showing Banner Ads they can also be hidden and destroyed:

```lua
admob.hide_banner()
Expand Down
51 changes: 51 additions & 0 deletions extension-admob/api/admob.script_api
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,54 @@
[Android](https://developers.google.com/admob/android/ad-inspector),
[iOS](https://developers.google.com/admob/ios/ad-inspector)

#*****************************************************************************************************

- name: load_appopen
type: function
desc: Starts loading an AppOpen Ad, can only be called after `admob.MSG_INITIALIZATION` event
Original docs
[Android](https://developers.google.com/admob/android/app-open),
[iOS](https://developers.google.com/admob/ios/app-open)

parameters:
- name: ad_unit_id
type: string
desc: Ad unit ID, for test ads use on Android `"ca-app-pub-3940256099942544/9257395921"`, or
on iOS `"ca-app-pub-3940256099942544/5575463023"`
Original docs
[Android](https://developers.google.com/admob/android/app-open),
[iOS](https://developers.google.com/admob/ios/app-open)

#*****************************************************************************************************

- name: show_appopen
type: function
desc: Shows loaded AppOpen Ad, can only be called after `admob.EVENT_LOADED`
Original docs
[Android](https://developers.google.com/admob/android/app-open),
[iOS](https://developers.google.com/admob/ios/app-open)

examples:
- desc: |-
```lua
if admob and admob.is_appopen_loaded() then
admob.show_appopen()
end
```

#*****************************************************************************************************

- name: is_appopen_loaded
type: function
desc: Checks if AppOpen Ad is loaded and ready to show
Original docs
[Android](https://developers.google.com/admob/android/app-open),
[iOS](https://developers.google.com/admob/ios/app-open)

returns:
- name: is_ready
type: boolean

#*****************************************************************************************************

- name: load_interstitial
Expand Down Expand Up @@ -479,6 +527,9 @@
- name: MSG_REWARDED_INTERSTITIAL
type: number

- name: MSG_APPOPEN
type: number

#*****************************************************************************************************

- name: EVENT_CLOSED
Expand Down
3 changes: 0 additions & 3 deletions extension-admob/ext.manifest
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
name: "AdMobExt"

platforms:
android:
context:
aaptExtraPackages: ["androidx.work", "com.google.android.gms.common", "androidx.startup"]

ios:
context:
Expand Down
3 changes: 3 additions & 0 deletions extension-admob/manifests/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,7 @@ repositories {
dependencies {
// https://developers.google.com/admob/android/quick-start#example_app-level_buildgradle_excerpt
implementation 'com.google.android.gms:play-services-ads:22.6.0'
implementation "androidx.lifecycle:lifecycle-common:2.3.1"
implementation "androidx.lifecycle:lifecycle-process:2.3.1"
annotationProcessor "androidx.lifecycle:lifecycle-compiler:2.3.1"
}
36 changes: 34 additions & 2 deletions extension-admob/src/admob.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ static const char DEFOLD_USERAGENT[] = "defold-3.4.1";
static int Lua_Initialize(lua_State* L)
{
DM_LUA_STACK_CHECK(L, 0);
Initialize(DEFOLD_USERAGENT);
Initialize();
return 0;
}

Expand All @@ -29,6 +29,24 @@ static int Lua_SetCallback(lua_State* L)
return 0;
}

static int Lua_LoadAppOpen(lua_State* L)
{
DM_LUA_STACK_CHECK(L, 0);
if (lua_type(L, 1) != LUA_TSTRING) {
return DM_LUA_ERROR("Expected string, got %s. Wrong type for App Open Ad UnitId variable '%s'.", luaL_typename(L, 1), lua_tostring(L, 1));
}
const char* unitId_lua = luaL_checkstring(L, 1);
LoadAppOpen(unitId_lua, false);
return 0;
}

static int Lua_ShowAppOpen(lua_State* L)
{
DM_LUA_STACK_CHECK(L, 0);
ShowAppOpen();
return 0;
}

static int Lua_LoadInterstitial(lua_State* L)
{
DM_LUA_STACK_CHECK(L, 0);
Expand Down Expand Up @@ -155,6 +173,14 @@ static int Lua_IsBannerLoaded(lua_State* L)
return 1;
}

static int Lua_IsAppOpenLoaded(lua_State* L)
{
DM_LUA_STACK_CHECK(L, 1);
bool is_loaded = IsAppOpenLoaded();
lua_pushboolean(L, is_loaded);
return 1;
}

static int Lua_SetPrivacySettings(lua_State* L)
{
DM_LUA_STACK_CHECK(L, 0);
Expand Down Expand Up @@ -189,6 +215,8 @@ static const luaL_reg Module_methods[] =
{
{"initialize", Lua_Initialize},
{"set_callback", Lua_SetCallback},
{"load_appopen", Lua_LoadAppOpen},
{"show_appopen", Lua_ShowAppOpen},
{"load_interstitial", Lua_LoadInterstitial},
{"show_interstitial", Lua_ShowInterstitial},
{"load_rewarded", Lua_LoadRewarded},
Expand All @@ -203,6 +231,7 @@ static const luaL_reg Module_methods[] =
{"is_interstitial_loaded", Lua_IsInterstitialLoaded},
{"is_rewarded_interstitial_loaded", Lua_IsRewardedInterstitialLoaded},
{"is_banner_loaded", Lua_IsBannerLoaded},
{"is_appopen_loaded", Lua_IsAppOpenLoaded},
{"set_privacy_settings", Lua_SetPrivacySettings},
{"request_idfa", Lua_RequestIDFA},
{"show_ad_inspector", Lua_ShowAdInspector},
Expand All @@ -225,6 +254,7 @@ static void LuaInit(lua_State* L)
SETCONSTANT(MSG_INITIALIZATION)
SETCONSTANT(MSG_IDFA)
SETCONSTANT(MSG_REWARDED_INTERSTITIAL)
SETCONSTANT(MSG_APPOPEN)

SETCONSTANT(EVENT_CLOSED)
SETCONSTANT(EVENT_FAILED_TO_SHOW)
Expand Down Expand Up @@ -280,13 +310,15 @@ static void LuaInit(lua_State* L)

static dmExtension::Result AppInitializeAdmob(dmExtension::AppParams* params)
{
dmLogInfo("AppInitializeAdmob");
return dmExtension::RESULT_OK;
}

static dmExtension::Result InitializeAdmob(dmExtension::Params* params)
{
dmLogInfo("InitializeAdmob");
LuaInit(params->m_L);
Initialize_Ext();
Initialize_Ext(params, DEFOLD_USERAGENT);
InitializeCallback();
return dmExtension::RESULT_OK;
}
Expand Down
49 changes: 42 additions & 7 deletions extension-admob/src/admob_android.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ struct Admob
jobject m_AdmobJNI;

jmethodID m_Initialize;
jmethodID m_LoadAppOpen;
jmethodID m_ShowAppOpen;
jmethodID m_LoadInterstitial;
jmethodID m_ShowInterstitial;
jmethodID m_LoadRewarded;
Expand All @@ -29,6 +31,7 @@ struct Admob
jmethodID m_DestroyBanner;
jmethodID m_ShowBanner;
jmethodID m_HideBanner;
jmethodID m_IsAppOpenLoaded;
jmethodID m_IsRewardedLoaded;
jmethodID m_IsInterstitialLoaded;
jmethodID m_IsRewardedInterstitialLoaded;
Expand Down Expand Up @@ -80,6 +83,16 @@ static void CallVoidMethodCharInt(jobject instance, jmethodID method, const char
env->DeleteLocalRef(jstr);
}

static void CallVoidMethodCharBoolean(jobject instance, jmethodID method, const char* cstr, bool cbool)
{
dmAndroid::ThreadAttacher threadAttacher;
JNIEnv* env = threadAttacher.GetEnv();

jstring jstr = env->NewStringUTF(cstr);
env->CallVoidMethod(instance, method, jstr, cbool);
env->DeleteLocalRef(jstr);
}

static void CallVoidMethodInt(jobject instance, jmethodID method, int cint)
{
dmAndroid::ThreadAttacher threadAttacher;
Expand All @@ -98,7 +111,9 @@ static void CallVoidMethodBool(jobject instance, jmethodID method, bool cbool)

static void InitJNIMethods(JNIEnv* env, jclass cls)
{
g_admob.m_Initialize = env->GetMethodID(cls, "initialize", "(Ljava/lang/String;)V");
g_admob.m_Initialize = env->GetMethodID(cls, "initialize", "()V");
g_admob.m_LoadAppOpen = env->GetMethodID(cls, "loadAppOpen", "(Ljava/lang/String;)V");
g_admob.m_ShowAppOpen = env->GetMethodID(cls, "showAppOpen", "()V");
g_admob.m_LoadInterstitial = env->GetMethodID(cls, "loadInterstitial", "(Ljava/lang/String;)V");
g_admob.m_ShowInterstitial = env->GetMethodID(cls, "showInterstitial", "()V");
g_admob.m_LoadRewarded = env->GetMethodID(cls, "loadRewarded", "(Ljava/lang/String;)V");
Expand All @@ -114,33 +129,53 @@ static void InitJNIMethods(JNIEnv* env, jclass cls)
g_admob.m_ShowAdInspector = env->GetMethodID(cls, "showAdInspector", "()V");
g_admob.m_UpdateBannerLayout= env->GetMethodID(cls, "updateBannerLayout", "()V");

g_admob.m_IsAppOpenLoaded = env->GetMethodID(cls, "isAppOpenLoaded", "()Z");
g_admob.m_IsRewardedLoaded = env->GetMethodID(cls, "isRewardedLoaded", "()Z");
g_admob.m_IsInterstitialLoaded = env->GetMethodID(cls, "isInterstitialLoaded", "()Z");
g_admob.m_IsRewardedInterstitialLoaded = env->GetMethodID(cls, "isRewardedInterstitialLoaded", "()Z");
g_admob.m_IsBannerLoaded = env->GetMethodID(cls, "isBannerLoaded", "()Z");
g_admob.m_SetMaxAdContentRating = env->GetMethodID(cls, "setMaxAdContentRating", "(I)V");
}

void Initialize_Ext()
void Initialize_Ext(dmExtension::Params* params, const char* defoldUserAgent)
{
dmAndroid::ThreadAttacher threadAttacher;
JNIEnv* env = threadAttacher.GetEnv();
jclass cls = dmAndroid::LoadClass(env, "com.defold.admob.AdmobJNI");

InitJNIMethods(env, cls);

jmethodID jni_constructor = env->GetMethodID(cls, "<init>", "(Landroid/app/Activity;)V");

g_admob.m_AdmobJNI = env->NewGlobalRef(env->NewObject(cls, jni_constructor, threadAttacher.GetActivity()->clazz));
const char* appOpenAdUnitId = dmConfigFile::GetString(params->m_ConfigFile, "admob.app_open_android", 0);
jstring jappOpenAdUnitId = env->NewStringUTF(appOpenAdUnitId);
jstring jdefoldUserAgent = env->NewStringUTF(defoldUserAgent);
jmethodID jni_constructor = env->GetMethodID(cls, "<init>", "(Landroid/app/Activity;Ljava/lang/String;Ljava/lang/String;)V");
g_admob.m_AdmobJNI = env->NewGlobalRef(env->NewObject(cls, jni_constructor, threadAttacher.GetActivity()->clazz, jappOpenAdUnitId, jdefoldUserAgent));
env->DeleteLocalRef(jappOpenAdUnitId);
env->DeleteLocalRef(jdefoldUserAgent);
}

void Finalize_Ext()
{
}

void Initialize(const char* defoldUserAgent)
void Initialize()
{
CallVoidMethod(g_admob.m_AdmobJNI, g_admob.m_Initialize);
}

void LoadAppOpen(const char* unitId, bool showImmediately)
{
CallVoidMethodCharBoolean(g_admob.m_AdmobJNI, g_admob.m_LoadAppOpen, unitId, showImmediately);
}

void ShowAppOpen()
{
CallVoidMethod(g_admob.m_AdmobJNI, g_admob.m_ShowAppOpen);
}

bool IsAppOpenLoaded()
{
CallVoidMethodChar(g_admob.m_AdmobJNI, g_admob.m_Initialize, defoldUserAgent);
return CallBoolMethod(g_admob.m_AdmobJNI, g_admob.m_IsAppOpenLoaded);
}

void LoadInterstitial(const char* unitId)
Expand Down
3 changes: 2 additions & 1 deletion extension-admob/src/admob_callback_private.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ enum MessageId
MSG_BANNER = 3,
MSG_INITIALIZATION = 4,
MSG_IDFA = 5,
MSG_REWARDED_INTERSTITIAL = 6
MSG_REWARDED_INTERSTITIAL = 6,
MSG_APPOPEN = 7
};

enum MessageEvent
Expand Down
Loading

0 comments on commit d11bf56

Please sign in to comment.