Kotlin SDK Integration Guide
This guide provides comprehensive instructions for integrating the Passage Kotlin SDK into your Android application and implementing the required backend infrastructure.
Table of Contents
Installation
Requirements
- Android 7.0 (API level 24)+
- Kotlin 1.9+
- Gradle 8.0+
Gradle with JitPack
Add JitPack repository to your settings.gradle.kts:
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven { url = uri("https://jitpack.io") }
}
}
Or in your root build.gradle.kts:
allprojects {
repositories {
google()
mavenCentral()
maven { url = uri("https://jitpack.io") }
}
}
Then add the dependency to your app's build.gradle.kts:
dependencies {
implementation("com.github.tailriskai:passage-kotlin:0.0.8")
}
SDK Integration
Overview
To integrate the Passage Kotlin SDK, you need to complete these steps:
- Backend Setup → Create intent token endpoint
- Get Token → Call your backend for intent token
- Open Passage → Launch connection flow with callbacks
- Handle Results → Process success/error responses
Note: Configuring the SDK is optional. If not configured, default settings will be used.
Quick Reference
Step 1: Configure SDK (Optional)
import com.passage.sdk.PassageSDK
import com.passage.sdk.PassageConfig
// Optional: Configure for debug logging
PassageSDK.configure(PassageConfig(debug = true))
Step 2: Get Intent Token
// Call your backend to get intent token
val client = OkHttpClient()
val request = Request.Builder()
.url("https://your-api.com/intent-token")
.get()
.build()
val response = client.newCall(request).execute()
val json = JSONObject(response.body?.string() ?: "")
val intentToken = json.getString("intentToken")
Step 3: Open Passage Flow
PassageSDK.open(
activity = this,
token = intentToken,
onConnectionComplete = { data ->
println("Success: ${data.connectionId}")
}
)
Detailed Implementation
Complete Jetpack Compose Integration
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.unit.dp
import androidx.lifecycle.lifecycleScope
import com.passage.sdk.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import okhttp3.Request
import org.json.JSONObject
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Optional: Configure Passage SDK for debug logging
PassageSDK.configure(PassageConfig(debug = true))
setContent {
MaterialTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
PassageScreen()
}
}
}
}
@Composable
fun PassageScreen() {
var isLoading by remember { mutableStateOf(false) }
var result by remember { mutableStateOf("") }
Column(
modifier = Modifier
.fillMaxSize()
.padding(20.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Button(
onClick = {
lifecycleScope.launch {
connectAccount { newResult ->
result = newResult
}
}
},
enabled = !isLoading
) {
Text(if (isLoading) "Connecting..." else "Connect Account")
}
if (isLoading) {
Spacer(modifier = Modifier.height(16.dp))
CircularProgressIndicator()
}
if (result.isNotEmpty()) {
Spacer(modifier = Modifier.height(16.dp))
Text(text = result)
}
}
}
private suspend fun connectAccount(onResult: (String) -> Unit) {
try {
// 1. Get intent token from your backend
val intentToken = getIntentToken()
// 2. Open Passage with callbacks
openPassage(intentToken, onResult)
} catch (e: Exception) {
onResult("Error: ${e.message}")
}
}
private suspend fun getIntentToken(): String = withContext(Dispatchers.IO) {
val client = OkHttpClient()
val request = Request.Builder()
.url("https://your-api.com/api/intent-token")
.get()
.build()
val response = client.newCall(request).execute()
val body = response.body?.string() ?: throw Exception("Empty response")
val json = JSONObject(body)
json.getString("intentToken")
}
private fun openPassage(token: String, onResult: (String) -> Unit) {
PassageSDK.open(
activity = this,
token = token,
onConnectionComplete = { data ->
runOnUiThread {
onResult("✅ Connected! ID: ${data.connectionId}")
println("History items: ${data.history.size}")
// Process the captured data
for (item in data.history) {
println("Data: $item")
}
}
},
onExit = { reason ->
println("User exited: ${reason ?: "unknown"}")
}
)
}
}
Activity-Based Integration (Traditional Android)
import android.os.Bundle
import android.widget.Button
import android.widget.ProgressBar
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import com.passage.sdk.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import okhttp3.Request
import org.json.JSONObject
class MainActivity : AppCompatActivity() {
private lateinit var connectButton: Button
private lateinit var progressBar: ProgressBar
private lateinit var resultText: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Optional: Configure SDK for debug logging
PassageSDK.configure(PassageConfig(debug = true))
// Setup UI
connectButton = findViewById(R.id.connectButton)
progressBar = findViewById(R.id.progressBar)
resultText = findViewById(R.id.resultText)
connectButton.setOnClickListener {
lifecycleScope.launch {
connectAccount()
}
}
}
private suspend fun connectAccount() {
showLoading(true)
try {
// Get intent token
val intentToken = getIntentToken()
// Open Passage flow
openPassage(intentToken)
} catch (e: Exception) {
showLoading(false)
showAlert("Error", e.message ?: "Unknown error")
}
}
private suspend fun getIntentToken(): String = withContext(Dispatchers.IO) {
val client = OkHttpClient()
val request = Request.Builder()
.url("https://your-api.com/api/intent-token")
.get()
.build()
val response = client.newCall(request).execute()
val body = response.body?.string() ?: throw Exception("Empty response")
val json = JSONObject(body)
json.getString("intentToken")
}
private fun openPassage(token: String) {
PassageSDK.open(
activity = this,
token = token,
onConnectionComplete = { data ->
runOnUiThread {
showLoading(false)
resultText.text = "Success! Connection ID: ${data.connectionId}"
showAlert(
title = "Success",
message = "Account connected successfully!"
)
}
},
onExit = { reason ->
println("User exited: ${reason ?: "unknown"}")
runOnUiThread {
showLoading(false)
}
}
)
}
private fun showLoading(loading: Boolean) {
connectButton.isEnabled = !loading
progressBar.isVisible = loading
connectButton.text = if (loading) "Connecting..." else "Connect Account"
}
private fun showAlert(title: String, message: String) {
AlertDialog.Builder(this)
.setTitle(title)
.setMessage(message)
.setPositiveButton("OK", null)
.show()
}
}
Using Options-Based API
// Alternative: Pass all parameters via PassageOpenOptions
val options = PassageOpenOptions(
intentToken = intentToken,
onConnectionComplete = { data ->
println("Success: ${data.connectionId}")
},
onExit = { reason ->
println("Exited: $reason")
}
)
PassageSDK.open(activity = this, options = options)
Backend Implementation
Your backend needs to create intent tokens for the Android app to use. Here's a typical implementation:
Node.js Example
app.post('/api/intent-token', async (req, res) => {
try {
// 1. Get access token using OAuth 2.0 Client Credentials
const accessToken = await getPassageAccessToken();
// 2. Create intent token
const response = await fetch('https://api.getpassage.ai/intent-token', {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
integrationId: 'netflix', // or other integrations
resources: {
genericHistory: {
read: {}
}
}
})
});
const { intentToken } = await response.json();
res.json({ intentToken });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
async function getPassageAccessToken() {
const response = await fetch('https://api.getpassage.ai/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'client_credentials',
client_id: process.env.PASSAGE_CLIENT_ID,
client_secret: process.env.PASSAGE_CLIENT_SECRET
})
});
const { access_token } = await response.json();
return access_token;
}
API Reference
Configuration
data class PassageConfig(
val debug: Boolean = false // Enable debug logging
)
Configuration is optional. If not configured, the SDK will use default settings.
Core Methods
// Singleton instance
PassageSDK
// Configure SDK (optional - call once at app startup)
fun configure(config: PassageConfig)
// Open connection flow (parameter-based)
fun open(
activity: Activity,
token: String,
onConnectionComplete: ((PassageSuccessData) -> Unit)? = null,
onExit: ((String?) -> Unit)? = null
)
// Open connection flow (options-based)
fun open(activity: Activity, options: PassageOpenOptions)
// Close connection programmatically
fun close()
Data Types
PassageSuccessData
data class PassageSuccessData(
val history: List<Any?>, // Captured data items
val connectionId: String // Unique connection identifier
)
📚 For detailed data structures returned by each integration, see Integration Data Types →
PassageOpenOptions
data class PassageOpenOptions(
val intentToken: String? = null,
val onConnectionComplete: ((PassageSuccessData) -> Unit)? = null,
val onExit: ((String?) -> Unit)? = null
)
Performance
The Passage SDK is designed to be lightweight with minimal impact on your app's performance.
Performance Characteristics
- Memory Usage: Approximately 15-20 MB overhead for full SDK functionality including WebView rendering
- CPU Impact: Minimal sustained usage with brief spikes during web page loading operations
- UI Responsiveness: Maintains smooth 60+ FPS during normal usage
- Startup Time: No measurable impact on app launch performance
- Memory Management: WebView instances are automatically released after sessions to free memory while preserving cookies/localStorage for user convenience
Note: Detailed benchmarking results for Android will be added in future updates.
Integration Data Types
- Browse integrations and resources in the explorer
Example App
The SDK includes a complete example app demonstrating all features:
- Open the project in Android Studio
- Navigate to
example/module - Add your backend endpoint URL in the configuration
- Build and run on device or emulator
- Tap "Connect" to test the integration
The example app demonstrates:
- Auto-fetch and manual token modes
- All callback handlers
- Integration selection
- Error handling
- Result display
Support
If you encounter issues not covered here:
- GitHub Issues: Create an issue with logs and steps to reproduce
- Email Support: support@getpassage.ai
- Documentation: Latest updates at docs.getpassage.ai