Skip to main content

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

  1. Installation
  2. SDK Integration
  3. Backend Implementation
  4. API Reference
  5. Performance

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:

  1. Backend Setup → Create intent token endpoint
  2. Get Token → Call your backend for intent token
  3. Open Passage → Launch connection flow with callbacks
  4. 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:

  1. Open the project in Android Studio
  2. Navigate to example/ module
  3. Add your backend endpoint URL in the configuration
  4. Build and run on device or emulator
  5. 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: