Prerequisites
System Requirements
- Minimum Android SDK: API 21 (Android 5.0)
- Target Android SDK: API 35
- Kotlin: 1.9.22+
- Gradle: 8.12.3+
- Jetpack Compose: Required only for using
loadComposeNativeAd()
Required Permissions
No manual permission configuration required. The SDK automatically declares all necessary permissions, which will be merged into your app’s manifest:
android.permission.INTERNET - For network communication
android.permission.ACCESS_NETWORK_STATE - For network connectivity detection
com.google.android.gms.permission.AD_ID - For advertising identifiers
Installation
Add the Velocity Ads SDK dependency to your project’s build.gradle:
dependencies {
implementation 'io.velocity:ads-sdk:0.3.0'
}
Make sure your build.gradle includes the Maven Central repository:
repositories {
mavenCentral()
}
ProGuard/R8 Rules
The SDK includes automatic ProGuard rules via consumer-rules.pro that are automatically applied to your app. You do not need to add any ProGuard rules manually.
SDK Initialization
Initialize the SDK once at app startup in your Application class:
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
VelocityAds.setUserId("USER_ID") // Optional: User identifier
val initRequest = VelocityAdsInitRequest.Builder("YOUR_APPLICATION_KEY")
.build()
VelocityAds.initSDK(applicationContext, initRequest, object : VelocityAdsInitListener {
override fun onInitSuccess() {
Log.d("SDK", "SDK initialized successfully")
}
override fun onInitFailure(error: VelocityAdsError) {
Log.e("SDK", "Initialization failed: $error")
}
})
}
}
Important:
- ⚠️ Initialize once during app startup in
Application.onCreate()
- ⚠️ Must be called before loading any ads
- ⚠️ Always use
applicationContext, never Activity context
For best performance, call VelocityAds.setUserId() before VelocityAds.initSDK() when a user identifier is available.
Loading an Ad
Loading Native Ads
Use this path when you want raw ad data to render in your own views.
val adRequest = VelocityNativeAdRequest.Builder()
.withPrompt("What's the weather today?") // Optional: Provide for context
.withAIResponse("The weather is sunny with 72°F...") // Optional: Provide for better targeting
.withConversationHistory(conversationHistory) // Optional: Previous conversation
.withAdditionalContext("Weather Application") // Optional: Extra context
.withAdUnitId("ad_unit_123") // Optional: Ad unit identifier
.build()
val nativeAd = VelocityNativeAd(adRequest)
nativeAd.loadAd(object : VelocityNativeAdListener {
override fun onAdLoaded(nativeAd: VelocityNativeAd) {
titleTextView.text = nativeAd.data.title
descriptionTextView.text = nativeAd.data.description
ctaButton.text = nativeAd.data.callToAction
sponsorTextView.text = nativeAd.data.advertiserName
Glide.with(context).load(nativeAd.data.advertiserIconUrl).into(iconImageView)
nativeAd.data.largeImageUrl?.let { Glide.with(context).load(it).into(mainImageView) }
// Register views for automatic click and impression tracking
nativeAd.registerViewForInteraction(
adView = adContainer,
clickableViews = listOf(ctaButton, adContainer)
)
}
override fun onAdFailedToLoad(nativeAd: VelocityNativeAd, error: VelocityAdsError) {
Log.e("Ad", "Failed to load ad: $error")
}
override fun onAdImpression(nativeAd: VelocityNativeAd) {
Log.d("Ad", "Impression recorded for adId=${nativeAd.data.adId}")
}
override fun onAdClicked(nativeAd: VelocityNativeAd) {
Log.d("Ad", "Ad clicked for adId=${nativeAd.data.adId}")
}
})
Registering Views for Interaction
After rendering your ad inside onAdLoaded, call registerViewForInteraction() to let the SDK handle clicks and impressions automatically.
nativeAd.registerViewForInteraction(
adView = adContainer, // root view — used for impression visibility detection
clickableViews = listOf(ctaButton, adContainer) // views that fire onAdClicked
)
| Event | Trigger | Callback fired |
|---|
| Impression | adView reaches ≥ 50% on-screen visibility | onAdImpression + impression URL ping |
| Click | User taps any view in clickableViews | onAdClicked + opens click URL in browser |
The impression fires once per ad load. Re-registering the same VelocityNativeAd instance will not re-fire the impression.
Destroying an Ad
Call destroyAd() on each instance when the hosting Activity or Fragment is destroyed to prevent memory leaks.
class MyActivity : AppCompatActivity() {
private val loadedAds = mutableListOf<VelocityNativeAd>()
override fun onDestroy() {
super.onDestroy()
loadedAds.forEach { it.destroyAd() }
loadedAds.clear()
}
}
Loading Compose Native Ads
Use this path when you want a Jetpack Compose ad card delivered as a @Composable lambda.
var composeAdContent by remember { mutableStateOf<(@Composable () -> Unit)?>(null) }
val adViewRequest = VelocityNativeAdViewRequest.Builder(VelocityAdViewSize.M)
.withPrompt("What's the weather today?")
.withAIResponse("The weather is sunny with 72°F...")
.withAdConfiguration(AdConfiguration()) // Optional: See Ad Theming
.build()
VelocityNativeAd(adViewRequest).loadAd(object : VelocityNativeAdComposeListener {
override fun onAdLoaded(nativeAd: VelocityNativeAd, adContent: @Composable () -> Unit) {
composeAdContent = adContent
}
override fun onAdFailedToLoad(nativeAd: VelocityNativeAd, error: VelocityAdsError) {
Log.e("Ad", "Failed to load compose ad: $error")
}
override fun onAdImpression(nativeAd: VelocityNativeAd) { }
override fun onAdClicked(nativeAd: VelocityNativeAd) { }
})
// Render the ad where it should appear in your layout
composeAdContent?.invoke()
Compose ad resources are released when the composable leaves the composition. Keep the returned content in the tree while the ad should stay active.
Loading View-Based Native Ads
Use this path when your app uses the traditional Android View system (XML layouts).
val adContainer: FrameLayout = findViewById(R.id.adContainer)
val adViewRequest = VelocityNativeAdViewRequest.Builder(VelocityAdViewSize.M)
.withPrompt("What's the weather today?")
.withAIResponse("The weather is sunny with 72°F...")
.withAdConfiguration(AdConfiguration()) // Optional: See Ad Theming
.build()
VelocityNativeAd(adViewRequest).loadAd(
context = this,
listener = object : VelocityNativeAdViewListener {
override fun onAdLoaded(nativeAd: VelocityNativeAd, nativeAdView: View) {
adContainer.removeAllViews()
adContainer.addView(nativeAdView)
}
override fun onAdFailedToLoad(nativeAd: VelocityNativeAd, error: VelocityAdsError) {
Log.e("Ad", "Failed to load ad: $error")
}
override fun onAdImpression(nativeAd: VelocityNativeAd) { }
override fun onAdClicked(nativeAd: VelocityNativeAd) { }
}
)
XML Layout:
<FrameLayout
android:id="@+id/adContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
Conversation History
For better ad targeting in chat applications, provide conversation history via withConversationHistory().
// First call — no conversation history
val firstRequest = VelocityNativeAdRequest.Builder()
.withPrompt("What's the weather today?")
.withAIResponse("The weather is sunny...")
.build()
VelocityNativeAd(firstRequest).loadAd(listener)
// Subsequent calls — with conversation history
val conversationHistory = listOf(
mapOf("role" to "user", "content" to "What's the weather today?"),
mapOf("role" to "assistant", "content" to "The weather is sunny...")
)
val nextRequest = VelocityNativeAdRequest.Builder()
.withPrompt("What about tomorrow?")
.withAIResponse("Tomorrow will be cloudy...")
.withConversationHistory(conversationHistory)
.build()
VelocityNativeAd(nextRequest).loadAd(listener)
withConversationHistory accepts List<Map<String, Any?>>? and should include all prior turns.
Ad Theming
When using Compose or View-based ads, customize the card appearance via AdConfiguration.
Custom Colors
val config = AdConfiguration(
colorScheme = AdColorScheme.Builder()
.light { colors ->
colors.copy(
ctaBackground = Color(0xFF6200EE),
ctaText = Color.White,
cardBackground = Color(0xFFF5F5F5)
)
}
.dark { colors ->
colors.copy(
ctaBackground = Color(0xFF9E50EE),
ctaText = Color.White,
cardBackground = Color(0xFF1E1E1E)
)
}
.build()
)
Custom Typography
val config = AdConfiguration(
adTypography = AdTypography.Builder(selectedSize = VelocityAdViewSize.M)
.title { base -> base.copy(fontSize = 18.sp, fontWeight = FontWeight.Bold) }
.description { base -> base.copy(fontSize = 14.sp) }
.ctaButton { base -> base.copy(fontWeight = FontWeight.SemiBold) }
.build()
)
Dark Theme Override
val 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(flag: Boolean)
Parameters:
flag (Boolean) - 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(flag: Boolean)
Parameters:
flag (Boolean) - 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 ChatActivity : AppCompatActivity() {
private val conversationHistory = mutableListOf<Map<String, Any>>()
private val loadedAds = mutableListOf<VelocityNativeAd>()
private fun onUserMessage(userMessage: String) {
getAIResponse(userMessage) { aiResponse ->
displayMessage(aiResponse)
loadContextualAd(userMessage, aiResponse)
conversationHistory.add(mapOf("role" to "user", "content" to userMessage))
conversationHistory.add(mapOf("role" to "assistant", "content" to aiResponse))
}
}
private fun loadContextualAd(prompt: String, aiResponse: String?) {
val historyToPass = conversationHistory.takeIf { it.isNotEmpty() }?.toList()
val adRequest = VelocityNativeAdRequest.Builder()
.withPrompt(prompt)
.withAIResponse(aiResponse)
.withConversationHistory(historyToPass)
.build()
val nativeAd = VelocityNativeAd(adRequest)
nativeAd.loadAd(object : VelocityNativeAdListener {
override fun onAdLoaded(nativeAd: VelocityNativeAd) {
loadedAds.add(nativeAd)
val adView = displayAdManually(nativeAd.data, chatContainer)
nativeAd.registerViewForInteraction(
adView = adView,
clickableViews = listOf(adView)
)
}
override fun onAdFailedToLoad(nativeAd: VelocityNativeAd, error: VelocityAdsError) {
Log.e("Chat", "Ad load failed: $error")
}
override fun onAdImpression(nativeAd: VelocityNativeAd) { }
override fun onAdClicked(nativeAd: VelocityNativeAd) { }
})
}
override fun onDestroy() {
super.onDestroy()
loadedAds.forEach { it.destroyAd() }
loadedAds.clear()
}
}
Advertising Identifiers
The Velocity Ads SDK automatically accesses Google Advertising ID (GAID) and App Set ID when available. The com.google.android.gms.permission.AD_ID permission is included automatically — no changes to your AndroidManifest.xml are needed.
Opting out of advertising identifiers will significantly reduce ad performance and revenue. Only do so if you have specific privacy requirements.
To opt out, add this to your 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