Skip to main content

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
)
EventTriggerCallback fired
ImpressionadView reaches ≥ 50% on-screen visibilityonAdImpression + impression URL ping
ClickUser taps any view in clickableViewsonAdClicked + 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