Skip to main content

Native Android Integration

Integrate Loyalteez into your native Android app using Kotlin.

Prerequisites

  • Android Studio
  • Min SDK 24 (Android 7.0)
  • Your Loyalteez Brand ID

Quick Start

1. Add Dependencies

// build.gradle (app)
dependencies {
// Networking
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.okhttp3:logging-interceptor:4.11.0'

// Coroutines
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'

// Privy SDK
implementation 'io.privy:privy-android:1.0.0'

// Jetpack Compose (optional, for UI)
implementation 'androidx.compose.ui:ui:1.5.4'
implementation 'androidx.compose.material3:material3:1.1.2'
}

2. Create API Service

// LoyalteezApi.kt
import retrofit2.Response
import retrofit2.http.*

interface LoyalteezApi {
@POST("/loyalteez-api/manual-event")
suspend fun trackEvent(@Body request: EventRequest): Response<EventResponse>

@POST("/relay")
suspend fun executeTransaction(
@Header("Authorization") token: String,
@Body request: TransactionRequest
): Response<TransactionResponse>

@GET("/loyalteez-api/debug")
suspend fun checkHealth(): Response<Any>
}

data class EventRequest(
val event: String,
val email: String,
val metadata: Map<String, Any>
)

data class EventResponse(
val success: Boolean,
val ltzDistributed: Int?,
val transactionHash: String?,
val walletAddress: String?
)

data class TransactionRequest(
val to: String,
val data: String,
val userAddress: String,
val value: String = "0",
val gasLimit: Int = 500000
)

data class TransactionResponse(
val success: Boolean,
val transactionHash: String
)

3. Create Service Class

// LoyalteezService.kt
import android.content.Context
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

class LoyalteezService(context: Context) {
private val apiUrl = "https://api.loyalteez.app"
private val gasRelayerUrl = "https://relayer.loyalteez.app"
private val brandId = "YOUR_BRAND_ID"

private val eventApi = Retrofit.Builder()
.baseUrl(apiUrl)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(LoyalteezApi::class.java)

private val gasRelayerApi = Retrofit.Builder()
.baseUrl(gasRelayerUrl)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(LoyalteezApi::class.java)

suspend fun trackEvent(
event: String,
email: String,
metadata: Map<String, Any> = emptyMap()
): Result<EventResponse> = withContext(Dispatchers.IO) {
try {
val request = EventRequest(
event = event,
email = email,
metadata = metadata + mapOf(
"platform" to "mobile",
"os" to "Android",
"appVersion" to BuildConfig.VERSION_NAME
)
)

val response = eventApi.trackEvent(request)
if (response.isSuccessful && response.body() != null) {
Result.success(response.body()!!)
} else {
Result.failure(Exception("Failed to track event"))
}
} catch (e: Exception) {
Result.failure(e)
}
}

suspend fun executeTransaction(
privyToken: String,
to: String,
data: String,
userAddress: String
): Result<TransactionResponse> = withContext(Dispatchers.IO) {
try {
val request = TransactionRequest(
to = to,
data = data,
userAddress = userAddress
)

val response = gasRelayerApi.executeTransaction(
token = "Bearer $privyToken",
request = request
)

if (response.isSuccessful && response.body() != null) {
Result.success(response.body()!!)
} else {
Result.failure(Exception("Transaction failed"))
}
} catch (e: Exception) {
Result.failure(e)
}
}

suspend fun checkHealth(): Boolean = withContext(Dispatchers.IO) {
try {
val response = eventApi.checkHealth()
response.isSuccessful
} catch (e: Exception) {
false
}
}
}

4. Create Composable UI

// MainActivity.kt
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

setContent {
MaterialTheme {
LoyalteezApp()
}
}
}
}

@Composable
fun LoyalteezApp() {
val coroutineScope = rememberCoroutineScope()
val service = remember { LoyalteezService(context) }

var isLoading by remember { mutableStateOf(false) }
var showReward by remember { mutableStateOf(false) }
var rewardAmount by remember { mutableStateOf(0) }
var isAuthenticated by remember { mutableStateOf(false) }
var userEmail by remember { mutableStateOf("") }

Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
// Header
Surface(
modifier = Modifier.fillMaxWidth(),
color = Color(0xFF8CBC99),
shape = RoundedCornerShape(12.dp)
) {
Column(modifier = Modifier.padding(20.dp)) {
Text("Loyalteez Demo", style = MaterialTheme.typography.headlineMedium)
Text("Earn rewards for your actions")
}
}

Spacer(modifier = Modifier.height(16.dp))

if (!isAuthenticated) {
// Login Section
Card(modifier = Modifier.fillMaxWidth()) {
Column(modifier = Modifier.padding(20.dp)) {
Text("Get Started", style = MaterialTheme.typography.titleLarge)
Spacer(modifier = Modifier.height(8.dp))
Text("Login to start earning LTZ rewards")
Spacer(modifier = Modifier.height(16.dp))
Button(
onClick = {
// Implement Privy login
isAuthenticated = true
userEmail = "[email protected]"
},
modifier = Modifier.fillMaxWidth(),
enabled = !isLoading
) {
Text("Login with Email")
}
}
}
} else {
// Actions Section
Card(modifier = Modifier.fillMaxWidth()) {
Column(modifier = Modifier.padding(20.dp)) {
Text("Earn Rewards", style = MaterialTheme.typography.titleLarge)
Spacer(modifier = Modifier.height(8.dp))
Text("Complete actions to earn LTZ")
Spacer(modifier = Modifier.height(16.dp))
Button(
onClick = {
coroutineScope.launch {
isLoading = true
val result = service.trackEvent(
event = "action_completed",
email = userEmail,
metadata = mapOf("screen" to "home")
)
isLoading = false

result.onSuccess { response ->
response.ltzDistributed?.let { amount ->
rewardAmount = amount
showReward = true
}
}
}
},
modifier = Modifier.fillMaxWidth(),
enabled = !isLoading
) {
Text(if (isLoading) "Processing..." else "Complete Action")
}
}
}
}

// Reward Notification
if (showReward) {
Spacer(modifier = Modifier.height(16.dp))
Surface(
color = Color(0xFF8CBC99),
shape = RoundedCornerShape(12.dp)
) {
Column(
modifier = Modifier.padding(20.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("🎉", style = MaterialTheme.typography.displayMedium)
Text(
"You earned $rewardAmount LTZ!",
color = Color.White,
style = MaterialTheme.typography.titleLarge
)
Text(
"Keep up the great work",
color = Color.White.copy(alpha = 0.9f)
)
}
}

LaunchedEffect(Unit) {
kotlinx.coroutines.delay(3000)
showReward = false
}
}
}
}

5. Add Permissions

<!-- AndroidManifest.xml -->
<manifest>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

<application>
<!-- Your app config -->
</application>
</manifest>

Testing

  1. Build and run: Shift + F10
  2. Test event tracking
  3. Check Partner Portal analytics

Support