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.
- Features
- Requirements
- Installation
- Quick Start
- Event Tracking
- Running Auctions
- Advanced Features
- Error Handling
- Testing
- License
- 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
- Android SDK 24+ (Android 7.0 Nougat)
- Java 11+
INTERNETpermission
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'
}
}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")
)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")
)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")
)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
)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
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")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)
}
}
}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"))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
)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
)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}")
}
}Mock the auction service in tests:
@Before
fun setup() {
TopsortAuctionsHttpService.setMockService(mockService)
}
@After
fun teardown() {
TopsortAuctionsHttpService.resetToDefaultService()
}This library is licensed under the MIT License.
For more information, visit topsort.com or check the API documentation.