A modern, type-safe Kotlin SDK for the VTEX Ads API. Built with Kotlin Coroutines for async operations and designed to work seamlessly with Android, server-side Kotlin, and other JVM platforms.
- β Type-safe API - Specialized builders with compile-time validation
- β Kotlin Coroutines - Non-blocking async operations
- β Cross-platform - Android, JVM backend, and other Kotlin platforms
- β Smart defaults - Sensible configurations out of the box
- β Comprehensive - Ad queries, event tracking, and order conversion
- β Well-tested - 146+ tests with high coverage
- β Easy to use - Intuitive API with clear documentation
dependencies {
implementation("com.vtex.ads:vtex-ads-sdk-kotlin:0.1.0-SNAPSHOT")
}
dependencies {
implementation 'com.vtex.ads:vtex-ads-sdk-kotlin:0.1.0-SNAPSHOT'
}
import com.vtex.ads.sdk.*
import com.vtex.ads.sdk.models.*
import kotlinx.coroutines.runBlocking
fun main() = runBlocking {
// 1. Configure the SDK with dynamic session and user ID providers
val client = VtexAdsClient(
publisherId = "your-publisher-id",
sessionIdProvider = { getCurrentSessionId() }, // Called on each request
userIdProvider = { getCurrentUserId() }, // Called on each request
channel = Channel.SITE
)
// 2. Query ads for home page
val ads = client.ads.getHomeAds(
placements = mapOf(
"home_banner" to BannerPlacementRequest.image(5, "desktop"),
"home_products" to ProductPlacementRequest.unique(10)
)
)
// 3. Send impression events
ads.getAllAds().forEach { ad ->
client.events.deliveryBeaconEvent(ad.impressionUrl)
}
// 4. Send click event when user clicks
client.events.deliveryBeaconEvent(ads.getAllAds().first().clickUrl)
}
- Installation
- Quick Start
- Configuration
- Ad Types
- Querying Ads
- Placement Builders
- Event Tracking
- Order Conversion
- Android Integration
- Best Practices
- API Reference
val client = VtexAdsClient(
publisherId = "your-publisher-id", // Required
sessionIdProvider = { getCurrentSessionId() }, // Required - called on each request
userIdProvider = { getCurrentUserId() }, // Optional - called on each request
channel = Channel.SITE // SITE, MSITE, or APP
)
val config = VtexAdsClientConfig(
publisherId = "your-publisher-id",
sessionIdProvider = { getCurrentSessionId() },
userIdProvider = { getCurrentUserId() },
channel = Channel.SITE,
brand = "your-brand", // For multi-brand publishers
timeout = 500L, // Request timeout in ms (default: 500ms)
maxRetries = 3, // Max retry attempts (default: 3)
retryDelayMs = 100L // Delay between retries (default: 100ms)
)
val client = VtexAdsClient(config)
For applications that need static values, you can use the backward compatibility method:
val client = VtexAdsClient.createWithStaticValues(
publisherId = "your-publisher-id",
sessionId = "static-session-id",
userId = "static-user-id",
channel = Channel.SITE
)
The SDK now supports dynamic session and user ID providers, which are called on each request. This is useful when these values can change during navigation:
val client = VtexAdsClient(
publisherId = "pub-123",
sessionIdProvider = {
// This function is called on each request
// Return the current session ID
getCurrentSessionId()
},
userIdProvider = {
// This function is called on each request
// Return the current user ID (can be null for anonymous users)
getCurrentUserId()
},
channel = Channel.SITE
)
The SDK supports four types of ads:
Type | Description | Use Case |
---|---|---|
Product Ads | Individual product placements | Product recommendations, sponsored products |
Banner Ads | Image or video banners | Hero banners, promotional content |
Sponsored Brand Ads | Brand showcase with products | Brand campaigns, category takeovers |
Digital Signage Ads | In-store digital displays | Physical store displays |
Query ads for your home page:
val ads = client.ads.getHomeAds(
placements = mapOf(
"home_banner_top" to BannerPlacementRequest.image(1, "desktop"),
"home_products_shelf" to ProductPlacementRequest.unique(10),
"home_sponsored_brands" to SponsoredBrandPlacementRequest.imageAndVideo(5)
)
)
// Access ads by placement
val banners = ads.getPlacement("home_banner_top")
val products = ads.getPlacement("home_products_shelf")
Query ads for search results:
val ads = client.ads.getSearchAds(
term = "smartphone",
placements = mapOf(
"search_banner" to BannerPlacementRequest.video(1, "720p", VideoSize.P720),
"search_products" to ProductPlacementRequest.unique(5)
)
)
Query ads for category pages:
val ads = client.ads.getCategoryAds(
categoryName = "Electronics > Smartphones",
placements = mapOf(
"category_banner" to BannerPlacementRequest.image(1, "desktop"),
"category_products" to ProductPlacementRequest.unique(8)
)
)
Query ads for product detail pages:
val ads = client.ads.getProductPageAds(
productSku = "PROD-123",
placements = mapOf(
"pdp_related_products" to ProductPlacementRequest.unique(4)
)
)
All query methods support additional parameters:
val ads = client.ads.getHomeAds(
placements = placements,
// Segmentation (targeting)
segmentation = listOf(
Segmentation(key = "STATE", values = listOf("SP", "RJ")),
Segmentation(key = "GENDER", values = listOf("M")),
Segmentation(key = "AUDIENCES", values = listOf("high_value_customers"))
),
// Tags for filtering
tags = listOf("electronics", "premium"),
// Deduplication options
dedupCampaignAds = true, // Remove duplicate ads from same campaign
dedupAds = true // Remove duplicate ads across campaigns
)
The SDK provides specialized builders for each ad type, offering type safety and validation.
Image Banners:
// Builder pattern
val banner = BannerPlacementRequest.builder()
.quantity(5)
.image("desktop")
.build()
// Factory method (concise)
val banner = BannerPlacementRequest.image(5, "desktop")
Video Banners:
// Builder pattern
val banner = BannerPlacementRequest.builder()
.quantity(3)
.video("720p", VideoSize.P720)
.build()
// Factory method
val banner = BannerPlacementRequest.video(3, "720p", VideoSize.P720)
Mixed (Image + Video):
val banner = BannerPlacementRequest.builder()
.quantity(10)
.imageAndVideo("desktop", VideoSize.P1080)
.build()
Video Size Options:
VideoSize.P1080 // 1920x1080 - Full screen only
VideoSize.P720 // 1280x720 - Full screen only
VideoSize.P480 // 854x480
VideoSize.P360 // 640x360
VideoSize.P320 // 568x320 - Mobile recommended
Unique Products (no duplicates):
// Builder pattern
val products = ProductPlacementRequest.builder()
.quantity(10)
.uniqueSkus() // Each SKU appears at most once
.build()
// Factory method
val products = ProductPlacementRequest.unique(10)
With Duplicates Allowed:
// Builder pattern
val products = ProductPlacementRequest.builder()
.quantity(15)
.allowSkuDuplications(true)
.build()
// Factory method
val products = ProductPlacementRequest.create(15, allowSkuDuplications = true)
Image Assets:
// Builder pattern
val sponsored = SponsoredBrandPlacementRequest.builder()
.quantity(5)
.withImageAssets()
.build()
// Factory method
val sponsored = SponsoredBrandPlacementRequest.image(5)
Video Assets:
// Builder pattern
val sponsored = SponsoredBrandPlacementRequest.builder()
.quantity(3)
.withVideoAssets(VideoSize.P720)
.build()
// Factory method
val sponsored = SponsoredBrandPlacementRequest.video(3, VideoSize.P720)
Mixed Assets (Image + Video):
// Builder pattern
val sponsored = SponsoredBrandPlacementRequest.builder()
.quantity(8)
.withImageAndVideoAssets(VideoSize.P1080)
.build()
// Factory method
val sponsored = SponsoredBrandPlacementRequest.imageAndVideo(8, VideoSize.P1080)
For advanced use cases, the generic builder is still available:
val placement = PlacementRequest.builder()
.quantity(5)
.types(AdType.BANNER)
.assetsType(AssetType.VIDEO)
.videoSize(VideoSize.P720)
.allowSkuDuplications(false)
.build()
The SDK provides a unified deliveryBeaconEvent
method for sending all types of ad interaction events (impressions, views, and clicks).
Send when an ad is displayed to the user:
// Single ad
client.events.deliveryBeaconEvent(ad.impressionUrl)
// Multiple ads
ads.getAllAds().forEach { ad ->
client.events.deliveryBeaconEvent(ad.impressionUrl)
}
Send when an ad is actually viewed (e.g., scrolled into viewport):
client.events.deliveryBeaconEvent(ad.viewUrl)
Send when a user clicks on an ad:
client.events.deliveryBeaconEvent(ad.clickUrl)
Events are automatically deduplicated by the API:
- Impressions: 1 minute
- Clicks: 1 hour
- Conversions: Not deduplicated (except same order_id within 30 days)
Track purchases to measure ad effectiveness.
val order = Order.builder()
.orderId("order-123")
.addItem("SKU-1", quantity = 2, price = 99.99)
.addItem("SKU-2", quantity = 1, price = 149.99)
.customerEmail("[email protected]")
.customerPhone("11999999999")
.customerDocument("12345678900") // CPF/CNPJ
.customerFirstName("John")
.customerLastName("Doe")
.state("SP")
.city("SΓ£o Paulo")
.gender("M") // M, F, or O
.isCompany(false)
.build()
client.deliveryOrderEvent(order) { success ->
if (success) {
println("Order tracked successfully!")
}
}
class CheckoutViewModel(
private val adsClient: VtexAdsClient
) : ViewModel() {
fun completeOrder(checkoutOrder: CheckoutOrder) {
viewModelScope.launch {
try {
// Convert to SDK order format
val adsOrder = Order.builder()
.orderId(checkoutOrder.id)
.createdAt(checkoutOrder.createdAt)
.customerEmail(checkoutOrder.customer.email)
.customerPhone(checkoutOrder.customer.phone)
.customerDocument(checkoutOrder.customer.document)
.customerFirstName(checkoutOrder.customer.firstName)
.customerLastName(checkoutOrder.customer.lastName)
.state(checkoutOrder.shippingAddress.state)
.city(checkoutOrder.shippingAddress.city)
.gender(checkoutOrder.customer.gender)
.isCompany(checkoutOrder.customer.isCompany)
.apply {
checkoutOrder.items.forEach { item ->
addItem(
productSku = item.sku,
quantity = item.quantity,
price = item.unitPrice, // NOT multiplied by quantity
sellerId = item.sellerId
)
}
}
.build()
// Track order conversion (non-blocking)
adsClient.deliveryOrderEvent(adsOrder) { success ->
if (success) {
Log.d("Checkout", "Order conversion tracked")
} else {
Log.w("Checkout", "Failed to track conversion")
}
}
// Continue with checkout flow
navigateToSuccess()
} catch (e: Exception) {
handleError(e)
}
}
}
}
- Customer data is automatically hashed with SHA-256
- Price should be unit price, not total (quantity is separate)
- Fire-and-forget: Event sending doesn't block the UI
- Conversion window: 14 days (orders tracked within this period)
class ProductListViewModel(
private val adsClient: VtexAdsClient
) : ViewModel() {
private val _ads = MutableLiveData<AdsResponse>()
val ads: LiveData<AdsResponse> = _ads
fun loadAds(searchTerm: String) {
viewModelScope.launch {
try {
val response = adsClient.ads.getSearchAds(
term = searchTerm,
placements = mapOf(
"search_products" to ProductPlacementRequest.unique(10),
"search_banner" to BannerPlacementRequest.image(1, "mobile")
)
)
_ads.value = response
// Track impressions
response.getAllAds().forEach { ad ->
adsClient.events.deliveryBeaconEvent(ad.impressionUrl)
}
} catch (e: Exception) {
// Handle error
Log.e("ProductList", "Failed to load ads", e)
}
}
}
fun trackAdClick(ad: Ad) {
adsClient.events.deliveryBeaconEvent(ad.clickUrl)
}
}
@Composable
fun AdBanner(ad: Ad.BannerAd, adsClient: VtexAdsClient) {
val context = LocalContext.current
AsyncImage(
model = ad.mediaUrl,
contentDescription = "Advertisement",
modifier = Modifier
.fillMaxWidth()
.height(200.dp)
.clickable {
// Track click event
adsClient.events.deliveryBeaconEvent(ad.clickUrl)
// Navigate to destination
ad.destinationUrl?.let { url ->
context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
}
}
)
// Track view when ad becomes visible
LaunchedEffect(ad.adId) {
adsClient.events.deliveryBeaconEvent(ad.viewUrl)
}
}
@Composable
fun ProductAdItem(ad: Ad.ProductAd, adsClient: VtexAdsClient, onClick: () -> Unit) {
Card(
modifier = Modifier
.fillMaxWidth()
.clickable {
adsClient.events.deliveryBeaconEvent(ad.clickUrl)
onClick()
}
) {
Column {
// Product image, title, price, etc.
Text("SKU: ${ad.productSku}")
Text("Sponsored") // Ad indicator
}
}
LaunchedEffect(ad.adId) {
adsClient.events.deliveryBeaconEvent(ad.impressionUrl)
}
}
- Unique per user: Generate a unique session ID for each user
- Persistent: Should last at least 14 days (conversion window)
- Ideally never expires: Use a UUID stored in user preferences
- Consistent: Same session ID throughout navigation
// Example: Android SharedPreferences
fun getOrCreateSessionId(context: Context): String {
val prefs = context.getSharedPreferences("vtex_ads", Context.MODE_PRIVATE)
var sessionId = prefs.getString("session_id", null)
if (sessionId == null) {
sessionId = UUID.randomUUID().toString()
prefs.edit().putString("session_id", sessionId).apply()
}
return sessionId
}
Use a consistent naming pattern: {channel}_{context}_{position}_{type}
val placements = mapOf(
"site_home_middle_banner" to BannerPlacementRequest.image(1, "desktop"),
"msite_search_top-shelf_product" to ProductPlacementRequest.unique(5),
"app_category_bottom-vitrine_sponsored_brand" to SponsoredBrandPlacementRequest.image(3)
)
try {
val ads = client.ads.getHomeAds(placements)
} catch (e: VtexAdsException) {
when (e) {
is VtexAdsAuthenticationException -> {
// Invalid publisher ID or authentication failed
Log.e("Ads", "Authentication error: ${e.message}")
}
is VtexAdsValidationException -> {
// Invalid request parameters
Log.e("Ads", "Validation error: ${e.message}")
}
is VtexAdsNetworkException -> {
// Network connectivity issues
Log.e("Ads", "Network error: ${e.message}")
}
is VtexAdsRateLimitException -> {
// Rate limit exceeded, retry after e.retryAfter seconds
Log.w("Ads", "Rate limited, retry after ${e.retryAfter}s")
}
else -> {
// Other errors
Log.e("Ads", "Unexpected error: ${e.message}")
}
}
}
- Default: 500ms (recommended for API guidelines)
- Max: 10 seconds
- Configure based on your use case:
val config = VtexAdsClientConfig(
publisherId = "pub-id",
sessionId = "session-id",
channel = Channel.SITE,
timeout = 500L, // Fast for real-time queries
maxRetries = 3, // Retry on network errors
retryDelayMs = 100L // Short delay between retries
)
β Recommended (Type-safe, validated):
val banner = BannerPlacementRequest.video(3, "720p", VideoSize.P720)
val products = ProductPlacementRequest.unique(10)
val sponsored = SponsoredBrandPlacementRequest.imageAndVideo(5)
β Not Recommended (Generic, error-prone):
val banner = PlacementRequest.builder()
.quantity(3)
.types(AdType.BANNER) // Easy to forget
.assetsType(AssetType.VIDEO)
.size("720p") // No validation
.build()
O SDK oferece funcionalidades de debug opcionais e retrocompatΓveis para ajudar no desenvolvimento e troubleshooting. Por padrΓ£o, nenhum log Γ© emitido.
import com.vtex.ads.sdk.*
// Sem debug (comportamento padrΓ£o - igual ao atual)
val client = VtexAdsClient(
publisherId = "your-publisher-id",
sessionIdProvider = { getCurrentSessionId() },
userIdProvider = { getCurrentUserId() },
channel = Channel.SITE
)
// Com debug habilitado
val client = VtexAdsClient(
publisherId = "your-publisher-id",
sessionIdProvider = { getCurrentSessionId() },
userIdProvider = { getCurrentUserId() },
channel = Channel.SITE,
debug = debugOf(VtexAdsDebug.EVENTS_ALL, VtexAdsDebug.ADS_LOAD),
debugFunction = { label, message -> android.util.Log.d(label, message) }
)
Categoria | DescriΓ§Γ£o | Exemplo de Uso |
---|---|---|
EVENTS_ALL |
Todos os eventos de interaΓ§Γ£o com anΓΊncios | Logs de impression, view, click e conversion |
EVENTS_IMPRESSION |
Eventos de impressΓ£o (quando anΓΊncios sΓ£o exibidos) | delivery_beacon_event success adId=123... |
EVENTS_VIEW |
Eventos de visualizaΓ§Γ£o (quando anΓΊncios sΓ£o vistos) | delivery_beacon_event success adId=123... |
EVENTS_CLICK |
Eventos de clique (quando usuΓ‘rios clicam em anΓΊncios) | delivery_beacon_event success adId=123... |
EVENTS_CONVERSION |
Eventos de conversΓ£o (quando pedidos sΓ£o completados) | send_conversion success orderId=456... |
ADS_LOAD |
Carregamento de anΓΊncios (sucesso e erro) | ads_load success requestId=req_123... |
val client = VtexAdsClient(
publisherId = "your-publisher-id",
sessionIdProvider = { getCurrentSessionId() },
userIdProvider = { getCurrentUserId() },
channel = Channel.SITE,
debug = debugOf(VtexAdsDebug.EVENTS_VIEW),
debugFunction = { label, message -> android.util.Log.d(label, message) }
)
val client = VtexAdsClient(
publisherId = "your-publisher-id",
sessionIdProvider = { getCurrentSessionId() },
userIdProvider = { getCurrentUserId() },
channel = Channel.SITE,
debug = debugOf(VtexAdsDebug.EVENTS_ALL, VtexAdsDebug.ADS_LOAD),
debugFunction = { label, message -> android.util.Log.d(label, message) }
)
val client = VtexAdsClient(
publisherId = "your-publisher-id",
sessionIdProvider = { getCurrentSessionId() },
userIdProvider = { getCurrentUserId() },
channel = Channel.SITE,
debug = debugOf(VtexAdsDebug.ADS_LOAD),
debugFunction = { label, message ->
logger.info("[$label] $message")
}
)
impression success adId=ad-123 placement=home.hero requestId=2d8a63ad-a885-4d1d-87e1-794120a8c521 campaignId=e48f7340-f123-46d8-8fa1-4e09454239e5 adType=banner pname=home_top_banner context=home channel=app adSize=mobile requestedAt=1760616781024
view success adId=ad-456 placement=search.top requestId=req-456 campaignId=camp-789 adType=product context=search channel=site
click success adId=ad-789 placement=category.banner adType=banner context=category channel=app
impression error adId=ad-123 placement=home.hero requestId=req-123 reason=network_error
conversion success orderId=order-123 userId=user-456 items=3
conversion error orderId=order-123 userId=user-456 reason=network_error
ads_load success requestId=req-123 status=200 latencyMs=150 count=5 context=HOME channel=SITE placements=2 userId=user-456 sessionId=session-789 types={PRODUCT=3, BANNER=2} returnedPlacements=home.hero,home.products segmentation=GENDER,AGE tagsCount=2 dedupCampaign=true dedupAds=false adIds=ad-123,ad-456,ad-789,ad-101,ad-202 responseSize=2048
ads_load error requestId=req-123 status=500 latencyMs=200 context=SEARCH channel=APP placements=1 userId=user-456 sessionId=session-789 cause=IOException: timeout
ads_load error requestId=req-123 status=parse_error latencyMs=100 context=CATEGORY channel=SITE placements=3 userId=user-456 sessionId=session-789 cause=VtexAdsException: Failed to parse response
// Criar conjunto de categorias de debug
val debugCategories = debugOf(
VtexAdsDebug.EVENTS_ALL,
VtexAdsDebug.ADS_LOAD
)
// Ou usar categorias especΓficas
val specificDebug = debugOf(VtexAdsDebug.EVENTS_IMPRESSION)
- Retrocompatibilidade: Sem configuraΓ§Γ£o de debug, nenhum log Γ© emitido (comportamento atual mantido)
- Performance: As mensagens de log sΓ£o avaliadas de forma lazy - se o debug estiver desabilitado, a string da mensagem nΓ£o Γ© construΓda
- Zero Overhead: Quando debug estΓ‘ desabilitado (
emptySet()
), nΓ£o hΓ‘ nenhum processamento adicional:- Parsing de URLs nΓ£o Γ© executado
- Agrupamento de anΓΊncios por tipo nΓ£o Γ© feito
- Processamento de segmentaΓ§Γ£o nΓ£o Γ© realizado
- ConstruΓ§Γ£o de strings de log nΓ£o acontece
- SeguranΓ§a: ExceΓ§Γ΅es na funΓ§Γ£o de debug nunca quebram a aplicaΓ§Γ£o
- Flexibilidade: A funΓ§Γ£o de debug Γ© injetΓ‘vel, permitindo integraΓ§Γ£o com qualquer sistema de logging
NΓO RECOMENDAMOS o uso do sistema de debug em produΓ§Γ£o pelos seguintes motivos:
- Performance: Logs detalhados podem impactar a performance da aplicaΓ§Γ£o
- Privacidade: Logs contΓͺm dados de usuΓ‘rios (userId, sessionId, segmentation, tags) que devem ser protegidos
- Volume: Logs extensos podem gerar grande volume de dados em produΓ§Γ£o
- Compliance: Dados pessoais em logs podem violar regulamentaΓ§Γ΅es como LGPD/GDPR
Use apenas em desenvolvimento e testes para debugging e troubleshooting.
Main client for interacting with the VTEX Ads API.
class VtexAdsClient(config: VtexAdsClientConfig)
// Properties
val ads: AdsService // Ad querying service
val events: EventService // Event tracking service
// Methods
fun updateUserId(userId: String?)
fun getCurrentUserId(): String?
Service for querying ads.
interface AdsService {
suspend fun getHomeAds(
placements: Map<String, PlacementRequest>,
segmentation: List<Segmentation>? = null,
tags: List<String>? = null,
dedupCampaignAds: Boolean = false,
dedupAds: Boolean = false
): AdsResponse
suspend fun getSearchAds(
term: String,
placements: Map<String, PlacementRequest>,
segmentation: List<Segmentation>? = null,
tags: List<String>? = null,
dedupCampaignAds: Boolean = false,
dedupAds: Boolean = false
): AdsResponse
suspend fun getCategoryAds(
categoryName: String,
placements: Map<String, PlacementRequest>,
segmentation: List<Segmentation>? = null,
tags: List<String>? = null,
dedupCampaignAds: Boolean = false,
dedupAds: Boolean = false
): AdsResponse
suspend fun getProductPageAds(
productSku: String,
placements: Map<String, PlacementRequest>,
segmentation: List<Segmentation>? = null,
tags: List<String>? = null,
dedupCampaignAds: Boolean = false,
dedupAds: Boolean = false
): AdsResponse
}
Service for tracking ad events.
interface EventService {
fun deliveryBeaconEvent(eventUrl: String, callback: ((Boolean) -> Unit)? = null)
suspend fun sendConversion(request: ConversionRequest): Boolean
}
sealed class Ad {
abstract val adId: String
abstract val type: AdType
abstract val clickUrl: String
abstract val impressionUrl: String
abstract val viewUrl: String
abstract val sellerId: String?
data class ProductAd(
override val adId: String,
override val type: AdType,
override val clickUrl: String,
override val impressionUrl: String,
override val viewUrl: String,
override val sellerId: String?,
val productSku: String
) : Ad()
data class BannerAd(
override val adId: String,
override val type: AdType,
override val clickUrl: String,
override val impressionUrl: String,
override val viewUrl: String,
override val sellerId: String?,
val mediaUrl: String
) : Ad()
data class SponsoredBrandAd(
override val adId: String,
override val type: AdType,
override val clickUrl: String,
override val impressionUrl: String,
override val viewUrl: String,
override val sellerId: String?,
val mediaUrl: String,
val products: List<BrandProduct>
) : Ad()
data class DigitalSignageAd(
override val adId: String,
override val type: AdType,
override val clickUrl: String,
override val impressionUrl: String,
override val viewUrl: String,
override val sellerId: String?,
val mediaUrl: String,
val duration: Int
) : Ad()
}
For detailed documentation on specialized builders, see SPECIALIZED_BUILDERS.md.
val customOkHttpClient = OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.addInterceptor(LoggingInterceptor())
.build()
// Note: Custom HTTP client configuration is not yet exposed in the public API
// Future versions will support custom OkHttpClient injection
Run the test suite:
# All tests
./gradlew test
# Specific test class
./gradlew test --tests "com.vtex.ads.sdk.models.SpecializedPlacementRequestTest"
# Integration test
./gradlew runIntegrationTest
Test Coverage:
- 146+ unit tests
- Integration tests with real API
- Mock-based tests for services
- Builder validation tests
- JDK: 21 or higher
- Kotlin: 1.9.22 or higher
- Minimum Android SDK: 21 (Android 5.0 Lollipop)
dependencies {
// Kotlin
implementation("org.jetbrains.kotlin:kotlin-stdlib:1.9.22")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
// HTTP
implementation("com.squareup.okhttp3:okhttp:4.12.0")
// JSON
implementation("com.squareup.moshi:moshi:1.15.0")
implementation("com.squareup.moshi:moshi-kotlin:1.15.0")
}
- β Ad querying service (home, search, category, product pages)
- β Event tracking (impression, view, click, conversion)
- β Specialized builders for type-safe placements
- β Order conversion tracking with auto-hashing
- β Dynamic user ID management
- β Retry logic with exponential backoff
- β Comprehensive error handling
- β Video ad support with resolution filtering
- β Segmentation and targeting
- β Full test coverage
Contributions are welcome! Please follow these steps:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature
) - Commit your changes (
git commit -m 'Add amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request
# Clone the repository
git clone https://github.com/vtex/vtex-ads-sdk-kotlin.git
cd vtex-ads-sdk-kotlin
# Build the project
./gradlew build
# Run tests
./gradlew test
# Run integration tests
./gradlew runIntegrationTest
MIT License
Copyright (c) 2025 VTEX
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
- Issues: GitHub Issues
- Documentation: VTEX Developer Portal
- Email: [email protected]
Built with β€οΈ by the VTEX Ads team.
Special thanks to:
- The Kotlin team for an amazing language
- Square for OkHttp and Moshi
- The VTEX developer community
Made with β€οΈ using Claude Code