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:
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();
| Callback | When it fires |
|---|
onAdLoaded | Ad data has been fetched successfully |
onAdFailedToLoad | The ad request failed |
onAdImpression | The ad becomes visible to the user |
onAdClicked | The 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
| Size | Height | Use Case |
|---|
VelocityAdViewSize.s | 50dp | Compact banner-style ads |
VelocityAdViewSize.m | 100dp | Standard inline ads |
VelocityAdViewSize.l | 300dp | Rich 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