i want guide me from scratch step by step how to m...
Creado el: 26 de abril de 2026
Respondido usando GPT-5.5 Thinking por Chat01
Creado el: 26 de abril de 2026
Respondido usando GPT-5.5 Thinking por Chat01
i want guide me from scratch step by step how to make a mobile app using qt6 to test admob, and tell me how to show real ads and earn money and also show me all ads types and try it on app.
note: i want each step has details what.
i will use qt6 creator and want build app in qwidgets not qml, so i want where to show banner for example
ui->label, added from qtcreator and tell me what do to
#include "widget.h"
#include "./ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
ui->label->setText("here banner");
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_pushButton_clicked()
{
// here to show reward video
}
also tell me all available ads and how to show them in qwidgets, and if can tell me prices for each ad for 1000 users
Below is a from-scratch Qt 6 Widgets + Android + AdMob guide. It uses your ui->label as the banner placeholder and your pushButton to show a rewarded video.
Important: in a Qt Widgets Android app, AdMob ads are Android native views. A banner cannot truly live “inside” a QLabel. The correct approach is: put a QLabel in Qt Designer where you want the banner, then place an Android AdView overlay at that label’s screen position.
Google’s current Android Mobile Ads SDK guide says to use SDK dependency com.google.android.gms:play-services-ads:25.2.0, minimum SDK 23+, compile SDK 35+, add the AdMob App ID to AndroidManifest.xml, and initialize the SDK once before loading ads. (Google for Developers) Google also says to use test ads while developing; using real ads for testing can lead to account suspension. (Google for Developers)
You will make a Qt Widgets Android app with:
| Ad format | How it appears in Qt Widgets app |
|---|---|
| Banner | Android native banner overlay positioned over ui->label |
| Interstitial | Full-screen ad from a button or natural transition |
| Rewarded | Full-screen rewarded video from your on_pushButton_clicked() |
| Rewarded interstitial | Full-screen reward ad shown at natural transition, after an intro/skip screen |
| App open | Full-screen app-open ad when app starts or returns to foreground |
| Native | Android native layout overlay positioned over a Qt placeholder label |
AdMob’s main SDK formats are banner, interstitial, native, rewarded, rewarded interstitial, and app open. (Google for Developers)
In Qt Creator:
File > New ProjectQLabel named labelQPushButton named pushButtonpushButtonInterstitialpushButtonRewardedInterstitialpushButtonAppOpenpushButtonNativeFor the banner label:
cppui->label->setMinimumHeight(90); ui->label->setText("Banner placeholder"); ui->label->setStyleSheet("background:#dddddd;");
The banner will cover that label on Android.
In Qt Creator:
This creates an android/ folder in your project.
Qt’s Android deployment docs say custom Android files live under the package source directory, Java/Kotlin code goes under <QT_ANDROID_PACKAGE_SOURCE_DIR>/src/, resources under res/, and the CMake target should set QT_ANDROID_PACKAGE_SOURCE_DIR. (Qt Documentation)
In CMakeLists.txt, add this after your executable target is created:
cmakeif(ANDROID) set_property(TARGET YourTargetName PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android" ) endif()
Replace YourTargetName with your real target name.
Example:
cmakeqt_add_executable(QtAdMobTest main.cpp widget.cpp widget.h widget.ui ) target_link_libraries(QtAdMobTest PRIVATE Qt6::Widgets) if(ANDROID) set_property(TARGET QtAdMobTest PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android" ) endif()
android/build.gradleOpen:
textandroid/build.gradle
Find the dependencies block and add:
gradledependencies { implementation 'com.google.android.gms:play-services-ads:25.2.0' }
Also make sure repositories include:
gradlerepositories { google() mavenCentral() }
Google’s quick-start guide currently lists play-services-ads:25.2.0. (Google for Developers)
AndroidManifest.xmlOpen:
textandroid/AndroidManifest.xml
Inside <application>, add the test AdMob App ID first:
xml<meta-data android:name="com.google.android.gms.ads.APPLICATION_ID" android:value="ca-app-pub-3940256099942544~3347511713"/>
Also make sure your app has hardware acceleration enabled:
xml<application android:hardwareAccelerated="true" ...>
Google notes that video ads in banner/native views require hardware acceleration. (Google for Developers)
You can also add these permissions near the top:
xml<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
Your manifest should contain something like:
xml<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <application android:hardwareAccelerated="true" android:label="QtAdMobTest"> <meta-data android:name="com.google.android.gms.ads.APPLICATION_ID" android:value="ca-app-pub-3940256099942544~3347511713"/> </application> </manifest>
Later, for real ads, replace the test App ID with your real AdMob App ID.
Create this file:
textandroid/src/com/example/qtadmob/AdMobBridge.java
Use this code:
javapackage com.example.qtadmob; import android.app.Activity; import android.graphics.Color; import android.os.Handler; import android.os.Looper; import android.util.Log; import android.view.Gravity; import android.view.ViewGroup; import android.widget.Button; import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; import com.google.android.gms.ads.AdError; import com.google.android.gms.ads.AdListener; import com.google.android.gms.ads.AdLoader; import com.google.android.gms.ads.AdRequest; import com.google.android.gms.ads.AdSize; import com.google.android.gms.ads.AdView; import com.google.android.gms.ads.FullScreenContentCallback; import com.google.android.gms.ads.LoadAdError; import com.google.android.gms.ads.MobileAds; import com.google.android.gms.ads.appopen.AppOpenAd; import com.google.android.gms.ads.interstitial.InterstitialAd; import com.google.android.gms.ads.interstitial.InterstitialAdLoadCallback; import com.google.android.gms.ads.nativead.MediaView; import com.google.android.gms.ads.nativead.NativeAd; import com.google.android.gms.ads.nativead.NativeAdView; import com.google.android.gms.ads.rewarded.RewardedAd; import com.google.android.gms.ads.rewarded.RewardItem; import com.google.android.gms.ads.rewarded.RewardedAdLoadCallback; import com.google.android.gms.ads.rewardedinterstitial.RewardedInterstitialAd; import com.google.android.gms.ads.rewardedinterstitial.RewardedInterstitialAdLoadCallback; public class AdMobBridge { private static final String TAG = "QtAdMobBridge"; // Google official Android demo ad unit IDs. private static final String TEST_BANNER = "ca-app-pub-3940256099942544/9214589741"; private static final String TEST_INTERSTITIAL = "ca-app-pub-3940256099942544/1033173712"; private static final String TEST_REWARDED = "ca-app-pub-3940256099942544/5224354917"; private static final String TEST_REWARDED_INTERSTITIAL = "ca-app-pub-3940256099942544/5354046379"; private static final String TEST_APP_OPEN = "ca-app-pub-3940256099942544/9257395921"; private static final String TEST_NATIVE = "ca-app-pub-3940256099942544/2247696110"; private static final Handler mainHandler = new Handler(Looper.getMainLooper()); private static AdView bannerView; private static InterstitialAd interstitialAd; private static RewardedAd rewardedAd; private static RewardedInterstitialAd rewardedInterstitialAd; private static AppOpenAd appOpenAd; private static NativeAd nativeAd; private static FrameLayout nativeContainer; public static void initialize(Activity activity) { mainHandler.post(() -> { MobileAds.initialize(activity, initializationStatus -> { Log.d(TAG, "Mobile Ads SDK initialized"); loadInterstitial(activity); loadRewarded(activity); loadRewardedInterstitial(activity); loadAppOpen(activity); }); }); } public static void showBanner(Activity activity, int x, int y, int width, int height) { mainHandler.post(() -> { FrameLayout root = activity.findViewById(android.R.id.content); if (root == null) { Log.e(TAG, "Root view not found"); return; } if (bannerView != null) { root.removeView(bannerView); bannerView.destroy(); bannerView = null; } bannerView = new AdView(activity); bannerView.setAdUnitId(TEST_BANNER); float density = activity.getResources().getDisplayMetrics().density; int adWidthDp = Math.max(320, Math.round(width / density)); AdSize adSize = AdSize.getCurrentOrientationAnchoredAdaptiveBannerAdSize( activity, adWidthDp ); bannerView.setAdSize(adSize); int adHeightPx = adSize.getHeightInPixels(activity); FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(width, adHeightPx); params.gravity = Gravity.TOP | Gravity.LEFT; params.leftMargin = x; params.topMargin = y; root.addView(bannerView, params); bannerView.setAdListener(new AdListener() { @Override public void onAdLoaded() { Log.d(TAG, "Banner loaded"); } @Override public void onAdFailedToLoad(LoadAdError adError) { Log.e(TAG, "Banner failed: " + adError.getMessage()); } }); bannerView.loadAd(new AdRequest.Builder().build()); }); } public static void hideBanner(Activity activity) { mainHandler.post(() -> { FrameLayout root = activity.findViewById(android.R.id.content); if (root != null && bannerView != null) { root.removeView(bannerView); bannerView.destroy(); bannerView = null; } }); } public static void loadInterstitial(Activity activity) { mainHandler.post(() -> InterstitialAd.load( activity, TEST_INTERSTITIAL, new AdRequest.Builder().build(), new InterstitialAdLoadCallback() { @Override public void onAdLoaded(InterstitialAd ad) { interstitialAd = ad; Log.d(TAG, "Interstitial loaded"); } @Override public void onAdFailedToLoad(LoadAdError error) { interstitialAd = null; Log.e(TAG, "Interstitial failed: " + error.getMessage()); } } )); } public static void showInterstitial(Activity activity) { mainHandler.post(() -> { if (interstitialAd == null) { Toast.makeText(activity, "Interstitial not ready yet", Toast.LENGTH_SHORT).show(); loadInterstitial(activity); return; } interstitialAd.setFullScreenContentCallback(new FullScreenContentCallback() { @Override public void onAdDismissedFullScreenContent() { interstitialAd = null; loadInterstitial(activity); } @Override public void onAdFailedToShowFullScreenContent(AdError adError) { interstitialAd = null; loadInterstitial(activity); } @Override public void onAdShowedFullScreenContent() { Log.d(TAG, "Interstitial shown"); } }); interstitialAd.show(activity); }); } public static void loadRewarded(Activity activity) { mainHandler.post(() -> RewardedAd.load( activity, TEST_REWARDED, new AdRequest.Builder().build(), new RewardedAdLoadCallback() { @Override public void onAdLoaded(RewardedAd ad) { rewardedAd = ad; Log.d(TAG, "Rewarded loaded"); } @Override public void onAdFailedToLoad(LoadAdError error) { rewardedAd = null; Log.e(TAG, "Rewarded failed: " + error.getMessage()); } } )); } public static void showRewarded(Activity activity) { mainHandler.post(() -> { if (rewardedAd == null) { Toast.makeText(activity, "Rewarded ad not ready yet", Toast.LENGTH_SHORT).show(); loadRewarded(activity); return; } rewardedAd.setFullScreenContentCallback(new FullScreenContentCallback() { @Override public void onAdDismissedFullScreenContent() { rewardedAd = null; loadRewarded(activity); } @Override public void onAdFailedToShowFullScreenContent(AdError adError) { rewardedAd = null; loadRewarded(activity); } }); rewardedAd.show(activity, rewardItem -> { int amount = rewardItem.getAmount(); String type = rewardItem.getType(); Toast.makeText( activity, "Reward earned: " + amount + " " + type, Toast.LENGTH_LONG ).show(); Log.d(TAG, "Reward earned: " + amount + " " + type); }); }); } public static void loadRewardedInterstitial(Activity activity) { mainHandler.post(() -> RewardedInterstitialAd.load( activity, TEST_REWARDED_INTERSTITIAL, new AdRequest.Builder().build(), new RewardedInterstitialAdLoadCallback() { @Override public void onAdLoaded(RewardedInterstitialAd ad) { rewardedInterstitialAd = ad; Log.d(TAG, "Rewarded interstitial loaded"); } @Override public void onAdFailedToLoad(LoadAdError error) { rewardedInterstitialAd = null; Log.e(TAG, "Rewarded interstitial failed: " + error.getMessage()); } } )); } public static void showRewardedInterstitial(Activity activity) { mainHandler.post(() -> { if (rewardedInterstitialAd == null) { Toast.makeText(activity, "Rewarded interstitial not ready yet", Toast.LENGTH_SHORT).show(); loadRewardedInterstitial(activity); return; } rewardedInterstitialAd.setFullScreenContentCallback(new FullScreenContentCallback() { @Override public void onAdDismissedFullScreenContent() { rewardedInterstitialAd = null; loadRewardedInterstitial(activity); } @Override public void onAdFailedToShowFullScreenContent(AdError adError) { rewardedInterstitialAd = null; loadRewardedInterstitial(activity); } }); rewardedInterstitialAd.show(activity, rewardItem -> { Toast.makeText(activity, "Rewarded interstitial reward earned", Toast.LENGTH_LONG).show(); }); }); } public static void loadAppOpen(Activity activity) { mainHandler.post(() -> AppOpenAd.load( activity, TEST_APP_OPEN, new AdRequest.Builder().build(), new AppOpenAd.AppOpenAdLoadCallback() { @Override public void onAdLoaded(AppOpenAd ad) { appOpenAd = ad; Log.d(TAG, "App open loaded"); } @Override public void onAdFailedToLoad(LoadAdError error) { appOpenAd = null; Log.e(TAG, "App open failed: " + error.getMessage()); } } )); } public static void showAppOpen(Activity activity) { mainHandler.post(() -> { if (appOpenAd == null) { Toast.makeText(activity, "App open ad not ready yet", Toast.LENGTH_SHORT).show(); loadAppOpen(activity); return; } appOpenAd.setFullScreenContentCallback(new FullScreenContentCallback() { @Override public void onAdDismissedFullScreenContent() { appOpenAd = null; loadAppOpen(activity); } @Override public void onAdFailedToShowFullScreenContent(AdError adError) { appOpenAd = null; loadAppOpen(activity); } }); appOpenAd.show(activity); }); } public static void showNative(Activity activity, int x, int y, int width, int height) { mainHandler.post(() -> { FrameLayout root = activity.findViewById(android.R.id.content); if (root == null) { Log.e(TAG, "Root view not found"); return; } if (nativeContainer != null) { root.removeView(nativeContainer); nativeContainer = null; } if (nativeAd != null) { nativeAd.destroy(); nativeAd = null; } nativeContainer = new FrameLayout(activity); FrameLayout.LayoutParams containerParams = new FrameLayout.LayoutParams(width, height); containerParams.gravity = Gravity.TOP | Gravity.LEFT; containerParams.leftMargin = x; containerParams.topMargin = y; root.addView(nativeContainer, containerParams); AdLoader adLoader = new AdLoader.Builder(activity, TEST_NATIVE) .forNativeAd(ad -> { nativeAd = ad; NativeAdView adView = createNativeAdView(activity, ad); nativeContainer.removeAllViews(); nativeContainer.addView( adView, new FrameLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT ) ); }) .withAdListener(new AdListener() { @Override public void onAdFailedToLoad(LoadAdError adError) { Log.e(TAG, "Native failed: " + adError.getMessage()); } }) .build(); adLoader.loadAd(new AdRequest.Builder().build()); }); } private static NativeAdView createNativeAdView(Activity activity, NativeAd ad) { NativeAdView adView = new NativeAdView(activity); LinearLayout layout = new LinearLayout(activity); layout.setOrientation(LinearLayout.VERTICAL); layout.setPadding(18, 18, 18, 18); layout.setBackgroundColor(Color.rgb(245, 245, 245)); TextView adLabel = new TextView(activity); adLabel.setText("Ad"); adLabel.setTextColor(Color.WHITE); adLabel.setBackgroundColor(Color.rgb(255, 170, 0)); adLabel.setPadding(8, 4, 8, 4); TextView headline = new TextView(activity); headline.setTextSize(18); headline.setTextColor(Color.BLACK); TextView body = new TextView(activity); body.setTextSize(14); body.setTextColor(Color.DKGRAY); MediaView mediaView = new MediaView(activity); mediaView.setMinimumHeight(160); Button callToAction = new Button(activity); layout.addView(adLabel); layout.addView(headline); layout.addView(body); layout.addView(mediaView); layout.addView(callToAction); adView.addView(layout); adView.setHeadlineView(headline); adView.setBodyView(body); adView.setMediaView(mediaView); adView.setCallToActionView(callToAction); headline.setText(ad.getHeadline()); if (ad.getBody() != null) { body.setText(ad.getBody()); } else { body.setText(""); } if (ad.getCallToAction() != null) { callToAction.setText(ad.getCallToAction()); } else { callToAction.setText("Open"); } if (ad.getMediaContent() != null) { mediaView.setMediaContent(ad.getMediaContent()); } adView.setNativeAd(ad); return adView; } public static void destroyAds(Activity activity) { mainHandler.post(() -> { FrameLayout root = activity.findViewById(android.R.id.content); if (root != null && bannerView != null) { root.removeView(bannerView); } if (root != null && nativeContainer != null) { root.removeView(nativeContainer); } if (bannerView != null) { bannerView.destroy(); bannerView = null; } if (nativeAd != null) { nativeAd.destroy(); nativeAd = null; } nativeContainer = null; interstitialAd = null; rewardedAd = null; rewardedInterstitialAd = null; appOpenAd = null; }); } }
Google’s official test ad unit IDs include adaptive banner, interstitial, rewarded, rewarded interstitial, app open, native, and native video. (Google for Developers)
Edit your widget.h:
cpp#ifndef WIDGET_H #define WIDGET_H #include <QWidget> QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACE class Widget : public QWidget { Q_OBJECT public: Widget(QWidget *parent = nullptr); ~Widget(); protected: void showEvent(QShowEvent *event) override; void resizeEvent(QResizeEvent *event) override; private slots: void on_pushButton_clicked(); private: Ui::Widget *ui; void initializeAds(); void showBannerAtLabel(); void showNativeAtLabel(); void showInterstitial(); void showRewarded(); void showRewardedInterstitial(); void showAppOpen(); void destroyAds(); }; #endif
Now edit your widget.cpp:
cpp#include "widget.h" #include "./ui_widget.h" #include <QTimer> #include <QPoint> #include <QRect> #include <QShowEvent> #include <QResizeEvent> #ifdef Q_OS_ANDROID #include <QJniObject> #include <QNativeInterface> #endif static const char *ADMOB_BRIDGE_CLASS = "com/example/qtadmob/AdMobBridge"; Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { ui->setupUi(this); ui->label->setText("Banner placeholder"); ui->label->setMinimumHeight(90); ui->label->setStyleSheet("background:#dddddd; color:#333333;"); initializeAds(); } Widget::~Widget() { destroyAds(); delete ui; } void Widget::showEvent(QShowEvent *event) { QWidget::showEvent(event); // Wait until Qt lays out the widget, then place Android banner over ui->label. QTimer::singleShot(500, this, [this]() { showBannerAtLabel(); }); } void Widget::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); // Reposition banner after rotation or layout changes. QTimer::singleShot(300, this, [this]() { showBannerAtLabel(); }); } void Widget::initializeAds() { #ifdef Q_OS_ANDROID QJniObject activity = QNativeInterface::QAndroidApplication::context(); if (!activity.isValid()) return; QJniObject::callStaticMethod<void>( ADMOB_BRIDGE_CLASS, "initialize", "(Landroid/app/Activity;)V", activity.object<jobject>() ); #endif } void Widget::showBannerAtLabel() { #ifdef Q_OS_ANDROID QJniObject activity = QNativeInterface::QAndroidApplication::context(); if (!activity.isValid()) return; QPoint topLeft = ui->label->mapToGlobal(QPoint(0, 0)); QRect rect(topLeft, ui->label->size()); qreal dpr = devicePixelRatioF(); int x = int(rect.x() * dpr); int y = int(rect.y() * dpr); int w = int(rect.width() * dpr); int h = int(rect.height() * dpr); QJniObject::callStaticMethod<void>( ADMOB_BRIDGE_CLASS, "showBanner", "(Landroid/app/Activity;IIII)V", activity.object<jobject>(), x, y, w, h ); #endif } void Widget::showNativeAtLabel() { #ifdef Q_OS_ANDROID QJniObject activity = QNativeInterface::QAndroidApplication::context(); if (!activity.isValid()) return; QPoint topLeft = ui->label->mapToGlobal(QPoint(0, 0)); QRect rect(topLeft, ui->label->size()); qreal dpr = devicePixelRatioF(); int x = int(rect.x() * dpr); int y = int(rect.y() * dpr); int w = int(rect.width() * dpr); int h = int(rect.height() * dpr); QJniObject::callStaticMethod<void>( ADMOB_BRIDGE_CLASS, "showNative", "(Landroid/app/Activity;IIII)V", activity.object<jobject>(), x, y, w, h ); #endif } void Widget::showInterstitial() { #ifdef Q_OS_ANDROID QJniObject activity = QNativeInterface::QAndroidApplication::context(); if (!activity.isValid()) return; QJniObject::callStaticMethod<void>( ADMOB_BRIDGE_CLASS, "showInterstitial", "(Landroid/app/Activity;)V", activity.object<jobject>() ); #endif } void Widget::showRewarded() { #ifdef Q_OS_ANDROID QJniObject activity = QNativeInterface::QAndroidApplication::context(); if (!activity.isValid()) return; QJniObject::callStaticMethod<void>( ADMOB_BRIDGE_CLASS, "showRewarded", "(Landroid/app/Activity;)V", activity.object<jobject>() ); #endif } void Widget::showRewardedInterstitial() { #ifdef Q_OS_ANDROID QJniObject activity = QNativeInterface::QAndroidApplication::context(); if (!activity.isValid()) return; QJniObject::callStaticMethod<void>( ADMOB_BRIDGE_CLASS, "showRewardedInterstitial", "(Landroid/app/Activity;)V", activity.object<jobject>() ); #endif } void Widget::showAppOpen() { #ifdef Q_OS_ANDROID QJniObject activity = QNativeInterface::QAndroidApplication::context(); if (!activity.isValid()) return; QJniObject::callStaticMethod<void>( ADMOB_BRIDGE_CLASS, "showAppOpen", "(Landroid/app/Activity;)V", activity.object<jobject>() ); #endif } void Widget::destroyAds() { #ifdef Q_OS_ANDROID QJniObject activity = QNativeInterface::QAndroidApplication::context(); if (!activity.isValid()) return; QJniObject::callStaticMethod<void>( ADMOB_BRIDGE_CLASS, "destroyAds", "(Landroid/app/Activity;)V", activity.object<jobject>() ); #endif } void Widget::on_pushButton_clicked() { // here to show reward video showRewarded(); }
Qt provides QJniObject for calling Java from Qt, and QNativeInterface::QAndroidApplication::context() can return the current Android Activity context. (Qt Documentation)
Your banner shows automatically at ui->label after the widget is shown:
cppQTimer::singleShot(500, this, [this]() { showBannerAtLabel(); });
Google describes banners as rectangular ads that occupy part of the layout, often anchored at the top or bottom. (Google for Developers)
Your existing button slot is correct:
cppvoid Widget::on_pushButton_clicked() { showRewarded(); }
Rewarded ads let you give in-app rewards after the user interacts with a video, playable ad, or survey. (Google for Developers)
Add another button in Qt Designer and connect it:
cppvoid Widget::on_pushButtonInterstitial_clicked() { showInterstitial(); }
Interstitial ads are full-screen ads best shown at natural breaks, not randomly during interaction. (Google for Developers)
Add a button:
cppvoid Widget::on_pushButtonRewardedInterstitial_clicked() { showRewardedInterstitial(); }
For production, show your own intro screen first: “Watch this ad to get 10 coins” with a Skip option. Google says rewarded interstitials require an intro screen with clear reward messaging and a skip option. (Google for Developers)
For testing, add a button:
cppvoid Widget::on_pushButtonAppOpen_clicked() { showAppOpen(); }
For real apps, use it only when the app opens or returns to foreground. App open ads are designed for app load screens. (Google for Developers)
For testing, you can reuse the same label area:
cppvoid Widget::on_pushButtonNative_clicked() { showNativeAtLabel(); }
For a real app, use a larger placeholder area than a banner. Native ads need room for headline, body, media, call-to-action, and attribution. Google says native ads provide assets to your app, and your app is responsible for displaying them. (Google for Developers)
Use these while developing:
| Format | Test ad unit ID |
|---|---|
| App open | ca-app-pub-3940256099942544/9257395921 |
| Adaptive banner | ca-app-pub-3940256099942544/9214589741 |
| Fixed banner | ca-app-pub-3940256099942544/6300978111 |
| Interstitial | ca-app-pub-3940256099942544/1033173712 |
| Rewarded | ca-app-pub-3940256099942544/5224354917 |
| Rewarded interstitial | ca-app-pub-3940256099942544/5354046379 |
| Native | ca-app-pub-3940256099942544/2247696110 |
| Native video | ca-app-pub-3940256099942544/1044960115 |
These are Google’s official demo ad unit IDs. (Google for Developers)
Do not use real ads while debugging. First make everything work with test IDs.
When ready:
AndroidManifest.xml:xmlandroid:value="ca-app-pub-xxxxxxxxxxxxxxxx~yyyyyyyyyy"
AdMobBridge.java with your real ad unit IDs.app-ads.txt if required.AdMob says new apps must pass app readiness review before they can fully serve ads. (Google Help) AdMob also says apps will not be able to fully serve ads until they are verified with app-ads.txt and approved after app readiness review when verification is required. (Google Help) New AdMob accounts may take up to 24 hours for approval, and new apps/ad units typically take up to an hour to start showing ads. (Google Help)
You should also implement consent. Google’s UMP SDK is used to manage privacy choices, and Google says you should request consent info at every app launch and check canRequestAds() before requesting ads. (Google for Developers)
Follow these strictly:
AdMob estimated earnings are adjusted at the end of the month for invalid clicks or impressions, and revenue from invalid activity can be deducted. (Google Help)
There is no fixed “price per 1000 users.” AdMob pays based on impressions, geography, ad format, advertiser demand, match rate, click-through rate, and eCPM. Google defines eCPM as:
texteCPM = (Total earnings / Impressions) × 1000
Google also says revenue depends on metrics such as impressions, eCPM, impression CTR, match rate, and show rate. (Google Help)
Use this formula:
textRevenue = (Impressions / 1000) × eCPM
Example with 1000 daily users:
| Scenario | Ads per user/day | Impressions | Example eCPM | Estimated revenue/day |
|---|---|---|---|---|
| Light banner only | 2 | 2,000 | $0.20 | $0.40 |
| Banner + interstitial | 4 | 4,000 | $1.00 | $4.00 |
| Rewarded-heavy app | 2 | 2,000 | $5.00 | $10.00 |
| Strong US/game traffic | 5 | 5,000 | $8.00 | $40.00 |
These are examples, not guaranteed prices. Real eCPM can be much lower or higher. Rewarded and interstitial ads often earn more per impression than banners, but they must be used carefully or users will uninstall the app.
For a simple Qt Widgets test app:
ui->label.pushButton.For your current code, the direct version is:
cppWidget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { ui->setupUi(this); ui->label->setText("Banner placeholder"); ui->label->setMinimumHeight(90); initializeAds(); QTimer::singleShot(500, this, [this]() { showBannerAtLabel(); }); } void Widget::on_pushButton_clicked() { showRewarded(); }
That is the cleanest Qt Widgets pattern for testing AdMob on Android.
W/default : java.lang.ClassNotFoundException: Didn't find class "com.example.admob.AdMobBridge" on path: DexPathList[[zip file "/data/app/~~PADbnrlpzEgq922cGau3Xw==/org.qtproject.example.Admob-ptcM-xzz741jyTk_Uoo1Yg==/base.apk"],nativeLibraryDirectories=[/system/lib64, /system_ext/lib64]]
W/default : at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:218)
W/default : at java.lang.ClassLoader.loadClass(ClassLoader.java:379)
W/default : at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
W/default : at org.qtproject.qt.android.QtNative.startQtNativeApplication(Native Method)
W/default : at org.qtproject.qt.android.QtNative.lambda2(QtNative.java:366)
W/default : at org.qtproject.qt.android.QtNativeSyntheticClass:0)
W/default : at org.qtproject.qt.android.QtThreadstartApplication2(QtNative.java:366)
W/default : at org.qtproject.qt.android.QtNative$$ExternalSyntheticLambda3.run(D8$$SyntheticClass:0)
W/default : at org.qtproject.qt.android.QtThread1.run(QtThread.java:25)
W/default : at java.lang.Thread.run(Thread.java:920)
This error is exactly here:
textClassNotFoundException: Didn't find class "com.example.admob.AdMobBridge"
Your C++ code is calling:
cpp"com/example/admob/AdMobBridge"
But your Java file is probably in a different package, for example from my previous example:
javapackage com.example.qtadmob;
So Android cannot find:
textcom.example.admob.AdMobBridge
because the real class is probably:
textcom.example.qtadmob.AdMobBridge
Qt only packages Java/Kotlin files correctly when they are under <QT_ANDROID_PACKAGE_SOURCE_DIR>/src/. Qt documents that custom Java/Kotlin code should be placed under that src directory, and QT_ANDROID_PACKAGE_SOURCE_DIR must point to the Android package template folder. (Qt Documentation)
Choose one package name. Since your error expects this:
textcom.example.admob.AdMobBridge
use that everywhere.
Your Java file must be here:
textyour_project/ android/ src/ com/ example/ admob/ AdMobBridge.java
At the top of AdMobBridge.java, use:
javapackage com.example.admob;
In C++, use:
cppstatic const char *ADMOB_BRIDGE_CLASS = "com/example/admob/AdMobBridge";
So your C++ call becomes:
cppQJniObject::callStaticMethod<void>( "com/example/admob/AdMobBridge", "initialize", "(Landroid/app/Activity;)V", activity.object<jobject>() );
QJniObject is the Qt class used for Java/JNI calls on Android. (Qt Documentation)
CMakeLists.txtMake sure your target has this:
cmakeif(ANDROID) set_property(TARGET Admob PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android" ) endif()
Your target may not be called Admob. Check this part:
cmakeqt_add_executable(Admob main.cpp widget.cpp widget.h widget.ui )
The name after qt_add_executable is the target name. Use the same name in set_property.
Example:
cmakeqt_add_executable(Admob main.cpp widget.cpp widget.h widget.ui ) target_link_libraries(Admob PRIVATE Qt6::Widgets) if(ANDROID) set_property(TARGET Admob PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android" ) endif()
After changing Java package/folders:
This matters because old APKs/build directories can keep stale Java packaging.
After building, you can check the APK content.
In Qt Creator build folder, find the APK, then run:
bashunzip -l app-debug.apk | grep AdMobBridge
You should see something related to compiled dex/classes, not necessarily the .java file. If nothing related is found, your Java file is not being included.
The most common reason is this folder is wrong:
textandroid/com/example/admob/AdMobBridge.java
It must be:
textandroid/src/com/example/admob/AdMobBridge.java
AdMobBridge.javaPath:
textandroid/src/com/example/admob/AdMobBridge.java
Top lines:
javapackage com.example.admob; import android.app.Activity; import android.util.Log; public class AdMobBridge { public static void initialize(Activity activity) { Log.d("AdMobBridge", "initialize called"); } }
widget.cppcpp#ifdef Q_OS_ANDROID #include <QJniObject> #include <QNativeInterface> #endif static const char *ADMOB_BRIDGE_CLASS = "com/example/admob/AdMobBridge"; void Widget::initializeAds() { #ifdef Q_OS_ANDROID QJniObject activity = QNativeInterface::QAndroidApplication::context(); if (!activity.isValid()) return; QJniObject::callStaticMethod<void>( ADMOB_BRIDGE_CLASS, "initialize", "(Landroid/app/Activity;)V", activity.object<jobject>() ); #endif }
First test only this minimal bridge. If logcat shows:
textAdMobBridge: initialize called
then JNI is working. After that, add the full AdMob code back.
great it works i want create func named AdReward_Watched in widget.cpp so when he watch full ad, i do something like qDebug()<< ward added,
also i want control message: Reward earned: 10 coins ,after ad finished i want change this message
Great. Add a Java → C++ callback so when the rewarded ad gives the reward, your Qt function runs.
Assuming your package is now:
textcom.example.admob
widget.hcpp#ifndef WIDGET_H #define WIDGET_H #include <QWidget> QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACE class Widget : public QWidget { Q_OBJECT public: Widget(QWidget *parent = nullptr); ~Widget(); void AdReward_Watched(int amount, const QString &type); private slots: void on_pushButton_clicked(); private: Ui::Widget *ui; void initializeAds(); void showRewarded(); void setRewardMessage(const QString &message); }; #endif
widget.cppAdd these includes:
cpp#include "widget.h" #include "./ui_widget.h" #include <QDebug> #include <QMetaObject> #ifdef Q_OS_ANDROID #include <QJniObject> #include <QNativeInterface> #include <jni.h> #endif
Then add this global pointer near the top of widget.cpp:
cppstatic Widget *g_widgetInstance = nullptr; static const char *ADMOB_BRIDGE_CLASS = "com/example/admob/AdMobBridge";
In your constructor:
cppWidget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { g_widgetInstance = this; ui->setupUi(this); ui->label->setText("here banner"); initializeAds(); // Change rewarded message here. setRewardMessage("Reward added: {amount} {type}"); }
In your destructor:
cppWidget::~Widget() { if (g_widgetInstance == this) g_widgetInstance = nullptr; delete ui; }
widget.cppcppvoid Widget::AdReward_Watched(int amount, const QString &type) { qDebug() << "reward added"; qDebug() << "Amount:" << amount; qDebug() << "Type:" << type; // Example: update UI // ui->labelCoins->setText(QString("Coins: %1").arg(amount)); // Example: add coins to your own variable/database // coins += amount; }
This function is called only when the AdMob rewarded callback says the user earned the reward.
widget.cppAdd this outside the Widget class functions:
cpp#ifdef Q_OS_ANDROID extern "C" JNIEXPORT void JNICALL Java_com_example_admob_AdMobBridge_nativeRewardWatched( JNIEnv *env, jclass, jint amount, jstring type ) { QString rewardType; if (type) { const char *chars = env->GetStringUTFChars(type, nullptr); rewardType = QString::fromUtf8(chars); env->ReleaseStringUTFChars(type, chars); } if (!g_widgetInstance) return; QMetaObject::invokeMethod( g_widgetInstance, [amount, rewardType]() { if (g_widgetInstance) { g_widgetInstance->AdReward_Watched(amount, rewardType); } }, Qt::QueuedConnection ); } #endif
Important: because your Java package is:
javapackage com.example.admob;
the C++ JNI function must be named:
cppJava_com_example_admob_AdMobBridge_nativeRewardWatched
If your package is different, the function name must change too.
In widget.cpp:
cppvoid Widget::setRewardMessage(const QString &message) { #ifdef Q_OS_ANDROID QJniObject jMessage = QJniObject::fromString(message); QJniObject::callStaticMethod<void>( ADMOB_BRIDGE_CLASS, "setRewardMessage", "(Ljava/lang/String;)V", jMessage.object<jstring>() ); #else Q_UNUSED(message); #endif }
You can now change the message from C++ like this:
cppsetRewardMessage("Reward added: {amount} {type}");
or:
cppsetRewardMessage("You got {amount} coins!");
or:
cppsetRewardMessage("Thanks for watching the full ad");
AdMobBridge.javaInside your Java class, add this near the top:
javaprivate static String rewardedMessage = "Reward earned: {amount} {type}"; public static native void nativeRewardWatched(int amount, String type); public static void setRewardMessage(String message) { mainHandler.post(() -> { rewardedMessage = message; }); } private static String buildRewardMessage(int amount, String type) { return rewardedMessage .replace("{amount}", String.valueOf(amount)) .replace("{type}", type); }
So your Java class has something like:
javapublic class AdMobBridge { private static final Handler mainHandler = new Handler(Looper.getMainLooper()); private static String rewardedMessage = "Reward earned: {amount} {type}"; public static native void nativeRewardWatched(int amount, String type); public static void setRewardMessage(String message) { mainHandler.post(() -> { rewardedMessage = message; }); } private static String buildRewardMessage(int amount, String type) { return rewardedMessage .replace("{amount}", String.valueOf(amount)) .replace("{type}", type); } // rest of your class... }
Find this part in your Java showRewarded function:
javarewardedAd.show(activity, rewardItem -> { int amount = rewardItem.getAmount(); String type = rewardItem.getType(); Toast.makeText( activity, "Reward earned: " + amount + " " + type, Toast.LENGTH_LONG ).show(); Log.d(TAG, "Reward earned: " + amount + " " + type); });
Replace it with this:
javarewardedAd.show(activity, rewardItem -> { int amount = rewardItem.getAmount(); String type = rewardItem.getType(); nativeRewardWatched(amount, type); Toast.makeText( activity, buildRewardMessage(amount, type), Toast.LENGTH_LONG ).show(); Log.d(TAG, "Reward earned: " + amount + " " + type); });
Your Qt button remains:
cppvoid Widget::on_pushButton_clicked() { showRewarded(); }
Now the full flow is:
textQt button clicked → Java showRewarded() → user watches rewarded ad → AdMob gives reward → Java calls nativeRewardWatched(amount, type) → C++ calls Widget::AdReward_Watched(amount, type) → qDebug() << "reward added"
If you set:
cppsetRewardMessage("You got {amount} coins!");
and the reward amount is 10, the Android toast becomes:
textYou got 10 coins!
And Qt debug output becomes:
textreward added Amount: 10 Type: coins