Skip to content

feat: support for App Open Ad en Android e iOS#386

Open
BETOXL wants to merge 29 commits intocapacitor-community:mainfrom
BETOXL:feat/app-open-ad
Open

feat: support for App Open Ad en Android e iOS#386
BETOXL wants to merge 29 commits intocapacitor-community:mainfrom
BETOXL:feat/app-open-ad

Conversation

@BETOXL
Copy link

@BETOXL BETOXL commented Oct 11, 2025

  • Adds native and TypeScript support for the Open Ad App format on Android and iOS.
  • Includes usage examples in the Angular demo.
  • Documentation and exposed events.

@distante distante requested a review from Copilot October 11, 2025 09:03
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds support for App Open Ad format on both Android and iOS platforms, integrating it into the AdMob plugin with complete TypeScript definitions and usage examples.

  • Adds App Open Ad functionality with load, show, and status check methods
  • Implements native iOS and Android code using Google Mobile Ads SDK
  • Includes TypeScript definitions, events, and Angular demo integration

Reviewed Changes

Copilot reviewed 15 out of 16 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/web.ts Adds stub methods for App Open Ad functionality in web implementation
src/index.ts Exports new App Open Ad module
src/definitions.ts Integrates App Open Ad plugin interface into main plugin definitions
src/app-open/* Complete TypeScript definitions for App Open Ad functionality including options, events, and plugin interface
ios/Sources/AdMobPlugin/* Native iOS implementation using GADAppOpenAd with manager pattern
android/src/main/java/* Native Android implementation using Google Mobile Ads AppOpenAd API
demo/angular/src/app/app.component.ts Angular demo showing App Open Ad usage with event listeners
README.md Documentation for App Open Ad API methods and usage examples

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Copy link
Contributor

@distante distante left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello @BETOXL ! thanks for your PR, please check the comments and the CI. You probably need to run the formatter. (I will try to add it to the pre commit lint in the future)

Also can you please update the tests to include your changes?

Thank you!

README.md Outdated
} from '@capacitor-community/admob';

export async function showAppOpenAd(): Promise<void> {
// Escuchar eventos
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comments in English.

@BETOXL BETOXL requested a review from distante October 13, 2025 21:53
@distante
Copy link
Contributor

a lot of comments are not correctly resolved.

Copy link
Contributor

@distante distante left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please do not resolve comments without fixing them.

BETOXL added 11 commits October 14, 2025 12:09
no single line ifs please
this should be inside the AdMob class.
comments in English.
no single line ifs please
Replace any type with proper AppOpenAdOptions type for better type safety and API consistency.
Copy link
Contributor

@distante distante left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CI is still failing.

You should check how the other implementation is done so you can fix and add your tests.

Moved AppOpenAd Plugin initialization inside the AdMob class.
Copy link
Author

@BETOXL BETOXL left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

delete comment

@BETOXL BETOXL changed the title feat: soporte para App Open Ad en Android e iOS feat: support for App Open Ad en Android e iOS Oct 25, 2025
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 17 changed files in this pull request and generated 17 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@Facevedo74
Copy link

Good morning, what became of this feat?

Copy link
Author

@BETOXL BETOXL left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changes

@BETOXL BETOXL marked this pull request as draft February 25, 2026 14:04
@BETOXL BETOXL marked this pull request as ready for review February 25, 2026 14:05
@distante distante requested a review from Copilot March 6, 2026 07:02
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 16 changed files in this pull request and generated 14 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +73 to +86
private final AppOpenAdPlugin appOpenAdPlugin = new AppOpenAdPlugin();

@PluginMethod
public void loadAppOpen(final PluginCall call) {
appOpenAdPlugin.loadAppOpen(call);
}

@PluginMethod
public void showAppOpen(final PluginCall call) {
appOpenAdPlugin.showAppOpen(call);
}

@PluginMethod
public void isAppOpenLoaded(final PluginCall call) {
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AppOpenAdPlugin extends Capacitor Plugin, but AdMob instantiates it directly (new AppOpenAdPlugin()) and forwards calls. Plugin instances need to be created/attached by Capacitor to have a valid bridge/context/activity; this setup risks getContext()/getActivity() being null and listeners not working. Refactor App Open into an executor/helper (like other ad executors) that uses AdMob's getContext/getActivity/notifyListeners, or implement the logic directly in AdMob.

Suggested change
private final AppOpenAdPlugin appOpenAdPlugin = new AppOpenAdPlugin();
@PluginMethod
public void loadAppOpen(final PluginCall call) {
appOpenAdPlugin.loadAppOpen(call);
}
@PluginMethod
public void showAppOpen(final PluginCall call) {
appOpenAdPlugin.showAppOpen(call);
}
@PluginMethod
public void isAppOpenLoaded(final PluginCall call) {
private AppOpenAdPlugin getAppOpenAdPlugin() {
if (getBridge() == null) {
return null;
}
try {
Plugin plugin = getBridge().getPlugin("AppOpenAdPlugin");
if (plugin instanceof AppOpenAdPlugin) {
return (AppOpenAdPlugin) plugin;
}
} catch (Exception ignored) {
// If the plugin cannot be obtained, return null and let callers handle it.
}
return null;
}
@PluginMethod
public void loadAppOpen(final PluginCall call) {
AppOpenAdPlugin appOpenAdPlugin = getAppOpenAdPlugin();
if (appOpenAdPlugin == null) {
call.reject("AppOpenAdPlugin is not available");
return;
}
appOpenAdPlugin.loadAppOpen(call);
}
@PluginMethod
public void showAppOpen(final PluginCall call) {
AppOpenAdPlugin appOpenAdPlugin = getAppOpenAdPlugin();
if (appOpenAdPlugin == null) {
call.reject("AppOpenAdPlugin is not available");
return;
}
appOpenAdPlugin.showAppOpen(call);
}
@PluginMethod
public void isAppOpenLoaded(final PluginCall call) {
AppOpenAdPlugin appOpenAdPlugin = getAppOpenAdPlugin();
if (appOpenAdPlugin == null) {
call.reject("AppOpenAdPlugin is not available");
return;
}

Copilot uses AI. Check for mistakes.
Comment on lines +27 to +33
appOpenAdManager.loadAd(context, () -> {
notifyListeners("appOpenAdLoaded", new JSONObject());
call.resolve();
}, () -> {
notifyListeners("appOpenAdFailedToLoad", new JSONObject());
call.reject("Failed to load App Open Ad");
});
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

notifyListeners expects a JSObject, but this code passes org.json.JSONObject, which is inconsistent with the rest of the plugin and may not compile. Use com.getcapacitor.JSObject (or pass null/an empty JSObject) for event payloads.

Copilot uses AI. Check for mistakes.
Comment on lines +36 to +46
@PluginMethod
public void showAppOpen(PluginCall call) {
Activity activity = getActivity();
appOpenAdManager.showAdIfAvailable(activity, () -> {
notifyListeners("appOpenAdClosed", new JSONObject());
call.resolve();
}, () -> {
notifyListeners("appOpenAdFailedToShow", new JSONObject());
call.reject("Failed to show App Open Ad");
});
}
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

showAppOpen assumes appOpenAdManager and activity are non-null; if loadAppOpen was never called or the plugin isn't attached, this can crash or hang. Add null checks and call.reject(...) when there's no loaded manager/ad or no current activity.

Copilot uses AI. Check for mistakes.
Comment on lines +22 to +25
if (isLoadingAd || appOpenAd != null) {
return;
}

Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

loadAd returns early when already loading or already loaded without invoking callbacks. Since the plugin call resolves/rejects only inside those callbacks, the JS promise can remain pending forever. Return a status and resolve immediately (already loaded/loading) or invoke onLoaded when appOpenAd != null.

Suggested change
if (isLoadingAd || appOpenAd != null) {
return;
}
if (appOpenAd != null) {
if (onLoaded != null) {
onLoaded.run();
}
return;
}
if (isLoadingAd) {
if (onFailed != null) {
onFailed.run();
}
return;
}

Copilot uses AI. Check for mistakes.
Comment on lines +65 to +86
isShowingAd = true;
appOpenAd.setFullScreenContentCallback(new FullScreenContentCallback() {
@Override
public void onAdDismissedFullScreenContent() {
appOpenAd = null;
isShowingAd = false;

if (onClosed != null) {
onClosed.run();
}
}

@Override
public void onAdFailedToShowFullScreenContent(com.google.android.gms.ads.AdError adError) {
appOpenAd = null;
isShowingAd = false;

if (onFailedToShow != null) {
onFailedToShow.run();
}
}
});
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AppOpenAdPluginEvents.Opened is defined/used, but Android never notifies an "opened" event. Consider triggering it from FullScreenContentCallback.onAdShowedFullScreenContent() so JS listeners behave consistently with the documented API.

Copilot uses AI. Check for mistakes.
Comment on lines +16 to +19
if isLoadingAd || appOpenAd != nil {
return
}

Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

loadAd returns early when an ad is already loaded or loading, but it doesn't invoke onLoaded/onFailed. Because the plugin call waits on these callbacks, this can leave the JS promise pending forever. Return a status (already loaded/loading) and resolve/reject accordingly, or invoke onLoaded when appOpenAd != nil.

Suggested change
if isLoadingAd || appOpenAd != nil {
return
}
// If an ad is already loaded, report success immediately.
if appOpenAd != nil {
onLoaded()
return
}
// If an ad is currently loading, report failure to avoid leaving callers hanging.
if isLoadingAd {
onFailed()
return
}

Copilot uses AI. Check for mistakes.
Comment on lines +39 to +43
isShowingAd = true
ad.fullScreenContentDelegate = self
ad.present(fromRootViewController: rootViewController)
self.onClosed = onClosed
self.onFailedToShow = onFailedToShow
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In showAdIfAvailable, onClosed/onFailedToShow are stored after calling present. Delegate callbacks can fire immediately, so the closures may still be nil and the JS call may never resolve/reject. Assign the closures before presenting the ad.

Suggested change
isShowingAd = true
ad.fullScreenContentDelegate = self
ad.present(fromRootViewController: rootViewController)
self.onClosed = onClosed
self.onFailedToShow = onFailedToShow
self.onClosed = onClosed
self.onFailedToShow = onFailedToShow
isShowingAd = true
ad.fullScreenContentDelegate = self
ad.present(fromRootViewController: rootViewController)

Copilot uses AI. Check for mistakes.
Comment on lines +217 to +223
console.log('Failed to load App Open Ad');
});

const options: AppOpenAdOptions = {
adUnitId: 'TU_AD_UNIT_ID',
showOnColdStart: true, // Opcional
showOnForeground: true, // Opcional
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The FailedToShow listener logs "Failed to load App Open Ad" and the comments use "Opcional"/TU_AD_UNIT_ID, which is inconsistent with the surrounding English docs. Consider updating to "Failed to show...", "Optional", and an English placeholder like YOUR_AD_UNIT_ID for clarity.

Suggested change
console.log('Failed to load App Open Ad');
});
const options: AppOpenAdOptions = {
adUnitId: 'TU_AD_UNIT_ID',
showOnColdStart: true, // Opcional
showOnForeground: true, // Opcional
console.log('Failed to show App Open Ad');
});
const options: AppOpenAdOptions = {
adUnitId: 'YOUR_AD_UNIT_ID',
showOnColdStart: true, // Optional
showOnForeground: true, // Optional

Copilot uses AI. Check for mistakes.
Comment on lines +3 to +4
showOnColdStart?: boolean;
showOnForeground?: boolean;
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

showOnColdStart / showOnForeground are defined in the public options but are not used anywhere in the repo (native or TS). This makes the API misleading; either implement the behavior (hook into app lifecycle) or remove these fields until supported.

Suggested change
showOnColdStart?: boolean;
showOnForeground?: boolean;

Copilot uses AI. Check for mistakes.
Comment on lines +32 to +41
@objc func showAppOpen(_ call: CAPPluginCall) {
DispatchQueue.main.async {
if let rootVC = UIApplication.shared.keyWindow?.rootViewController {
self.appOpenAdManager?.showAdIfAvailable(rootViewController: rootVC, onClosed: {
self.notifyListeners("appOpenAdClosed", data: [:])
call.resolve()
}, onFailedToShow: {
self.notifyListeners("appOpenAdFailedToShow", data: [:])
call.reject("Failed to show App Open Ad")
})
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

showAppOpen never emits the appOpenAdOpened event even though it’s exposed in AppOpenAdPluginEvents and used in the demo/docs. Consider emitting an "Opened" event when presentation begins (e.g., via GADFullScreenContentDelegate.adDidPresentFullScreenContent or a callback from AppOpenAdManager).

Copilot uses AI. Check for mistakes.
Copy link
Contributor

@distante distante left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please check the copilot comments.

I will try to check this this week(end) so we can get this merged

* @see <a href="https://developers.google.com/admob/android/targeting">Target Settings</a>
*/
// ---------------------------------------------------------
// REQUEST CONFIGURATION
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please keep the documentation link for future developers

@distante
Copy link
Contributor

distante commented Mar 6, 2026

Also check the CI errors.


Note: Closes #167

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants