Skip to main content

Prerequisites

System Requirements
  • Flutter SDK: 3.24.0+
  • Dart SDK: 3.5.0+
  • Android: API 21+ (Android 5.0)
  • iOS: 13.0+
  • Xcode: 16.0+
iOS Setup CocoaPods (default): No additional setup needed. The plugin resolves automatically when you run pod install. Swift Package Manager (optional): If your project uses SPM instead of CocoaPods, enable it in Flutter:
flutter config --enable-swift-package-manager
Required Permissions No manual permission configuration required. The SDK automatically declares all necessary permissions:
  • android.permission.INTERNET - For network communication
  • com.google.android.gms.permission.AD_ID - For advertising identifiers

Installation

Add to your app’s pubspec.yaml:
dependencies:
  velocity_ads: ^0.3.0
Then run:
flutter pub get

SDK Initialization

VelocityAds uses different application keys for iOS and Android. Always pass the key that matches the platform where the app is currently running.
import 'dart:io';
import 'package:flutter/widgets.dart';
import 'package:velocity_ads/velocity_ads.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  const iosAppKey = 'your-ios-app-key';
  const androidAppKey = 'your-android-app-key';
  final appKey = Platform.isIOS ? iosAppKey : androidAppKey;

  // Optional: set user ID before initialization
  VelocityAds.setUserId('user_12345');

  await VelocityAds.initialize(appKey: appKey);

  runApp(const MyApp());
}
Important:
  • ⚠️ Initialize once during app startup in main() before runApp()
  • ⚠️ Must be called before loading any ads
  • ⚠️ Call setUserId() before initialize() if a user identifier is available at startup

Advanced Initialization with Error Handling

import 'dart:io';
import 'package:flutter/widgets.dart';
import 'package:velocity_ads/velocity_ads.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  const iosAppKey = 'your-ios-app-key';
  const androidAppKey = 'your-android-app-key';
  final appKey = Platform.isIOS ? iosAppKey : androidAppKey;

  try {
    VelocityAds.setUserId('user_12345');
    await VelocityAds.initialize(appKey: appKey);
    print('VelocityAds initialized successfully');
  } on VelocityAdsError catch (e) {
    print('VelocityAds initialization failed: [${e.code}] ${e.message}');
  }

  runApp(MyApp());
}

Loading Native Ads

The VelocityAds plugin uses an instance-based pattern: create a request, pass it to a VelocityNativeAd, and call load().
import 'package:velocity_ads/velocity_ads.dart';

Future<void> loadAd() async {
  final request = VelocityNativeAdRequest(
    prompt: 'What are the best running shoes?',
    aiResponse: 'Here are some great running shoe options...',
    adUnitId: 'home-screen-ad',
  );

  final ad = VelocityNativeAd(request);

  try {
    await ad.load();
    print('Ad loaded: ${ad.data?.title}');
  } catch (e) {
    print('Failed to load ad: $e');
  }
}
After load() completes, ad creative data is available via ad.data (a NativeAdData object) with fields: adId, title, description, callToAction, advertiserName, sponsoredLabel, badgeLabel, advertiserIconUrl, largeImageUrl, squareImageUrl, clickUrl, and impressionUrl.

Building Custom Ad UI

Widget buildCustomAdUI(VelocityNativeAd ad) {
  final data = ad.data;
  if (data == null) return const SizedBox.shrink();

  return Card(
    child: Column(
      children: [
        Text('Sponsored by ${data.advertiserName}',
            style: TextStyle(fontSize: 12, color: Colors.grey)),
        if (data.largeImageUrl != null)
          Image.network(data.largeImageUrl!, fit: BoxFit.cover),
        Padding(
          padding: EdgeInsets.all(12),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text(data.title,
                  style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
              SizedBox(height: 8),
              Text(data.description, style: TextStyle(fontSize: 14)),
              SizedBox(height: 12),
              ElevatedButton(
                onPressed: () => launchUrl(Uri.parse(data.clickUrl)),
                child: Text(data.callToAction),
              ),
            ],
          ),
        ),
      ],
    ),
  );
}

Conversation History

For better ad targeting in chat applications, provide conversation history:
final conversationHistory = [
  ConversationMessage.user('What are the best running shoes?'),
  ConversationMessage.assistant('Here are some great running shoe options...'),
];

final request = VelocityNativeAdRequest(
  prompt: 'Which ones are best for marathons?',
  aiResponse: 'For marathons, I recommend...',
  conversationHistory: conversationHistory,
  additionalContext: 'User is training for their first marathon',
  adUnitId: 'chat-ad',
);

final ad = VelocityNativeAd(request);
try {
  await ad.load();
} catch (e) {
  print('Failed to load ad: $e');
}
conversationHistory accepts a list of ConversationMessage objects. Providing full conversation history improves contextual ad targeting.

Event Tracking

Set a VelocityNativeAdListener on the ad instance before calling load() to receive lifecycle events:
class MyAdListener extends VelocityNativeAdListener {
  @override
  void onAdLoaded(VelocityNativeAd ad) {
    print('Ad loaded: ${ad.data?.title}');
  }

  @override
  void onAdFailedToLoad(VelocityNativeAd ad, VelocityAdsError error) {
    print('Ad failed: [${error.code}] ${error.message}');
  }

  @override
  void onAdImpression(VelocityNativeAd ad) {
    print('Impression: ${ad.adId}');
  }

  @override
  void onAdClicked(VelocityNativeAd ad) {
    print('Clicked: ${ad.adId}');
  }
}

// Attach before loading
final ad = VelocityNativeAd(request);
ad.listener = MyAdListener();
await ad.load();
CallbackWhen it fires
onAdLoadedAd data has been fetched successfully
onAdFailedToLoadThe ad request failed
onAdImpressionThe ad becomes visible to the user
onAdClickedThe user taps the ad

Destroying an Ad

Always call destroy() when an ad is no longer needed to release native resources:
@override
void dispose() {
  ad.destroy();
  super.dispose();
}

Multiple Concurrent Ads

Each VelocityNativeAd instance is independent and can be loaded concurrently:
final ad1 = VelocityNativeAd(VelocityNativeAdRequest(prompt: 'Running shoes', adUnitId: 'slot-1'));
final ad2 = VelocityNativeAd(VelocityNativeAdRequest(prompt: 'Running accessories', adUnitId: 'slot-2'));

await Future.wait([ad1.load(), ad2.load()]);

// Clean up both when done
ad1.destroy();
ad2.destroy();

Loading Native Ad Views

Use VelocityNativeAdViewRequest when you want the SDK to render the ad layout automatically. The SDK handles impression tracking and click handling.
VelocityNativeAd? _ad;

Future<void> loadAdView() async {
  final viewRequest = VelocityNativeAdViewRequest(
    size: VelocityAdViewSize.m,
    prompt: 'What are the best running shoes?',
    aiResponse: 'Here are some great running shoe options...',
  );

  _ad = VelocityNativeAd(viewRequest);
  _ad!.viewListener = MyAdViewListener(
    onLoaded: (ad, adView) => setState(() {}),
    onFailed: (ad, error) => print('Failed: [${error.code}] ${error.message}'),
  );

  try {
    await _ad!.load();
  } catch (e) {
    print('Failed: $e');
  }
}

@override
Widget build(BuildContext context) {
  return Column(
    children: [
      // ... your content ...
      if (_ad?.widget != null) _ad!.widget!,
    ],
  );
}

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

Ad View Sizes

SizeHeightUse Case
VelocityAdViewSize.s50dpCompact banner-style ads
VelocityAdViewSize.m100dpStandard inline ads
VelocityAdViewSize.l300dpRich media ads with images

Ad Theming

Customize the appearance of SDK-rendered ad views via AdConfiguration. Custom Colors
final viewRequest = VelocityNativeAdViewRequest(
  size: VelocityAdViewSize.l,
  adConfiguration: AdConfiguration(
    colorScheme: AdColorScheme(
      light: AdColors.lightDefaults().copyWith(
        ctaBackground: Color(0xFF6200EE),
        ctaText: Color(0xFFFFFFFF),
        cardBackground: Color(0xFFF8F8F8),
      ),
      dark: AdColors.darkDefaults().copyWith(
        ctaBackground: Color(0xFF9E50EE),
        ctaText: Color(0xFFFFFFFF),
      ),
    ),
  ),
  prompt: 'Best running shoes for marathons',
);
Custom Typography
final config = AdConfiguration(
  adTypography: AdTypography(
    title: AdTextStyle(fontSize: 18, fontWeight: AdFontWeight.bold),
    description: AdTextStyle(fontSize: 14),
    ctaButton: AdTextStyle(fontSize: 16, fontWeight: AdFontWeight.semiBold),
    brandName: AdTextStyle(fontSize: 14, fontWeight: AdFontWeight.medium),
    sponsoredLabel: AdTextStyle(fontSize: 12),
    sponsoredBadgeText: AdTextStyle(fontSize: 12, fontWeight: AdFontWeight.medium),
  ),
);
Dark Theme Override
final config = AdConfiguration(
  darkTheme: false,  // Force light mode
  // darkTheme: true   // Force dark mode
  // darkTheme: null   // Follow system (default)
);

GDPR

If your app serves users in the European Economic Area (EEA), UK, or other regions where GDPR applies, you must obtain user consent before processing their personal data. Method: VelocityAds.setConsent(bool flag) Parameters:
  • flag (bool) - true if the user gives consent for data processing, false if the user denies consent
// User gives consent (GDPR)
VelocityAds.setConsent(true);

// User denies consent
VelocityAds.setConsent(false);
Geography-Specific: Only call this API in regions where GDPR regulations apply.

CCPA

If your app serves users in regions where CCPA applies, you must provide a way for users to opt out of the sale of their personal information. Method: VelocityAds.setDoNotSell(bool flag) Parameters:
  • flag (bool) - true if the user opts out of the sale of personal information, false if the user allows data sharing
// User opts out of data sale (CCPA)
VelocityAds.setDoNotSell(true);

// User allows data sharing
VelocityAds.setDoNotSell(false);
Geography-Specific: Only call this API in regions where CCPA regulations apply.

Ad Load Example

This example shows how to integrate the SDK in a chat application with conversation history for better ad targeting.
class ChatScreen extends StatefulWidget {
  @override
  State<ChatScreen> createState() => _ChatScreenState();
}

class _ChatScreenState extends State<ChatScreen> {
  final List<ConversationMessage> _conversationHistory = [];
  final List<Widget> _messages = [];
  final List<VelocityNativeAd> _loadedAds = [];

  Future<void> _onUserMessage(String userMessage) async {
    final aiResponse = await getAIResponse(userMessage);

    setState(() {
      _messages.add(Text(userMessage));
      _messages.add(Text(aiResponse));
    });

    _conversationHistory.add(ConversationMessage.user(userMessage));
    _conversationHistory.add(ConversationMessage.assistant(aiResponse));

    await _loadContextualAd(userMessage, aiResponse);
  }

  Future<void> _loadContextualAd(String prompt, String aiResponse) async {
    final request = VelocityNativeAdRequest(
      prompt: prompt,
      aiResponse: aiResponse,
      conversationHistory: _conversationHistory.isEmpty ? null : _conversationHistory,
    );

    final ad = VelocityNativeAd(request);
    ad.listener = _ChatAdListener(
      onLoaded: (ad) {
        setState(() {
          _messages.add(buildCustomAdUI(ad));
        });
      },
      onFailed: (ad, error) {
        print('Ad failed: [${error.code}] ${error.message}');
      },
    );

    try {
      await ad.load();
      _loadedAds.add(ad);
    } catch (e) {
      // onAdFailedToLoad is also called on the listener
    }
  }

  @override
  void dispose() {
    for (final ad in _loadedAds) {
      ad.destroy();
    }
    super.dispose();
  }
}

Advertising Identifiers

The Velocity Ads SDK automatically accesses Google Advertising ID (GAID) and App Set ID on Android when available. On iOS, the SDK uses IDFA and IDFV when the user has granted App Tracking Transparency (ATT) permission.
Opting out of advertising identifiers will significantly reduce ad performance and revenue. Only do so if you have specific privacy requirements.
To opt out on Android, add this to android/app/src/main/AndroidManifest.xml:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:tools="http://schemas.android.com/tools">
    <uses-permission
        android:name="com.google.android.gms.permission.AD_ID"
        tools:node="remove"/>
</manifest>
These identifiers enable:
  • Better ad targeting - More relevant ads for your users
  • Improved analytics - Better campaign performance measurement
  • Higher revenue - Increased eCPM through better targeting