Skip to content

Native SDKs

Lane.Chat ships native SDKs for Android (Kotlin), iOS (Swift), and Flutter (Dart) — all at version 0.3.0, with aligned API names. Each presents the Lane.Chat conversation inside your app, collects a privacy-aware device fingerprint, forwards push tokens, and shares one visitor identity across native and web.

The SDKs manage their own connection — there is nothing to host and no endpoint to configure.

What the SDKs do

  • Present the Lane.Chat conversation UI inside your app — link handling, share, and close are built in.
  • Collect basic device characteristics (device id, model, OS, locale) — no ad IDs, no contacts, no location.
  • Forward APNs/FCM tokens so Lane.Chat's servers send pushes; your signing key never ships in the app.
  • Bind identify() traits and a user id so one device maps to one visitor across native and web chat.
  • Surface replies while the chat is closed: system notifications, an in-app banner, or your own UI via event listeners (Android), plus event streams and push-tap routing on every platform.
AndroidiOSFlutter
LanguageKotlinSwiftDart
InstallMaven Central chat.lane:sdkSwift Package Managerpub.dev lanechat_flutter
MinimumminSdk 24, Kotlin 1.9+, Java 17iOS 15, Swift 5.9, Xcode 15Flutter 3.24, Dart 3.5
Push transportFCMAPNsFCM (both platforms, via your own firebase_messaging)

Android

Install

kotlin
dependencies {
    implementation("chat.lane:sdk:0.3.0")
}

Bootstrap

kotlin
class MyApp : Application() {
    override fun onCreate() {
        super.onCreate()
        LaneChat.initialize(this, appId = "YOUR_APP_KEY")
    }
}

For link policy, WebView tuning, or the open animation, pass a full LaneChatConfig instead:

kotlin
LaneChat.initialize(this, LaneChatConfig(
    appId = "YOUR_APP_KEY",
    linkPolicy = LinkPolicy(httpMode = HttpMode.IN_APP), // or EXTERNAL
    chatOpenAnimation = ChatOpenAnimation.SLIDE_UP,
    debug = BuildConfig.DEBUG,
))

LaneChatConfig fields: appId (required), endpoint, widgetBaseUrl, linkPolicy, webView, debug, requestTimeoutMs, chatOpenAnimation.

Identify and open chat

kotlin
LaneChat.identify(
    userId = "alice_123",
    traits = mapOf("plan" to "pro", "email" to "[email protected]", "order_id" to "X-42")
)

LaneChat.showChat(context)   // agents see plan / email / order_id alongside the chat

Prefer embedding? Use LaneChat.chatFragment() inside your own nav host.

Push (FCM)

Register the bundled messaging service in AndroidManifest.xml:

xml
<service
    android:name="chat.lane.sdk.LaneChatMessagingService"
    android:exported="false">
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT" />
    </intent-filter>
</service>

It forwards new FCM tokens (LaneChat.setDeviceToken) and renders Lane.Chat pushes. If your app already has its own FirebaseMessagingService, keep it and delegate instead — isLaneChatPush(data) tells you whether a payload is Lane.Chat's, and handlePush(context, data) renders it and returns true when it was:

kotlin
override fun onNewToken(token: String) = LaneChat.setDeviceToken(token)

override fun onMessageReceived(message: RemoteMessage) {
    if (LaneChat.handlePush(this, message.data)) return  // was ours, rendered
    // ... your own push handling
}

Android 13+ requires the runtime POST_NOTIFICATIONS permission. The SDK declares it but never prompts on its own — prompt timing is a UX decision only your app can make. Call this wherever fits your onboarding:

kotlin
val granted = LaneChat.ensureNotificationPermission(activity)
// false = the system dialog was just shown; observe the result in
// onRequestPermissionsResult keyed on LaneChat.NOTIFICATION_PERMISSION_REQUEST_CODE

Background listening

By default the SDK only reacts to chat activity while the chat UI is open. setBackgroundListening adds a native presence WebSocket that stays connected while your app is in the foreground and the chat is closed, so a reply surfaces without the user having chat open:

kotlin
LaneChat.setBackgroundListening(true, NoticeStyle.SYSTEM_NOTIFICATION)

Off by default — no socket, no battery or network use until you call it. The NoticeStyle argument controls how the message is surfaced:

StyleBehavior
SYSTEM_NOTIFICATION (default)A regular system notification; deduplicated against an FCM push for the same message.
IN_APP_BANNERA lightweight banner the SDK draws over your current Activity. Auto-dismisses after ~5 s; tap opens chat directly into the conversation.
NONENothing rendered — build your own UI from LaneChatEventListener callbacks.

The two layers are independent and commonly both on: presence covers "app open, chat closed" instantly; FCM push covers backgrounded and killed apps.

kotlin
LaneChat.addListener(object : LaneChatEventListener {
    override fun onUnreadCountChanged(count: Int) { updateBadge(count) }
    override fun onMessageReceived(type: String, excerpt: String, isAi: Boolean) { }
    // Also: onSessionLoaded, onChatOpened, onChatClosed, onMessageSent
})

Callbacks arrive on the main thread; excerpt is a preview, never the full message body.

Links tapped inside the chat route in three tiers: your setLinkHandler { url, source -> Boolean } gets first refusal (return true to consume — in-app routing, deeplinks); non-http(s) schemes go to the OS; and http(s) opens per LinkPolicy.httpModeIN_APP (Chrome Custom Tabs, default) or EXTERNAL (system browser). source is one of message, rich_button, kb_article, kb_citation, tg_binding, powered_by, navigation, home_link.

Chat open animation

ChatOpenAnimation controls how the chat screen animates in and out: SLIDE_UP (default), FADE, SYSTEM (platform default transition), or NONE. Set it in LaneChatConfig, or change it at runtime — it takes effect on the next open:

kotlin
LaneChat.setChatOpenAnimation(ChatOpenAnimation.FADE)

Android public API

MethodPurpose
LaneChat.initialize(context, config)Bootstrap with a full LaneChatConfig.
LaneChat.initialize(context, appId, endpoint?)Convenience overload.
LaneChat.identify(userId, traits?)Attach a logged-in user (persisted across restarts).
LaneChat.setSessionData(data)Merge context into the current conversation only. See Two levels of visitor data.
LaneChat.showChat(context)Launch chat as a full-screen Activity.
LaneChat.chatFragment()Embed chat as a Fragment.
LaneChat.setDeviceToken(token)Forward an FCM token.
LaneChat.isLaneChatPush(data) / LaneChat.handlePush(context, data)Push triage for apps with their own messaging service.
LaneChat.setPushEnabled(enabled)Opt this device out of / back into push (unregisters or re-registers the current token server-side).
LaneChat.ensureNotificationPermission(activity)Android 13+: request POST_NOTIFICATIONS if not yet granted.
LaneChat.setBackgroundListening(enabled, notice?)Foreground presence socket + notice style.
LaneChat.setChatOpenAnimation(style)Change the open/close animation at runtime.
LaneChat.preload()Network warm-up (DNS/TLS/CDN) for the widget host ahead of the first open.
LaneChat.track(event, properties?)Custom analytics event.
LaneChat.reset()Clear identity and session on logout; restarts anonymous.
LaneChat.addListener(listener) / removeListener(listener)Chat lifecycle callbacks.
LaneChat.setLinkHandler(handler)Intercept links clicked inside the chat.
LaneChat.onWebViewCreatedRaw WebView escape hatch, runs after SDK settings apply.

All methods are thread-safe; network I/O runs on a background coroutine scope and calls return immediately. identify, setSessionData, track, and push registration wait for the session to exist instead of being dropped, so they are safe to call right after initialize.

iOS

Install (Swift Package Manager)

In Xcode: File → Add Package Dependencies…, add the Lane.Chat iOS SDK package, and select the LaneChatSDK product. Requires iOS 15+, Swift 5.9, Xcode 15. The SDK has zero third-party dependencies — pure Apple frameworks.

Bootstrap

swift
import LaneChatSDK

// In AppDelegate.didFinishLaunchingWithOptions:
LaneChat.initialize(appId: "YOUR_APP_KEY")

// Forward APNs tokens:
func application(_ application: UIApplication,
                 didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    LaneChat.setDeviceToken(deviceToken)
}

For link policy or WebView tuning, pass a full config:

swift
LaneChat.initialize(LaneChatConfig(
    appId: "YOUR_APP_KEY",
    linkPolicy: LaneChatLinkPolicy(httpMode: .inApp), // or .external
    debug: true
))

LaneChatConfig fields: appId (required), endpoint, widgetBaseUrl, linkPolicy, debug, webView.

Identify and open chat

swift
LaneChat.identify(userId: currentUser.id, traits: [
    "plan": currentUser.plan,
    "email": currentUser.email,
    "order_id": "X-42"
])

LaneChat.showChat(from: self)   // agents see the traits alongside the chat

Push (APNs)

  1. Enable Push Notifications for your App ID and add the remote-notification background mode.

  2. Request authorization and register (helper included):

    swift
    PushRegistration.requestAuthorization { granted, _ in
        // iOS calls didRegisterForRemoteNotificationsWithDeviceToken on success
    }
  3. Forward the token with LaneChat.setDeviceToken(_:) (above).

Route a push tap back into the conversation — Lane.Chat pushes are tagged lane_chat and may carry a deep link:

swift
func userNotificationCenter(_ center: UNUserNotificationCenter,
                            didReceive response: UNNotificationResponse,
                            withCompletionHandler completionHandler: @escaping () -> Void) {
    let userInfo = response.notification.request.content.userInfo
    if let root = UIApplication.shared.connectedScenes
        .compactMap({ ($0 as? UIWindowScene)?.keyWindow?.rootViewController })
        .first {
        PushRegistration.handlePush(userInfo, from: root)  // presents chat + routes the deep link
    }
    completionHandler()
}

iOS public API

MethodPurpose
LaneChat.initialize(appId:) / LaneChat.initialize(_ config:)Bootstrap. Call once at launch.
LaneChat.identify(userId:traits:)Bind an end-user. Waits for the session — safe right after initialize.
LaneChat.setSessionData(_:)Merge context into the current conversation only. See Two levels of visitor data.
LaneChat.showChat(from:)Present the chat full-screen (main thread).
LaneChat.chatViewController()Get a UIViewController for custom embedding.
LaneChat.setDeviceToken(_:)Forward the raw APNs token (Data).
LaneChat.setPushEnabled(_:)Opt this device out of / back into push.
LaneChat.track(_:properties:)Custom events.
LaneChat.reset()Wipe identity on logout; restarts an anonymous session.
LaneChat.addListener(_:) / removeListener(_:)Multicast LaneChatDelegate events (laneChatOpened, laneChatClosed, laneChatMessageReceived, laneChatUnreadCountChanged, …). Weakly held, main-thread.
LaneChat.setLinkHandler(_:)Intercept links before the SDK opens them; return true to consume.
LaneChat.webViewConfigurator / LaneChat.onWebViewCreatedRaw WebView escape hatches.
PushRegistration.requestAuthorization(options:completion:)Notification auth + register helper.
PushRegistration.isLaneChatPush(_:) / .deepLinkURL(from:) / .handlePush(_:from:)Push triage and tap handling.

The SDK is thread-safe; UI calls (showChat) must run on the main thread. Non-http(s) link schemes go to the system; http(s) opens per linkPolicy.httpMode.inApp (SFSafariViewController, default) or .external (system browser).

Flutter

Lane.Chat ships an official Flutter SDK — pure Dart on top of webview_flutter, no custom platform code to vendor, with the same API names as the Android and iOS SDKs.

Install

flutter pub add lanechat_flutter

Bootstrap, identify, open chat

dart
import 'package:lanechat_flutter/lanechat_flutter.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await LaneChat.initialize('YOUR_APP_KEY');
  runApp(const MyApp());
}

// After login:
await LaneChat.identify('alice_123', traits: {'plan': 'pro', 'email': '[email protected]'});

// From any button:
LaneChat.showChat(context);

showChat pushes a full-screen LaneChatChatPage. To place chat inside your own layout, embed LaneChatView and decide what "close" means:

dart
LaneChatView(onCloseRequested: () => Navigator.of(context).maybePop())

Events

Everything the widget reports is available as typed streams:

dart
LaneChat.onUnreadChanged.listen((count) => setBadge(count));
LaneChat.onMessageReceived.listen((m) => debugPrint('${m.type}: ${m.excerpt}'));
// Also: LaneChat.events, onSessionLoaded, onChatOpened, onChatClosed, onMessageSent

Push (FCM)

The SDK does not depend on firebase_messaging — bring your own and forward tokens:

dart
final messaging = FirebaseMessaging.instance;
await messaging.requestPermission();
final token = await messaging.getToken();
if (token != null) await LaneChat.setDeviceToken(token);
messaging.onTokenRefresh.listen(LaneChat.setDeviceToken);

iOS + firebase_messaging: use getToken(), never the raw APNs token

On iOS, firebase_messaging exposes both an APNs device token (getAPNSToken()) and an FCM registration token (getToken()). Lane.Chat's push registration expects the FCM token on both platforms; a raw APNs token fails validation with a 422 and push silently never arrives.

Route notification taps back into the chat:

dart
void openChatOnTap(Map<String, dynamic> data) {
  if (!LaneChat.isLaneChatPush(data)) return;   // not ours
  LaneChat.handlePushTap(data);                 // stores the deep link
  LaneChat.showChat(context);                   // delivers it once the widget is ready
}

Flutter public API

MemberPurpose
LaneChat.initialize(appId, {config})Bootstrap. Call once at launch.
LaneChat.identify(userId, {traits})Attach a logged-in user.
setSessionDataNot available on Flutter yet — see Two levels of visitor data.
LaneChat.showChat(context)Push the full-screen chat page.
LaneChatView / LaneChatChatPageEmbeddable / full-screen chat UI.
LaneChat.setDeviceToken(token)Forward an FCM token.
LaneChat.setPushEnabled(enabled)Opt this device out of / back into push.
LaneChat.isLaneChatPush(data) / handlePushTap(data)Push triage and tap handling.
LaneChat.setLinkHandler(handler)First step of link routing.
LaneChat.events + typed streamsWidget event bus.
LaneChat.track(eventName, {properties})Custom events.
LaneChat.reset()Clear identity on logout; restarts anonymous.

LaneChatConfig fields: endpoint, widgetBaseUrl, linkPolicy, webView, debug — link routing works like the native SDKs (your handler first, then non-http(s) to the system, then http(s) per LaneChatHttpMode.inApp / .external).

Push: how the pieces fit

You never ship signing keys in the app. Upload your credentials once on the dashboard, and Lane.Chat's servers dispatch pushes to the tokens the SDK registers:

  • Android / Flutter — in Firebase, generate a service-account private key (JSON) and upload it in the dashboard under Channels → App Push.
  • iOS — enable push for your App ID, generate an APNs Auth Key (.p8, preferred) or certificate (.p12), and upload it in the dashboard.

The server checks visitor presence before sending: while the app is in the foreground with a live connection, pushes for that visitor are suppressed — the user sees the in-app surface instead of a duplicate notification. setPushEnabled(false) unregisters the device token server-side and stops local rendering; setPushEnabled(true) re-registers it.

Two levels of visitor data

Agents see two layers of context next to a conversation, and the SDK has one method for each. Which one you want comes down to a single question: does this still matter the next time they get in touch?

identify(userId, traits)setSessionData(data)
AnswersWho is this person?What is this conversation about?
LifetimeFollows the user across every future conversationThis conversation only
Typical keysplan, email, signup_date, mrrorder_id, cart_total, screen, error_code

User data is the durable layer. Call identify() once at login and forget about it; the user id is persisted across app restarts and the traits are re-sent every time the chat opens, so a later call is always reflected.

Session data is the here-and-now. Set it as the user moves around your app, right before they reach out:

Android:

kotlin
LaneChat.setSessionData(mapOf("order_id" to "X-42", "cart_total" to 129.5))
LaneChat.showChat(context)

iOS / macOS:

swift
LaneChat.setSessionData(["order_id": "X-42", "cart_total": 129.5])
LaneChat.showChat(from: self)

Keys are merged, not replaced. A later call carrying screen leaves order_id and cart_total in place and adds screen, so each part of your app can contribute what it knows without resending everything. An existing key changes only when a later call names it, and passing a null value drops a key.

Both calls are cheap and never reload the chat, and both work the same whether the chat is open or closed. They do need the SDK running: call initialize() first — calls made before that are logged and dropped rather than queued.

Flutter

setSessionData is not available in the Flutter package yet. Put the values you need on identify() traits in the meantime; agents still see them, they just persist on the visitor instead of expiring with the conversation.

Shared identity across native and web

The SDK carries its device identity and your identify()'d user_id into the chat session, so the server reuses the same visitor record the SDK created — one device, one visitor identity across native and web. Traits show up for agents in the visitor panel. Call identify() before or after opening chat; the chat URL is rebuilt on every open, so the latest identity always applies.

Zero endpoint configuration

The SDK manages its own session with Lane.Chat's servers — you never configure a backend URL, chat host, or socket endpoint. The current chat host page and realtime endpoint are delivered by the server at session start and persisted by the SDK, so Lane.Chat can migrate infrastructure without you shipping an app update. Transient failures recover on their own: identity, events, and push registration wait for the session instead of being dropped, and an expired session is transparently re-established.

Session starts and custom events have per-app quotas; normal production traffic does not come near them.

Privacy

The SDKs collect only basic device characteristics (device id, OS, model, locale, timezone, screen, network type, app/SDK version). No advertising IDs, IMEI/OAID, carrier name, location, or contacts — nothing that requires an extra runtime permission. The same device resolves to the same visitor on every platform.

App store privacy declarations

Everything the SDK collects is for app functionality only — no tracking, no sharing with third parties, no advertising use. Copy these answers into the store forms as-is:

Google Play — Data safety form

  • Device or other IDs → Collected, purpose App functionality, not shared, not processed ephemerally.
  • If you call identify(...): Personal info → User IDs → collected, App functionality, not shared.
  • Messages → Other in-app messages (the support chat itself) → collected, App functionality, not shared.

The Android SDK declares only INTERNET, ACCESS_NETWORK_STATE, and POST_NOTIFICATIONS; it does not use the advertising ID.

App Store Connect — App Privacy

  • Identifiers → Device ID → collected, App Functionality, linked to the user, not used for tracking.
  • If you call identify(...): Identifiers → User ID → same answers.
  • User Content → Other User Content (chat messages) → collected, App Functionality, linked, no tracking.

The iOS package ships a privacy manifest (PrivacyInfo.xcprivacy) with these same declarations and NSPrivacyTracking = false, so Xcode's aggregated privacy report already accounts for the SDK. No App Tracking Transparency prompt is needed.

Lane.Chat — AI customer support, multichannel CRM, and roleplay AI.