Skip to content

Topsort/topsort.kt

Repository files navigation

Topsort Android SDK

Maven Central License: MIT API Kotlin CI codecov

The official Android SDK for the Topsort retail media platform. Track impressions, clicks, purchases, and page views with full support for promoted and organic content attribution.

Table of Contents

Features

  • Event Tracking - Impressions, clicks, purchases, and page views
  • Promoted & Organic - Full attribution support for both content types
  • Banner Auctions - Run auctions with comprehensive error handling and callbacks
  • A/B Testing - Built-in experiment bucket support (placementId)
  • Quality Scores - Pass product quality signals for auction optimization
  • Event Context - Rich context with device type, channel, and page information
  • Offline Support - Events are queued and sent when connectivity is restored
  • Java & Kotlin - Full interoperability with both languages

Requirements

  • Android SDK 24+ (Android 7.0 Nougat)
  • Java 11+
  • INTERNET permission

Installation

Add the dependency to your build.gradle:

dependencies {
    implementation 'com.topsort:topsort-kt:2.0.1'
}

Ensure Java 11 compatibility:

android {
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_11
        targetCompatibility JavaVersion.VERSION_11
    }
    kotlinOptions {
        jvmTarget = '11'
    }
}

Quick Start

Initialize the SDK in your Application class:

class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()

        Analytics.setup(
            application = this,
            opaqueUserId = "user-unique-id", // Consistent ID for the user
            token = "your-api-token"
        )
    }
}

Report a promoted click:

Analytics.reportClickPromoted(
    resolvedBidId = "bid-from-auction",
    placement = Placement(path = "/search")
)

Event Tracking

Impressions

Track when products are displayed to users.

Promoted impression (from auction winner):

Analytics.reportImpressionPromoted(
    resolvedBidId = "resolved-bid-id",
    placement = Placement(
        path = "/search/results",
        position = 1,
        page = 1,
        pageSize = 20
    ),
    // Optional context
    deviceType = Device.MOBILE,
    channel = Channel.ONSITE,
    page = Page.Factory.buildWithId(PageType.SEARCH, "electronics")
)

Organic impression (non-promoted content):

Analytics.reportImpressionOrganic(
    entity = Entity(id = "product-123", type = EntityType.PRODUCT),
    placement = Placement(path = "/category/electronics")
)

Clicks

Track when users click on products.

Promoted click:

Analytics.reportClickPromoted(
    resolvedBidId = "resolved-bid-id",
    placement = Placement(path = "/search"),
    clickType = ClickType.PRODUCT,  // or LIKE, ADD_TO_CART
    deviceType = Device.MOBILE,
    channel = Channel.ONSITE
)

Organic click:

Analytics.reportClickOrganic(
    entity = Entity(id = "product-123", type = EntityType.PRODUCT),
    placement = Placement(path = "/home")
)

Purchases

Track completed purchases with full item details:

Analytics.reportPurchase(
    id = "order-12345",
    items = listOf(
        PurchasedItem(
            productId = "product-123",
            quantity = 2,
            unitPrice = 1999,  // $19.99 in cents
            resolvedBidId = "bid-id-if-promoted",  // Optional
            vendorId = "vendor-456"  // Optional, for halo attribution
        )
    ),
    deviceType = Device.DESKTOP,
    channel = Channel.ONSITE
)

Page Views

Track page/screen views for analytics:

Analytics.reportPageView(
    page = Page.Factory.buildWithId(
        type = PageType.PDP,
        pageId = "product-123"
    ),
    deviceType = Device.MOBILE,
    channel = Channel.ONSITE
)

Page types (from PageType enum): HOME, PDP, SEARCH, CATEGORY, CART, OTHER

Running Auctions

Sponsored Listings

Use AuctionConfig to run sponsored listing auctions:

// Create auction configuration
val config = AuctionConfig.ProductIds(
    numSlots = 3,
    ids = listOf("product-1", "product-2", "product-3"),
    opaqueUserId = "user-123",        // For targeting
    placementId = 5,                   // A/B test bucket (1-8)
    qualityScores = listOf(0.9, 0.8, 0.7)  // Optional quality signals
)

// Build and run the auction
val auction = Auction.fromConfig(config)
val request = AuctionRequest(listOf(auction))

lifecycleScope.launch(Dispatchers.IO) {
    try {
        val response = TopsortAuctionsHttpService.runAuctionsSync(request)
            ?: return@launch  // No response

        response.results.forEach { result ->
            result.winners.forEach { winner ->
                println("Winner: ${winner.id}")
                println("Bid ID: ${winner.resolvedBidId}")
                println("Campaign: ${winner.campaignId}")  // Campaign attribution
            }
        }
    } catch (e: AuctionError) {
        handleError(e)
    }
}

Other auction types:

// Single category
AuctionConfig.CategorySingle(numSlots = 2, category = "electronics")

// Multiple categories
AuctionConfig.CategoryMultiple(numSlots = 2, categories = listOf("phones", "tablets"))

// Keyword search
AuctionConfig.Keyword(numSlots = 3, keyword = "wireless headphones")

Banner Auctions

Use BannerView for banner ads with callbacks:

val bannerView = findViewById<BannerView>(R.id.banner_view)

// Configure callbacks
bannerView.onError { throwable ->
    Log.e("Banner", "Error loading banner", throwable)
}

bannerView.onAuctionError { error ->
    when (error) {
        is AuctionError.HttpError -> // Network error
        is AuctionError.DeserializationError -> // Parse error
        is AuctionError.EmptyResponse -> // No response
        else -> // Other errors
    }
}

bannerView.onNoWinners {
    // No ads available
    bannerView.visibility = View.GONE
}

bannerView.onImageLoad {
    // Banner loaded successfully
    bannerView.visibility = View.VISIBLE
}

// Run auction
val config = BannerConfig.LandingPage(
    slotId = "homepage-banner",
    ids = listOf("featured-product-1", "featured-product-2")
)

lifecycleScope.launch {
    bannerView.setup(
        config = config,
        path = "/home",
        location = "hero-banner"
    ) { id, type ->
        // Handle click
        when (type) {
            EntityType.PRODUCT -> openProductPage(id)
            EntityType.VENDOR -> openVendorPage(id)
            else -> openUrl(id)
        }
    }
}

Advanced Features

Event Context

Add rich context to all events:

// Device type
deviceType = Device.MOBILE    // or DESKTOP

// Channel
channel = Channel.ONSITE      // or OFFSITE, INSTORE

// Click type (for clicks only)
clickType = ClickType.PRODUCT // or LIKE, ADD_TO_CART

// Page context
page = Page.Factory.buildWithId(PageType.SEARCH, "query-id")
// or with multiple values
page = Page.Factory.buildWithValues(PageType.CATEGORY, listOf("electronics", "phones"))

A/B Testing

Use placementId (1-8) to bucket users into experiments:

val config = AuctionConfig.ProductIds(
    numSlots = 3,
    ids = productIds,
    placementId = userBucket  // 1-8 based on user assignment
)

Quality Scores

Pass quality signals to optimize auction results:

val config = AuctionConfig.ProductIds(
    numSlots = 3,
    ids = listOf("p1", "p2", "p3"),
    qualityScores = listOf(0.95, 0.82, 0.71)  // Must match ids size
)

Error Handling

The SDK uses AuctionError sealed class for auction errors:

try {
    val response = TopsortAuctionsHttpService.runAuctionsSync(request)
        ?: throw AuctionError.EmptyResponse

    // Use response...
} catch (e: AuctionError) {
    when (e) {
        is AuctionError.HttpError ->
            Log.e("Auction", "Network error", e.error)
        is AuctionError.DeserializationError ->
            Log.e("Auction", "Invalid response: ${String(e.data)}")
        is AuctionError.EmptyResponse ->
            Log.e("Auction", "Empty response from server")
        is AuctionError.SerializationError ->
            Log.e("Auction", "Failed to build request")
        is AuctionError.InvalidNumberAuctions ->
            Log.e("Auction", "Invalid auction count: ${e.count}")
    }
}

Testing

Mock the auction service in tests:

@Before
fun setup() {
    TopsortAuctionsHttpService.setMockService(mockService)
}

@After
fun teardown() {
    TopsortAuctionsHttpService.resetToDefaultService()
}

License

This library is licensed under the MIT License.


For more information, visit topsort.com or check the API documentation.

About

A library that helps you send events from your android app

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Contributors