Add calls, chat and a prebuilt call UI to your Flutter app. The SDK wraps signalling (ring/accept), media (LiveKit) and chat behind one small client — with a drop-in UI-Kit, or bring your own.
Install
1flutter pub add socrits livekit_client permission_handlerAdd platform permissions (Android needs min SDK 21):
1<!-- android/app/src/main/AndroidManifest.xml -->2<uses-permission android:name="android.permission.INTERNET"/>3<uses-permission android:name="android.permission.CAMERA"/>4<uses-permission android:name="android.permission.RECORD_AUDIO"/>5<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>6<uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>78<!-- ios/Runner/Info.plist -->9<key>NSCameraUsageDescription</key><string>Video calls</string>10<key>NSMicrophoneUsageDescription</key><string>Voice calls</string>Initialize & connect
Create the client with your Socrits base URL and connect the signalling socket. The app authenticates with a session token your backend mints — see Server & tokens.
1import 'package:socrits/socrits.dart';2import 'package:livekit_client/livekit_client.dart' as lk;34final client = Socrits(baseUrl: 'https://api.socrits.com');56// Connect the signalling socket. Your backend issues the user's session token7// (POST /v1/session with your sk_live_ key) — never embed the key in the app.8await client.connect(9 sessionEndpoint: 'https://your-backend.com/socrits/session', // returns { token }10 userId: 'alice',11 userName: 'Alice',12);13// (or, if you already have a token) await client.connect(userToken: token, userId: 'alice');Set up callbacks
1// Wire these once, right after connect()2client.onIncomingCall = (IncomingCall call) {3 // show your ringing screen, then accept or reject:4 // final room = await client.accept(call);5 // client.reject(call);6};7client.onCallConnected = (lk.Room room, CallMode mode) {8 // both sides are in the room now — show the call screen (see below)9};10client.onCallRejected = (callId) { /* callee declined */ };11client.onCallEnded = (reason) { /* other side hung up / left */ };1213// Offline ringing: register the device's FCM token so Socrits can push a ring14client.registerDevice(fcmToken, platform: 'android');Make & receive a call
1// Caller: start a call. Returns a callId; the callee gets onIncomingCall.2final callId = await client.invite(3 toUserId: 'bob',4 mode: CallMode.video, // or CallMode.voice5);67// Callee: inside onIncomingCall8final room = await client.accept(call); // joins the media room9// onCallConnected fires on BOTH sides with the connected lk.Room.Option A — with the UI-Kit
SocritsCallView is a complete WhatsApp-style call screen: remote video full-screen, your camera in a PiP, a live timer, and Speaker / Flip / Video / Mute / End controls (voice calls show a pulsing avatar). Hand it the connected room and you’re done.
1import 'package:socrits/socrits.dart';23client.onCallConnected = (room, mode) {4 Navigator.of(context).push(MaterialPageRoute(5 builder: (_) => Scaffold(6 body: SocritsCallView(7 room: room,8 title: 'Bob',9 initialCam: mode == CallMode.video,10 initialMic: true,11 initialSpeaker: mode == CallMode.video, // video → speaker, voice → earpiece12 onHangUp: () => Navigator.of(context).pop(),13 ),14 ),15 ));16};Widget parameters
room— the connectedlk.Roomfromaccept()/onCallConnected. Required.title— name shown for the other party.initialMic/initialCam— starting mic/camera state (default true).initialSpeaker— start on loudspeaker (true) or earpiece. Defaulttrue; set it from your ring screen (video → speaker, voice → earpiece). Added in 0.1.7.onHangUp— called when the user taps End or the call ends; pop your screen here.
Option B — without the UI-Kit
Prefer your own design? accept() and onCallConnected give you the raw LiveKit Room. Control tracks and render video yourself with livekit_client:
1// You get the raw LiveKit Room from accept() / onCallConnected — build any UI you want.2final lk.Room room = await client.accept(call);34// controls5await room.localParticipant?.setMicrophoneEnabled(true);6await room.localParticipant?.setCameraEnabled(mode == CallMode.video);78// render remote video9final listener = room.createListener();10listener.on<lk.TrackSubscribedEvent>((e) {11 if (e.track is lk.VideoTrack) {12 setState(() => remote = e.track as lk.VideoTrack);13 }14});15// in build(): if (remote != null) lk.VideoTrackRenderer(remote!)1617// end the call18await room.disconnect();Offline ringing
When the callee’s app is backgrounded or killed its socket is paused, so a socketonIncomingCall never arrives. Socrits instead sends a high-priority FCM data push, and SocritsCallKit turns it into the native full-screen incoming-call screen (over the lock screen on Android, CallKit on iOS). Registering the FCM token alone is not enough — wire these four steps:
1// socrits bundles flutter_callkit_incoming; you also need firebase_core +2// firebase_messaging for the push itself.3import 'package:firebase_messaging/firebase_messaging.dart';4import 'package:socrits/socrits.dart';56// 1) Top-level FCM background handler — runs when the app is killed/backgrounded.7@pragma('vm:entry-point')8Future<void> _bg(RemoteMessage m) async {9 await SocritsCallKit.onPushData(m.data); // raises the native full-screen ring10}1112void main() {13 FirebaseMessaging.onBackgroundMessage(_bg);14 // 2) Foreground pushes (harmless if your live socket already rang):15 FirebaseMessaging.onMessage.listen((m) => SocritsCallKit.onPushData(m.data));16 runApp(const MyApp());17}1// 3) Route the OS Accept / Decline — register ONCE, high in the widget tree.2SocritsCallKit.appName = 'MyApp';3SocritsCallKit.listen(4 onAccept: (callId, extra) async {5 // reconnect first if the app was cold-started by the ring:6 await client.connect(userToken: await mintToken(), userId: me);7 await client.acceptCallById(callId); // joins the room → onCallConnected fires8 },9 onDecline: (callId) => client.rejectCallById(callId),10);1112// 4) After connect(), register this device's FCM token so Socrits can ring it:13client.registerDevice(14 await FirebaseMessaging.instance.getToken(),15 platform: 'android',16);SocritsCallKit.onPushData(data)— turns a Socrits FCM data message into the native ring. Safe to call from the background isolate.SocritsCallKit.listen(onAccept, onDecline)— routes the OS Accept/Decline back to your client;onAcceptreconnects (if cold-started) then callsacceptCallById.client.acceptCallById(callId)/rejectCallById(callId)— answer a push-delivered call that had no prioronIncomingCall.registerDevice(fcmToken)— call afterconnect()so Socrits knows where to push.
google-services.json into your app too. Without this, registerDevice() stores a token Socrits can’t reach. Fire a test ring from Development Tools → Push debug.flutter_callkit_incoming (bundled with socrits) needs its full-screen-intent activity + broadcast receiver and the call permissions in your Android manifest — see its README. And on Xiaomi / Oppo / Vivo, enable Autostart and set battery to “No restrictions” for your app, or the OS blocks the wake-up push while the app is killed.Chat
1// 1:1 or group chat rides the same connection2client.onMessage = (ChatMessage m) { /* append to the thread */ };34await client.sendMessage(toUserId: 'bob', text: 'Hey!');5final history = await client.messageHistory(conversationId, limit: 30);6final convos = await client.conversations();7client.setTyping(conversationId, true);8client.markRead(conversationId);