Skip to main content

Installation

  1. Add InsForge dependencies to your project
build.gradle.kts:
repositories {
    mavenLocal() // For local development
    mavenCentral()
}

dependencies {
    implementation("dev.insforge:insforge-kotlin:0.1.6")
}
  1. Initialize InsForge SDK
import dev.insforge.createInsforgeClient
import dev.insforge.auth.Auth
import dev.insforge.database.Database
import dev.insforge.storage.Storage
import dev.insforge.functions.Functions
import dev.insforge.realtime.Realtime
import dev.insforge.ai.AI

val client = createInsforgeClient(
    baseUrl = "https://your-app.insforge.app",
    anonKey = "your-api-key"
) {
    install(Auth)
    install(Database)
    install(Storage)
    install(Functions)
    install(Realtime) {
        autoReconnect = true
        reconnectDelay = 5000
    }
    install(AI)
}
  1. Enable Logging (Optional)
For debugging, you can configure the SDK log level:
import dev.insforge.InsforgeLogLevel

val client = createInsforgeClient(
    baseUrl = "https://your-app.insforge.app",
    anonKey = "your-api-key"
) {
    // DEBUG: logs request method/URL and response status
    // VERBOSE: logs full headers and request/response bodies
    logLevel = InsforgeLogLevel.DEBUG

    install(Auth)
    install(Database)
    // ... other modules
}
Log LevelDescription
NONENo logging (default, recommended for production)
ERROROnly errors
WARNWarnings and errors
INFOInformational messages
DEBUGDebug info (request method, URL, response status)
VERBOSEFull details (headers, request/response bodies)
Use NONE or ERROR in production to avoid exposing sensitive data in logs.

Android Initialization

  1. Add Chrome Custom Tabs dependency to your build.gradle.kts:
dependencies {
    implementation("androidx.browser:browser:1.9.0")
}
  1. Initialize InsForge SDK (With Chrome Custom Tabs and Session Storage)
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.net.Uri
import androidx.browser.customtabs.CustomTabsIntent
import dev.insforge.createInsforgeClient
import dev.insforge.ai.AI
import dev.insforge.auth.Auth
import dev.insforge.auth.BrowserLauncher
import dev.insforge.auth.ClientType
import dev.insforge.auth.SessionStorage
import dev.insforge.database.Database
import dev.insforge.functions.Functions
import dev.insforge.realtime.Realtime
import dev.insforge.storage.Storage

class InsforgeManager(private val context: Context) {

    val client = createInsforgeClient(
        baseUrl = "https://your-app.insforge.app",
        anonKey = "your-anon-key"
    ) {
        install(Auth) {
            // 1. Chrome Custom Tabs - opens in-app, similar to iOS ASWebAuthenticationSession
            browserLauncher = BrowserLauncher { url ->
                val customTabsIntent = CustomTabsIntent.Builder()
                    .setShowTitle(true)
                    .build()
                // Handle non-Activity context safely
                if (context is Activity) {
                    customTabsIntent.launchUrl(context, Uri.parse(url))
                } else {
                    customTabsIntent.intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                    customTabsIntent.launchUrl(context, Uri.parse(url))
                }
            }

            // 2. enable session persistence
            persistSession = true

            // 3. config SessionStorage (use SharedPreferences)
            sessionStorage = object : SessionStorage {
                private val prefs = context.getSharedPreferences(
                    "insforge_auth",
                    Context.MODE_PRIVATE
                )

                override suspend fun save(key: String, value: String) {
                    prefs.edit().putString(key, value).apply()
                }

                override suspend fun get(key: String): String? {
                    return prefs.getString(key, null)
                }

                override suspend fun remove(key: String) {
                    prefs.edit().remove(key).apply()
                }
            }

            // 4. set client type for mobile
            clientType = ClientType.MOBILE
        }
        // Install Database module
        install(Database)

        // Install Realtime module for real-time subscriptions
        install(Realtime) {
            debug = true
        }
        // Install other modules
        install(Storage)
        install(Functions)
        install(AI)
    }
}
  1. Use Jetpack DataStore for Session Storage (Optional)
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map

val Context.authDataStore: DataStore<Preferences> by preferencesDataStore(name = "insforge_auth")

class DataStoreSessionStorage(private val context: Context) : SessionStorage {
    
    override suspend fun save(key: String, value: String) {
        context.authDataStore.edit { prefs ->
            prefs[stringPreferencesKey(key)] = value
        }
    }
    
    override suspend fun get(key: String): String? {
        return context.authDataStore.data.map { prefs ->
            prefs[stringPreferencesKey(key)]
        }.first()
    }
    
    override suspend fun remove(key: String) {
        context.authDataStore.edit { prefs ->
            prefs.remove(stringPreferencesKey(key))
        }
    }
}

// Then use it in your InsForge client
install(Auth) {
    browserLauncher = ...
    persistSession = true
    sessionStorage = DataStoreSessionStorage(context)
}

connect()

Establish a WebSocket connection to the realtime server.

Example

// Connect to Realtime server
client.realtime.connect()

// Check connection status
println("Connected: ${client.realtime.isConnected}")
println("Socket ID: ${client.realtime.socketId}")

Monitor Connection State

// Monitor connection state using Flow
client.realtime.connectionState.collect { state ->
    when (state) {
        is Realtime.ConnectionState.Connected -> println("Connected")
        is Realtime.ConnectionState.Disconnected -> println("Disconnected")
        is Realtime.ConnectionState.Connecting -> println("Connecting...")
        is Realtime.ConnectionState.Error -> println("Error: ${state.message}")
    }
}

disconnect()

Disconnect from the realtime server.

Example

client.realtime.disconnect()

// Verify disconnection
println("Connected: ${client.realtime.isConnected}")  // false
println("Socket ID: ${client.realtime.socketId}")      // null

subscribe()

Subscribe to a channel to receive events.

Parameters

  • channel (String) - The channel name to subscribe to

Returns

SubscribeResponse(
    ok: Boolean,
    channel: String,
    error: SubscribeError?  // null if ok is true
)

Example

client.realtime.connect()

val response = client.realtime.subscribe("todos")

if (response.ok) {
    println("Subscribed to channel: ${response.channel}")
} else {
    println("Subscribe failed: ${response.error?.message}")
}

unsubscribe()

Unsubscribe from a channel.

Example

client.realtime.unsubscribe("todos")

// Verify unsubscription
val channels = client.realtime.getSubscribedChannels()
println("Still subscribed: ${channels.contains("todos")}")  // false

on()

Register an event listener for a specific event type.

Parameters

  • event (String) - The event name to listen for (e.g., “INSERT”, “UPDATE”, “DELETE”)
  • callback (EventCallback<T>) - Callback function that receives the event data

Example (Listen for INSERT Events)

import dev.insforge.realtime.models.SocketMessage

client.realtime.connect()
client.realtime.subscribe("todos")

// Register INSERT event listener
client.realtime.on<SocketMessage>("INSERT") { message ->
    message?.let {
        println("INSERT event received:")
        println("  Channel: ${it.channel}")
        println("  MessageId: ${it.messageId}")
        println("  Payload: ${it.payload}")
    }
}

Example (Listen for Multiple Event Types)

client.realtime.connect()
client.realtime.subscribe("todos")

// Register listeners for all CRUD events
listOf("INSERT", "UPDATE", "DELETE").forEach { eventType ->
    client.realtime.on<SocketMessage>(eventType) { message ->
        message?.let {
            println("$eventType event: ${it.payload}")
        }
    }
}

off()

Remove an event listener.

Example

val callback = Realtime.EventCallback<SocketMessage> { message ->
    println("Event received: $message")
}

// Register listener
client.realtime.on("INSERT", callback)

// Later, remove the listener
client.realtime.off("INSERT", callback)

once()

Register a one-time event listener that automatically removes itself after the first event.

Example

client.realtime.once<SocketMessage>("INSERT") { message ->
    println("Received first INSERT event: ${message?.payload}")
    // Listener is automatically removed after this
}

publish()

Publish a message to a channel.
You must be subscribed to a channel before publishing messages to it.

Parameters

  • channel (String) - The channel name to publish to
  • event (String) - The event name
  • payload (Any) - The message payload

Example

client.realtime.connect()
client.realtime.subscribe("chat")

// Publish a message
client.realtime.publish(
    channel = "chat",
    event = "message",
    payload = mapOf(
        "text" to "Hello from Kotlin SDK",
        "timestamp" to System.currentTimeMillis()
    )
)

Error Handling

// Publishing without connection throws an error
try {
    client.realtime.publish("channel", "event", mapOf("test" to "data"))
} catch (e: IllegalStateException) {
    println("Error: ${e.message}")  // Not connected
}

getSubscribedChannels()

Get the list of currently subscribed channels.

Example

client.realtime.connect()
client.realtime.subscribe("todos")
client.realtime.subscribe("chat")

val channels = client.realtime.getSubscribedChannels()
println("Subscribed to: $channels")  // [todos, chat]

Best Practices

  1. Always disconnect when done: Call disconnect() in onDispose or onDestroy to clean up resources.
  2. Check subscribe response: Always verify response.ok before assuming subscription succeeded.
  3. Handle connection state: Monitor connectionState Flow to handle reconnection scenarios.
  4. Use typed callbacks: Specify the expected type in on<T>() for type-safe event handling.
  5. Subscribe before publish: You must subscribe to a channel before publishing messages to it.
  6. Remove listeners when not needed: Use off() to remove listeners and prevent memory leaks.

Error Handling

import dev.insforge.exceptions.InsforgeException

try {
    client.realtime.connect()
    val response = client.realtime.subscribe("todos")

    if (!response.ok) {
        when (response.error?.code) {
            "UNAUTHORIZED" -> println("Not authorized to subscribe")
            "CHANNEL_NOT_FOUND" -> println("Channel does not exist")
            else -> println("Subscribe failed: ${response.error?.message}")
        }
    }
} catch (e: InsforgeException) {
    println("Realtime error: ${e.message}")
} catch (e: IllegalStateException) {
    println("Invalid state: ${e.message}")
}