novvy_ads 1.0.0-beta.33 copy "novvy_ads: ^1.0.0-beta.33" to clipboard
novvy_ads: ^1.0.0-beta.33 copied to clipboard

Flutter plugin for Novvy Ads SDK.

The Flutter plugin for the Novvy Ads SDK.

Features #

  • Interstitial Ads: Full-screen ads that cover the interface of their host app
  • Rewarded Ads: Ads that reward users for watching short videos, with a null-safe reward callback
  • Banner Ads: Self-managing inline banner ads with server-driven positioning and player-state scheduling
  • Feed Ads: Native or H5-zip feed ads with visibility-driven playback control for scrollable lists
  • AdMob Mediation: Seamless integration with Google AdMob mediation
  • Zero lifecycle boilerplate: The plugin manages the full load → show → dispose cycle internally for all ad types
  • Centralized callbacks: Register one NovvyAdsCallback at initialization; all ad outcomes are delivered there

Platform Support #

  • Android 7.0+ (API 24+)
  • iOS 13.0+ (simulator: Apple Silicon Mac only — see iOS notes)

Installation #

1. Add dependency #

Add novvy_ads to your pubspec.yaml:

dependencies:
  novvy_ads: ^1.0.0-beta.33

2. Get packages #

flutter pub get

3. Configure Android #

Add AdMob App ID to AndroidManifest

Open android/app/src/main/AndroidManifest.xml and add your AdMob App ID inside the <application> tag:

<manifest>
    <application>
        <!-- Required by Google Mobile Ads SDK -->
        <meta-data
            android:name="com.google.android.gms.ads.APPLICATION_ID"
            android:value="ca-app-pub-xxxxxxxxxxxxxxxx~yyyyyyyyyy"/>
    </application>
</manifest>

Replace ca-app-pub-xxxxxxxxxxxxxxxx~yyyyyyyyyy with your actual AdMob App ID. The app will crash at launch without this entry.

Verify minSdk

Ensure your app's android/app/build.gradle sets minSdk to at least 24:

android {
    defaultConfig {
        minSdk 24
    }
}

4. Configure iOS #

Verify deployment target

In ios/Podfile, ensure the platform line is iOS 13.0 or higher:

platform :ios, '13.0'

Install pods

From your app's ios/ directory:

pod install

The first pod install automatically downloads the NovvyAds iOS SDK xcframework (~5 MB) into the plugin's ios/ directory; subsequent runs skip the download unless the pinned SDK version changes.

Add NSUserTrackingUsageDescription to Info.plist

The NovvyAds iOS SDK uses the App Tracking Transparency (ATT) API to request permission for the IDFA (used for ad personalization and attribution). iOS will abort the process at launch if you call any ATT-backed API without declaring NSUserTrackingUsageDescription in your app's Info.plist.

Open ios/Runner/Info.plist and add:

<key>NSUserTrackingUsageDescription</key>
<string>This identifier will be used to deliver personalized ads to you.</string>

Customize the description string to match your app's tone and any store-review guidance. The exact text is shown to users in the system tracking permission dialog.

Exclude x86_64 for simulator builds

The bundled NovvyAds xcframework only ships arm64 slices, so simulator builds must drop x86_64 (Intel Mac simulator). The plugin's podspec already declares this for its own target, but to keep the host app project in sync — especially on Xcode 15+ where user_target_xcconfig can be ignored — add the following to your ios/Podfile:

post_install do |installer|
  installer.pods_project.targets.each do |target|
    flutter_additional_ios_build_settings(target)

    # NovvyAds xcframework only ships arm64 simulator slices.
    target.build_configurations.each do |config|
      config.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = 'x86_64'
    end
  end

  installer.aggregate_targets.each do |aggregate|
    aggregate.user_project.native_targets.each do |target|
      target.build_configurations.each do |config|
        config.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = 'x86_64'
      end
    end
    aggregate.user_project.save
  end
end

Apple Silicon Macs only for simulator. Intel Macs and Intel-based CI runners cannot run the iOS simulator until the upstream NovvyAds SDK ships an x86_64 simulator slice. Real-device builds (arm64) work from any Mac. See iOS notes for context.

Usage #

1. Implement NovvyAdsCallback #

Create a class that implements the callback interface. All ad outcomes — success or failure — are delivered here. You only need to wire this up once for your entire app.

import 'package:novvy_ads/novvy_ads.dart';

class MyAdsCallback implements NovvyAdsCallback {
  @override
  void onInterstitialDismissed(String adUnitId) {
    print('Interstitial dismissed: $adUnitId');
  }

  @override
  void onInterstitialFailed(String adUnitId, String error) {
    print('Interstitial failed [$adUnitId]: $error');
  }

  @override
  void onRewardedDismissed(String adUnitId, NovvyReward? reward) {
    if (reward != null) {
      print('User earned reward: ${reward.amount} ${reward.type}');
      // Grant the reward here (coins, lives, unlocked content, etc.)
    } else {
      print('Rewarded ad dismissed without completing');
    }
  }

  @override
  void onRewardedFailed(String adUnitId, String error) {
    print('Rewarded ad failed [$adUnitId]: $error');
  }

  // Optional — override only if you need banner event tracking
  @override
  void onBannerImpression(String adUnitId) {
    print('Banner impression: $adUnitId');
  }

  @override
  void onBannerFailed(String adUnitId, String error) {
    print('Banner failed [$adUnitId]: $error');
  }

  @override
  void onBannerClosed(String adUnitId) {
    print('Banner closed by user: $adUnitId');
  }
}

2. Initialize the SDK #

Call NovvyAds.initialize() once in your main() function, before runApp():

import 'package:flutter/widgets.dart';
import 'package:novvy_ads/novvy_ads.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await NovvyAds.initialize(
    config: NovvyInitConfig(
      appId: 'YOUR_APP_ID',
      endpoint: 'YOUR_END_POINT',
      apiKey: 'YOUR_API_KEY',
    ),
    callback: MyAdsCallback(),
  );

  runApp(const MyApp());
}

To improve ad targeting and revenue, pass user- and content-level context to the SDK before showing ads. Call this whenever the user signs in, the user profile changes, or the user navigates to new content:

NovvyAds.updateContext(
  NovvyContextConfig(
    // ── User attributes (privacy-sensitive) ──────────────────────────
    userId:      'your-publisher-side-user-id',
    hashedEmail: NovvyAds.hashEmail('your_email@example.com'),
    isPaidUser:  true,
    age:         28,

    // ── Content attributes ───────────────────────────────────────────
    seriesName:    'YOUR_SERIES_NAME',
    episodeNumber: 1,
    contentUrl:    'YOUR_CONTENT_URL',
  ),
);

All fields are independently optional — pass any combination, or none. Each field maps directly to the OpenRTB bid request the SDK sends to the Novvy bid server:

Field Sent as What to pass
userId user.user_id Your publisher-side stable user identifier (any string). Use the same value you store on your backend so server-side reporting can be reconciled.
hashedEmail user.hashed_email The user's email address, already SHA-256 hashed. Always pipe through NovvyAds.hashEmail(...) — never pass a raw email.
isPaidUser user.is_paid_user true if the current user has an active paid subscription / premium entitlement, false otherwise. Used for paid-vs-free segmentation.
age user.age The user's age in years (an integer such as 28). Used for demographic targeting.
seriesName app.content.series_name The current series / show / channel the user is watching.
episodeNumber app.content.episode_number The episode number within that series.
contentUrl app.content.url The canonical URL of the current content page (deep link or web URL).

Tip: NovvyAds.hashEmail(rawEmail) returns a SHA-256 hex digest. Always hash on the client — never pass raw email addresses to the SDK.

When does it take effect? Context is read lazily at each ad's load() call, so changes only apply to ads loaded after you call updateContext. Set it as early as you have the data; it's safe to call multiple times.

Note: userId, isPaidUser, and age are currently forwarded on Android only; iOS support lands once the iOS native SDK ships the fields. hashedEmail and the content fields work on both platforms today.

4. Interstitial Ads #

Show a full-screen interstitial at a natural transition point (e.g. between episodes):

import 'package:novvy_ads/novvy_ads.dart';

class EpisodeScreen extends StatefulWidget {
  const EpisodeScreen({super.key});

  @override
  State<EpisodeScreen> createState() => _EpisodeScreenState();
}

class _EpisodeScreenState extends State<EpisodeScreen> {
  @override
  void initState() {
    super.initState();
    NovvyAds.updateContext(NovvyContextConfig(
      seriesName: 'My Series',
      episodeNumber: 3,
    ));
  }

  void _onEpisodeEnd() {
    NovvyAds.showInterstitial('YOUR_AD_UNIT_ID');
    // Result arrives in MyAdsCallback.onInterstitialDismissed
    // or MyAdsCallback.onInterstitialFailed
  }

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: _onEpisodeEnd,
      child: const Text('Next Episode'),
    );
  }
}

The plugin manages the full lifecycle (load → show → dispose) internally.

5. Rewarded Ads #

Offer users a reward in exchange for watching a video:

void _onUnlockContent() {
  NovvyAds.showRewarded('YOUR_REWARDED_AD_UNIT_ID');
  // Result arrives in MyAdsCallback.onRewardedDismissed or onRewardedFailed
}

Important: onRewardedDismissed is always called when the ad closes. Only grant the reward when reward is non-null — a null value means the user dismissed the ad early without completing the video.

6. Banner Ads #

Banner ads are self-managing inline native views that render inside your widget tree. Like interstitial and rewarded ads, the plugin handles the full lifecycle (load → attach → show/hide → dispose) internally.

The SDK supports two server-driven display modes — no client-side code changes are needed to switch between them:

Mode Behavior Use case
Playback Position Banner appears/disappears at specific playback timestamps configured on the server Mid-roll or post-roll overlay ads timed to content
Play / Pause Banner appears when the player is paused and hides when playback resumes Non-intrusive ads that only show during user-initiated pauses

The display mode is controlled entirely by the server configuration for each ad unit. Your client integration is the same for both modes — just implement NovvyPlayerAdapter and the SDK handles the rest.

Implement a NovvyPlayerAdapter

The SDK uses the adapter to read your player's current state. Implement this interface to bridge your video/audio player:

import 'package:novvy_ads/novvy_ads.dart';

class MyPlayerAdapter extends NovvyPlayerAdapter {
  final MyVideoPlayer _player;

  MyPlayerAdapter(this._player);

  @override
  int get currentPositionMs => _player.position.inMilliseconds;

  @override
  bool get isPlaying => _player.isPlaying;
}
  • currentPositionMs — used by the Playback Position mode to evaluate show/hide time windows.
  • isPlaying — used by the Play / Pause mode to toggle banner visibility.

Both properties are polled by the plugin (~500 ms interval) and forwarded to the native SDK, so keep the getters lightweight.

Display the banner

Place NovvyAds.banner() inside a Stack. The widget manages loading, positioning, and cleanup automatically:

import 'package:novvy_ads/novvy_ads.dart';

class _MyPageState extends State<MyPage> {
  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        // Your content
        MyContentWidget(),
        // Banner — auto-positioned by server config (top/bottom + offset)
        NovvyAds.banner(
          adUnitId: 'YOUR_BANNER_AD_UNIT_ID',
          player: MyPlayerAdapter(myVideoPlayer),
        ),
      ],
    );
  }
}

The banner has a fixed width of 260dp × 90dp, left-aligned within the container, and auto-positioned based on server-driven gravity (top/bottom) and vertical offset. It does not block touch events on surrounding UI.

Tip: When hidden (regardless of display mode), the widget is fully transparent and does not intercept touch events (IgnorePointer).

7. Feed Ads #

Feed ads are designed to be inserted into scrollable lists (e.g. article feeds, episode lists). Two creative formats are supported and the plugin selects the renderer automatically — host code is identical for both:

  • Image creative — rendered natively (hero image + product info overlay).
  • H5 zip creative — rendered in a WebView from a downloaded zip bundle (HTML/CSS/JS, optional video). The bundle is downloaded once per material_url and cached on disk; subsequent loads of the same creative reuse the cache.

Playback is driven by viewport visibility — you control when the ad plays and pauses via setPlaying(true/false).

Step 1 — Add feed ad callbacks

Add the feed-related overrides to your NovvyAdsCallback implementation:

class MyAdsCallback implements NovvyAdsCallback {
  // ... existing interstitial/rewarded/banner callbacks ...

  @override
  void onFeedAdReady(String adUnitId, NovvyFeedAdProvider provider) {
    // Ad loaded successfully — store the provider and insert provider.widget
    // into your feed list (see Step 3)
  }

  @override
  void onFeedAdFailed(String adUnitId, String error) {
    // Ad failed to load — log or retry as needed
    print('Feed ad failed [$adUnitId]: $error');
  }

  @override
  void onFeedAdPlaybackTick(String adUnitId, int remainingSeconds) {
    // Fires every second while the ad is playing.
    // Use this to show a countdown timer overlay if desired.
  }

  // Optional — override for analytics
  @override
  void onFeedAdImpression(String adUnitId) { }

  @override
  void onFeedAdClicked(String adUnitId) { }
}

Step 2 — Request a feed ad

Call loadFeedAd() to start loading. The result is delivered asynchronously via onFeedAdReady / onFeedAdFailed:

NovvyAds.loadFeedAd(
  adUnitId: 'YOUR_FEED_AD_UNIT_ID',
  durationSeconds: 5, // ad playback duration in seconds (default: 5)
);

Tip: Call loadFeedAd() ahead of time (e.g. when the page loads) so the ad is ready before the user scrolls to the insertion point.

Step 3 — Insert the ad widget into your feed

When onFeedAdReady fires, store the NovvyFeedAdProvider and insert its widget into your list data source:

NovvyFeedAdProvider? _feedAdProvider;

@override
void onFeedAdReady(String adUnitId, NovvyFeedAdProvider provider) {
  setState(() {
    _feedAdProvider = provider;
    _feedItems.insert(3, provider); // insert at desired position
  });
}

In your ListView.builder, render the ad widget wrapped in a fixed-height SizedBox:

ListView.builder(
  itemCount: _feedItems.length,
  itemBuilder: (context, index) {
    final item = _feedItems[index];
    if (item is NovvyFeedAdProvider) {
      return SizedBox(
        height: 250, // adjust to match your feed item height
        child: item.widget,
      );
    }
    return NormalFeedItem(item);
  },
)

Step 4 — Control playback based on visibility

Feed ads do not auto-play. You must call setPlaying(true) when the ad scrolls into the viewport and setPlaying(false) when it scrolls out. The recommended approach is to use the visibility_detector package:

if (item is NovvyFeedAdProvider) {
  return VisibilityDetector(
    key: Key('feed-ad-${item.hashCode}'),
    onVisibilityChanged: (info) {
      item.setPlaying(info.visibleFraction > 0.5);
    },
    child: SizedBox(
      height: 250,
      child: item.widget,
    ),
  );
}

The onFeedAdPlaybackTick callback fires every second while playing, providing a remainingSeconds countdown you can use to display a timer.

H5 zip note: When the feed scrolls back into view after a previous setPlaying(false), the H5 page is reloaded from index.html so playback restarts from a clean state — the user taps the creative to replay (matches typical TikTok-style feed UX).

Step 5 — Dispose when done

You must call provider.dispose() manually when the ad is no longer needed (e.g. the page is destroyed or the user navigates away). Removing the widget from the tree only detaches the native view — it does not release native resources.

@override
void dispose() {
  _feedAdProvider?.dispose();
  super.dispose();
}

Note: Feed ads currently support Android only. iOS support is coming soon.

API Reference #

NovvyAds #

Method Description
initialize({config, callback}) Initialize the Novvy SDK. Call once at startup.
updateContext(NovvyContextConfig) Update contextual signals for ad targeting.
showInterstitial(adUnitId) Load and show an interstitial ad.
showRewarded(adUnitId) Load and show a rewarded video ad.
banner({adUnitId, player}) Create a self-managing banner ad widget.
loadFeedAd({adUnitId, durationSeconds}) Load a feed ad. Result delivered via onFeedAdReady.
hashEmail(email) SHA-256 hash an email for privacy compliance.

NovvyAdsCallback #

Method Description
onInterstitialDismissed(adUnitId) Interstitial was shown and dismissed.
onInterstitialFailed(adUnitId, error) Interstitial failed to load or show.
onRewardedDismissed(adUnitId, reward) Rewarded ad dismissed; reward is non-null if earned.
onRewardedFailed(adUnitId, error) Rewarded ad failed to load or show.
onBannerImpression(adUnitId) (Optional) A banner ad impression was recorded.
onBannerFailed(adUnitId, error) (Optional) A banner ad failed to load.
onBannerClosed(adUnitId) (Optional) The user closed the banner via the close button.
onFeedAdReady(adUnitId, provider) Feed ad loaded; insert provider.widget into your list.
onFeedAdFailed(adUnitId, error) Feed ad failed to load.
onFeedAdPlaybackTick(adUnitId, remainingSeconds) (Optional) Fires every second with countdown.
onFeedAdImpression(adUnitId) (Optional) A feed ad impression was recorded.
onFeedAdClicked(adUnitId) (Optional) The feed ad was clicked.

NovvyInitConfig #

Field Type Description
appId String Your application ID
endpoint String Novvy API endpoint URL
apiKey String Your API key

NovvyContextConfig #

All fields are optional. See Set User & Content Context for usage and OpenRTB field mappings.

Field Type Description
userId String? Publisher-side stable user identifier (Android only for now)
hashedEmail String? SHA-256 hashed user email — use NovvyAds.hashEmail(...)
isPaidUser bool? Whether the current user is a paying/subscribed user (Android only for now)
age int? The user's age in years, for demographic targeting (Android only for now)
seriesName String? Current content series name
episodeNumber int? Current episode number
contentUrl String? URL of the current content page

NovvyPlayerAdapter (abstract) #

Property Type Description
currentPositionMs int Current playback position in milliseconds.
isPlaying bool Whether the player is currently playing.

NovvyFeedAdProvider #

Property / Method Type Description
widget Widget The native ad view to insert into your list. Wrap in a SizedBox with a fixed height.
setPlaying(bool) void Set playback state — true when visible, false when scrolled out.
dispose() void Release all native resources. Must be called manually.

NovvyReward #

Field Type Description
amount int Reward amount
type String Reward type identifier

Troubleshooting #

AdMob configuration #

"The Google Mobile Ads SDK was initialized incorrectly" / app crashes on launch:

  • Verify the <meta-data> entry for com.google.android.gms.ads.APPLICATION_ID is present in AndroidManifest.xml
  • Confirm the value is your AdMob App ID (starts with ca-app-pub-)

"Missing APPLICATION_ID" in logcat:

  • Ensure the <meta-data> tag is inside <application>, not <manifest>

Ads not loading #

onInterstitialFailed / onRewardedFailed fires immediately:

  • Check that appId, endpoint, and apiKey passed to NovvyAds.initialize() are correct
  • Confirm the device has an active internet connection
  • Check logcat for more detailed error output from the Novvy SDK

Assertion error at runtime #

NovvyAds.initialize() must be called before showInterstitial():

  • You called showInterstitial() or showRewarded() before initialize() completed
  • Ensure await NovvyAds.initialize(...) finishes in main() before runApp()

Android build errors #

minSdk conflict:

  • Set minSdk 24 (or higher) in android/app/build.gradle
  • Run flutter clean && flutter pub get and rebuild

Gradle sync fails:

  • Run ./gradlew dependencies in android/ to inspect the dependency tree
  • Ensure mavenCentral() is listed in your project-level repositories

iOS build errors #

Unable to find matching slice in 'ios-arm64 ios-arm64-simulator' for ... x86_64:

  • Add the EXCLUDED_ARCHS[sdk=iphonesimulator*] = x86_64 snippet to your ios/Podfile post_install (see Configure iOS)
  • After editing, run pod install from ios/ and rebuild

Module 'novvy_ads' not found:

  • This is usually the same root cause as above — the host app target is still building for x86_64. Verify the Podfile post_install hook ran successfully, and that EXCLUDED_ARCHS is set in Runner.xcodeproj Build Settings for every configuration.

App crashes immediately on NovvyAds.initialize() with __abort_with_payload / "app attempted to access privacy-sensitive data without a usage description":

  • Your ios/Runner/Info.plist is missing the NSUserTrackingUsageDescription key. Add it as shown in Configure iOS.
  • After editing the plist, do a full rebuild — flutter clean && flutter run — so the new plist is packaged into the app bundle.

Pod install fails to download NovvyAds.xcframework.zip:

  • Check internet connectivity to github.com (the prepare_command script runs curl against the GitHub Release asset)
  • Manually download from the URL printed in the prepare_command failure and place the unzipped NovvyAds.xcframework directly under the plugin's ios/ directory; subsequent pod install runs will reuse it

iOS Notes #

AdMob mediation on iOS #

The Flutter plugin links the NovvyAds Core xcframework, which routes ads directly through Novvy's own delivery stack. Unlike Android — where Google Mobile Ads SDK is bundled and AdMob mediation is transparent — iOS exposes AdMob mediation as a separate concern handled by the host app, not by this plugin.

If your iOS app already uses Google AdMob and you want Novvy to act as one of its waterfall networks, integrate the official NovvyAds/AdMob subspec in addition to this plugin:

# ios/Podfile, alongside the Flutter plugin:
pod 'NovvyAds/AdMob', :git => 'https://github.com/NovvyAI/novvy-ads-cocoapods.git', :tag => 'v1.0.0-beta.2'

Then register NovvyAdMobMediationAdapter as a Custom Event in the AdMob console for each ad unit you want Novvy to bid on. See the NovvyAds iOS SDK README for the full mediation setup.

This is independent of NovvyAds.showInterstitial(...) / showRewarded(...) calls from Dart — those always go through the Novvy direct path regardless.

Simulator architecture limitation #

The bundled NovvyAds xcframework currently ships ios-arm64 and ios-arm64-simulator slices only. Apple Silicon Macs build the simulator as arm64 and are unaffected; Intel Macs and Intel-based CI runners cannot run the iOS simulator until upstream provides an x86_64 simulator slice. Real-device builds work from any Mac.

Requirements #

  • Dart: 3.0.0+
  • Flutter: 3.10.0+
  • Android: API 24+ (Android 7.0 Nougat)
  • iOS: 13.0+ (Xcode 14.0+, Swift 5.0+)

Additional Resources #

Support #

For issues or questions:

License #

MIT License — Copyright (c) 2026 Novvy AI

0
likes
140
points
784
downloads

Documentation

API reference

Publisher

verified publishernovvy.ai

Weekly Downloads

Flutter plugin for Novvy Ads SDK.

Homepage

License

MIT (license)

Dependencies

crypto, flutter, meta, plugin_platform_interface

More

Packages that depend on novvy_ads

Packages that implement novvy_ads