kindly 2.0.33
kindly: ^2.0.33 copied to clipboard
Kindly Chat SDK for Flutter - customer support chat widget for iOS and Android. Successor to the kindly_sdk package.
Kindly Chat SDK for Flutter #
Kindly Chat SDK for Flutter — customer support chat widget for iOS and Android applications. Thin Flutter facade over the public Kindly iOS and Android SDKs.
The published package ships the native iOS xcframework and Android AAR inline, so consumer apps don't need to wire up Swift Package Manager or JitPack themselves.
Features #
- Customisable themes and colours
- Authentication via JWT (callback-based)
- Multi-language support
- Real-time chat
- Push notifications (APNS + FCM)
- Trigger specific dialogues / deep-link URLs
- Programmatic message send
- Context attached to next message or button click
- Native iOS and Android implementations bundled — no extra setup
Requirements #
- Flutter >= 3.0 (Dart >= 2.17)
- iOS >= 17.2 (matches the bundled xcframework — older targets fail at launch with missing dyld symbols)
- Android
minSdk>= 21, plugin builds withcompileSdk 35(matches android-source)
Installation #
Add kindly to your pubspec.yaml:
dependencies:
kindly: ^2.0.0
Then flutter pub get.
Migrating from
kindly_sdk? This package is the renamed successor tokindly_sdk. The public API is identical — only the package name changed. To migrate: bump the dep fromkindly_sdktokindly: ^2.0.0, and replaceimport 'package:kindly_sdk/kindly_sdk.dart'withimport 'package:kindly/kindly.dart'. Nothing else needs to change.
iOS Setup #
No additional setup required. The package vendors a signed KindlySDK.xcframework and CocoaPods links it automatically when Flutter runs pod install. (The framework is named KindlySDK rather than Kindly so it doesn't case-collide with the kindly pod on macOS's case-insensitive filesystem.)
Android Setup #
No additional setup required. The package ships a kindlysdk.aar that Gradle picks up directly via implementation files("libs/kindlysdk.aar").
Note on manifest merger. The bundled AAR declares
android:label="@string/app_name"on its<application>element. If your app'sAndroidManifest.xmlalso setsandroid:label, Android's manifest merger fails with "Attribute application@label is also present at [kindlysdk.aar]". To resolve, addtools:replace="android:label"to your<application>tag (and declarexmlns:tools="http://schemas.android.com/tools"on the root<manifest>if not already):<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <application android:label="My App" … tools:replace="android:label"> … </application> </manifest>
Usage #
Basic Setup #
import 'package:kindly/kindly.dart';
await KindlySDK.start(
botKey: 'YOUR_BOT_KEY',
);
await KindlySDK.displayChat();
Authentication #
await KindlySDK.start(
botKey: 'YOUR_BOT_KEY',
authTokenCallback: () async {
final token = await yourAuthService.getToken();
return token;
},
);
Custom Theme #
await KindlySDK.setCustomTheme(
background: Colors.white,
botMessageBackground: Colors.grey[200],
botMessageText: Colors.black,
userMessageBackground: Colors.blue,
userMessageText: Colors.white,
buttonBackground: Colors.blue,
buttonText: Colors.white,
navBarBackground: Colors.blue,
navBarText: Colors.white,
inputBackground: Colors.grey[100],
inputText: Colors.black,
);
// Revert to default
await KindlySDK.clearCustomTheme();
Context Data #
await KindlySDK.setNewContext({
'userId': '12345',
'userName': 'John Doe',
'email': 'john@example.com',
'plan': 'premium',
});
await KindlySDK.clearNewContext();
Trigger Specific Dialogues #
// Launch chat with a specific dialogue
await KindlySDK.launchChat(triggerDialogueId: 'welcome_dialogue');
// Or trigger a dialogue inside an active chat
await KindlySDK.triggerDialogue('help_dialogue');
// Clear any pending trigger (Android only)
await KindlySDK.clearTriggerDialogue();
Deep links #
The SDK recognises kindly://chat/dialogue/{id} URLs.
final handled = await KindlySDK.handleUrl('kindly://chat/dialogue/welcome');
Programmatic Message Send #
Send a message on behalf of the user. The SDK renders the bubble locally and reconciles the server-assigned id when the POST returns — do not also display the message yourself.
final reconciled = await KindlySDK.sendMessage(
'Hello there',
newContext: {'priority': 'high'},
);
print(reconciled?['id']); // server id
Handover to a Human Agent #
Initiate a handover and receive a callback when the handover begins.
await KindlySDK.callHandover(() {
print('Handover initiated');
});
Push Notifications #
iOS
await KindlySDK.setAPNSDeviceToken(deviceToken);
await KindlySDK.handleNotification(notificationData);
final isKindly = await KindlySDK.isKindlyNotification(notificationData);
Android
await KindlySDK.saveNotificationToken(fcmToken);
await KindlySDK.handleNotification(remoteMessage.data);
final isKindly = await KindlySDK.isKindlyNotification(remoteMessage.data);
Other Features #
// Check chat UI state (iOS only)
final isDisplayed = await KindlySDK.isChatDisplayed;
// Update language for the active session
await KindlySDK.setLanguage('no');
// Save a JWT to the platform keychain (iOS only)
await KindlySDK.saveAuthToken('jwt-here');
// Toggle SDK logging / Sentry crash reporting
await KindlySDK.setVerboseLogging(true);
await KindlySDK.setCrashReporting(true);
// Close UI without ending the session
await KindlySDK.closeChat();
// End the active session (clears messages + token, dismisses UI)
await KindlySDK.endChat();
// Tear the SDK down completely — must call start() again before reuse
await KindlySDK.kill();
API Reference #
Lifecycle #
start({botKey, languageCode?, authTokenCallback?})— initialise the SDK. First call before anything else.displayChat({languageCode?, triggerDialogueId?})— show the chat UI.launchChat({triggerDialogueId?})— connect (if needed) and show the chat UI. Recommended on Android; routed todisplayChaton iOS.closeChat()— dismiss the UI without ending the session.endChat()— end the active session, clear messages + token, dismiss UI.kill()— fully tear down the SDK;start()must be called again before reuse.setLanguage(languageCode)— change language for the current session.
Messaging #
sendMessage(text, {newContext?})— send a message programmatically; resolves with{id, text, created, sender, ...}.setNewContext(context)— stageMap<String, String>context for the next message or click.clearNewContext()— clear staged context.triggerDialogue(dialogueId)— trigger a dialogue by id (must be connected).clearTriggerDialogue()— clear pending trigger (Android only).handleUrl(url)— handlekindly://chat/dialogue/{id}deep links; returnsbool.callHandover(callback)— request handover to a human agent (Android only).
Theme #
setCustomTheme({...colors})— override individual theme slots (background,botMessageBackground,botMessageText,userMessageBackground,userMessageText,buttonBackground,buttonText,buttonOutline,buttonSelectedBackground,navBarBackground,navBarText,inputBackground,inputText,inputCursor,chatLogElements,defaultShadow,maintenanceHeaderBackground).clearCustomTheme()— revert to the default / API theme.
Push #
setAPNSDeviceToken(token)— register the APNS token (iOS only).saveNotificationToken(token)— register the FCM token (Android only).handleNotification(data)— forward a push to the SDK.isKindlyNotification(data)—trueif the push originated from Kindly.
State & settings #
isChatDisplayed—Future<bool>; iOS only (Android always returnsfalse— track via host UI state).saveAuthToken(token)— persist a JWT to the platform keychain (iOS).setVerboseLogging(bool)— toggle verbose logging.setCrashReporting(bool)— toggle Sentry crash reporting.
License #
This project is licensed under the MIT License — see the LICENSE file for details.
Maintenance #
Internal note for future maintainers — not relevant if you're integrating the SDK into your app.
This package wraps two native artefacts that are built from sibling repos and bundled inline:
ios/Frameworks/KindlySDK.xcframework— built from../ios-source/, signed with the teamG2P4AFMWD6Apple Distribution cert. (Renamed from upstreamKindly.frameworkduring bundling — seeAGENTS.mdfor the why.)android/libs/kindlysdk.aar— built from../android-source/via./gradlew :kindlysdk:assembleRelease -PsdkMode=true.
There are two release paths:
- Manual local (
make publishfrom a developer laptop) — used when the bridge layer (lib/kindly.dart+ the twoKindlyPluginfiles) needs hand-edits becauseEntryPoint.swift/EntryPoint.ktchanged. - Cross-repo CI (automatic on every iOS / Android source release) —
ios-source/.github/workflows/deploy-sdk.ymlandandroid-source/.github/workflows/deploy-sdk.ymlclone this repo, rebundle the corresponding native binary, bump pubspec.yaml's patch, anddart pub publish— keepingkindlyin sync with the latest native bug fixes without any manual step.
See docs/flutter-sdk-release-architecture.md for the full diagram of the auth model, secrets, and how to flip between architectures.
make setup # one-time: install Ansible / coreutils / etc.
make bundle # build-only: refresh the xcframework + AAR (no publish)
make publish-dry # bundle + validate via `dart pub publish --dry-run`
make publish # bundle + validate + confirmation prompt + upload + tag + bump
make publish is the canonical release command. It runs a single Ansible invocation that bundles, validates, prompts before the irreversible upload, then publishes + tags + bumps. make bundle is a stand-alone convenience for build-only work — make publish doesn't need it.
Version cycle (mirrors the iOS pipeline) #
pubspec.yaml's version: field is the canonical source of truth, equivalent to iOS's .bumpversion.cfg. The release flow is:
- Whatever version is in
pubspec.yamlright now is what gets published. Ifpubspec.yamlsays1.1.0,make publishpublishes1.1.0. - Right after a successful
dart pub publish, the playbook tags the released commitv1.1.0and pushes the tag. - It then auto-increments the patch (
1.1.0→1.1.1), commits the bump with messageBump version for next release, and pushes that commit on the current branch. - So at any point in time,
HEADof your branch carries the next version waiting to be released, and the most recent release tag is one patch behind.
For a minor or major bump, edit pubspec.yaml and CHANGELOG.md by hand before running make publish — same model as bumping .bumpversion.cfg manually on iOS before make deploy-sdk. Patch bumps are automatic.
Cert prerequisite: the bundling step requires the Apple Distribution cert for team G2P4AFMWD6 to already be in your login keychain. If you regularly release the iOS SDK from ../ios-source/, you have it. Otherwise run once: cd ../ios-source && bundle exec fastlane match development --readonly.
The plugin's bridge layer (lib/kindly.dart + ios/Classes/KindlyPlugin.swift + android/src/main/kotlin/ai/kindly/flutter_sdk/KindlyPlugin.kt) is a thin facade over the public EntryPoint API of each native SDK. If a method isn't on EntryPoint, it isn't reachable from Flutter — that's by design.
When the iOS or Android SDK changes:
- No public-API change (bug fix, internal refactor): just
make publish(patch bumps automatically). - EntryPoint API change (new method, signature change): update the three bridge files first, optionally hand-bump minor/major in
pubspec.yaml, thenmake publish.
Manual ansible-playbook invocations bundle by default too — you can pass -e skip_bundle=true to skip the bundle step (only useful when iterating on the publish playbook itself).
See scripts/README.md for what each playbook does.