BaxCloud Calls UI Kit
Flutter UI kit for 1:1 and group audio/video calls with native ringing UI, server-mediated invites, and bring-your-own FCM / APNs VoIP push.
Package: baxcloud_calls_uikit_sdk
Requires: baxcloud_core · Flutter 3.10+
Features
- Outgoing & incoming calls (audio / video)
- Group invites via
toUserIds - Native ringing UI (iOS system call UI, full-screen incoming on Android)
- FaceTime-style in-call UI with configurable layouts (
faceTime/grid/carousel/audio) - Accept / decline / cancel / end over the BaxCloud Calls API
- Push registration helpers (
android/ios/ios_voip) - In-call UI via
BaxCallView(or bring your own screen) BaxCallControllerfor mute / camera / flip / speaker / hang-up- Call lifecycle events stream
- Automatic analytics device metadata (via
baxcloud_core) - Native CallKit / notification strings overridable via
BaxCallStrings(i18n)
Documentation
| Resource | Link |
|---|---|
| Calls UI Kit guide (install, native ringing, push, events) | baxcloud.tech/docs/uikits/calls |
| Push setup (FCM + APNs VoIP) | Push setup section |
| UI Kits overview | baxcloud.tech/docs/uikits |
| API authentication | baxcloud.tech/docs/api/authentication |
| Dashboard | baxcloud.tech/dashboard |
Installation
dependencies:
baxcloud_core: ^0.1.5
baxcloud_calls_uikit_sdk: ^0.1.1
flutter pub get
Use a client key (bax_pk_…) with the appropriate media / calls scopes from the dashboard.
Quick start
import 'package:baxcloud_core/baxcloud_core.dart';
import 'package:baxcloud_calls_uikit_sdk/baxcloud_calls_uikit_sdk.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await BaxCalls.initialize(
config: BaxConfig(
projectId: 'your-project-id',
apiKey: 'bax_pk_your_client_key',
// Optional analytics overrides:
// appVersion: '1.2.3',
// buildNumber: '45',
// bundleId: 'com.example.app',
),
localUser: const BaxcloudUser(
userId: 'u1',
name: 'Ada',
avatarUrl: 'https://example.com/ada.png',
metadata: {'role': 'caller', 'locale': 'en'},
),
theme: const BaxCallTheme(appName: 'MyApp'),
);
runApp(const MyApp());
}
Override native CallKit / notification copy for translations via BaxCallTheme.strings:
theme: BaxCallTheme(
appName: 'MyApp',
strings: BaxCallStrings(
textAccept: 'Aceitar',
textDecline: 'Recusar',
missedCallSubtitle: 'Chamada perdida',
missedCallCallback: 'Retornar',
callingSubtitle: 'Chamando…',
incomingCallChannelName: 'Chamadas recebidas',
missedCallChannelName: 'Chamadas perdidas',
),
),
Or patch defaults: const BaxCallStrings().copyWith(textAccept: 'Aceitar').
You can also assign BaxCalls.instance.theme = … when the locale changes (applies to the next ring / call).
Update local user
await BaxCalls.updateLocalUser(
BaxCalls.instance.localUser!.update(
name: 'Ada Lovelace',
metadata: {'locale': 'pt-BR'},
),
);
// Or patch fields directly
await BaxCalls.updateLocalUserFields(
name: 'Ada Lovelace',
metadata: {'locale': 'pt-BR'}, // merged into existing map
);
Start a 1:1 call
final invitation = await BaxCalls.startOutgoing(
toUserId: 'u2',
callType: BaxCallType.video,
);
// Navigate to in-call UI when accepted / connected (see Events).
Start a group call
await BaxCalls.startOutgoing(
toUserIds: ['u2', 'u3', 'u4'],
callType: BaxCallType.audio,
);
Show an incoming call from push
// FCM data or PushKit payload map
await BaxCalls.showIncoming(
BaxIncomingCall.fromPush(messageData),
);
In-call UI
Defaults are FaceTime-style for 1:1 video: front camera, full-screen remote, local PiP, floating circular controls.
BaxCallScope(
config: config,
child: BaxCallView(
roomName: invitation.roomName,
user: localUser,
callType: invitation.callType,
isGroup: invitation.isGroup,
isHost: true, // caller; callees typically omit / use false
canJoinWithNoHost: true,
uiConfig: const BaxCallUiConfig(
layout: BaxCallLayout.faceTime, // or grid / carousel / audio
cameraFacing: BaxCallCameraFacing.front,
fullScreen: true,
showLocalPip: true,
controlsStyle: BaxCallControlsStyle.floating, // or .bar
// hangUpColor: Color(0xFFFF3B30),
),
),
);
| Option | Values | Default |
|---|---|---|
layout |
faceTime, grid, carousel, audio |
Auto (faceTime / grid / audio) |
cameraFacing |
front, back |
front |
controlsStyle |
floating, bar |
floating |
fullScreen |
bool | true |
showLocalPip |
bool | true |
showDuration |
bool | true (live m:ss timer) |
showConnectionStatus |
bool | true (Video/Audio · Secure chips) |
Presets: BaxCallUiConfig.faceTime(), .groupGrid(), .audioOnly() — pass named overrides on the preset.
Call controller (mute / camera / speaker)
While a call is connected, drive media from your own buttons, app logic, or overlays:
final c = BaxCalls.instance.activeController;
await c?.setMuted(true);
await c?.toggleCamera();
await c?.flipCamera();
await c?.setSpeakerOn(true);
await c?.hangUp();
Inside BaxCallView / inCallBuilder, prefer:
session.controller— same APIBaxCallController.of(context)/maybeOf(context)under the call tree
BaxCallController is a ChangeNotifier — listen with AnimatedBuilder / ListenableBuilder for reactive UI.
Bring your own design
Four levels of customization:
- Theme / layout —
uiConfig(colors, FaceTime vs grid, camera facing) - Partial —
controlsBuilderand/orparticipantTileBuilder - Full chrome —
inCallBuilderwith kit media + your controls (recommended) - Own navigation only — skip
BaxCallViewafteraccept/connected(you own token + media)
The kit always joins the room when you use BaxCallView. Embed session.media and use session.controller — do not open a second media connection with session.credentials.
BaxCallView(
roomName: roomName,
user: user, // BaxcloudUser
callType: BaxCallType.video,
inCallBuilder: (context, session) {
return Stack(
fit: StackFit.expand,
children: [
session.media, // full-bleed video + name/duration chrome
Align(
alignment: Alignment.bottomCenter,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
IconButton(
icon: Icon(session.controller.isMuted ? Icons.mic_off : Icons.mic),
onPressed: () => session.controller.toggleMute(),
),
IconButton(
icon: const Icon(Icons.call_end, color: Colors.red),
onPressed: () => session.hangUp(),
),
],
),
),
],
);
},
);
See In-call UI design and UI design.
Listen to events
BaxCalls.instance.events.listen((event) {
switch (event.type) {
case BaxCallEventType.incoming:
case BaxCallEventType.outgoing:
case BaxCallEventType.ringing:
case BaxCallEventType.accepted:
case BaxCallEventType.connected:
case BaxCallEventType.declined:
case BaxCallEventType.cancelled:
case BaxCallEventType.ended:
case BaxCallEventType.missed:
case BaxCallEventType.timeout:
case BaxCallEventType.muteChanged:
case BaxCallEventType.cameraChanged:
case BaxCallEventType.holdChanged:
case BaxCallEventType.error:
// Update your UI / navigation
break;
}
});
Push notifications (bring your own)
BaxCloud does not ship Firebase or Apple certificates. Configure optional credentials under Project → Features → Call push in the dashboard, then wire your app:
- Register device tokens after
BaxCalls.initialize:
await BaxCalls.registerPushToken(
fcmToken,
platform: BaxPushPlatform.android,
);
await BaxCalls.registerPushToken(
voipToken,
platform: BaxPushPlatform.iosVoip,
);
- On a call invite push (
type == bax_call_invite), callBaxCalls.showIncoming(...). - Check what the server can deliver:
final caps = await BaxCalls.refreshCapabilities();
// caps.fcmConfigured / caps.apnsVoipConfigured
// caps.coldStartAndroid / caps.coldStartIos
Without project FCM / APNs VoIP credentials, invites still return a pushPayload — your app or backend must deliver it. Full walkthrough: Push setup.
Foreground incoming poll (no-push fallback)
When FCM / PushKit is not configured (or for dev / two-device testing while the app is open), the kit can poll for ringing invites:
// Auto-starts after initialize() only when the project has no server push.
// Force on for dev / example apps:
BaxCalls.setIncomingPollEnabled(true);
Production apps with push should disable poll after registering tokens:
await BaxCalls.registerPushToken(fcmToken, platform: BaxPushPlatform.android);
BaxCalls.setIncomingPollEnabled(false);
Push and poll both call showIncoming — the kit deduplicates by invitationId so you won't get double rings. Poll does not replace push for background/killed state. Details: Incoming poll.
Platform setup
iOS
- Camera & microphone usage descriptions in
Info.plist - Background modes:
audio,voip,remote-notification - PushKit + VoIP capability for killed-state ringing
Android
CAMERA,RECORD_AUDIO,INTERNET,POST_NOTIFICATIONS- Full-screen intent + foreground service (
PHONE_CALL,CAMERA,MICROPHONE) /MANAGE_OWN_CALLSas required by your target SDK
See the platform section in the docs.
API surface (high level)
| App API | Purpose |
|---|---|
BaxCalls.initialize |
Config + local user + native ringing bridge |
BaxCalls.startOutgoing |
Invite 1:1 or group |
BaxCalls.showIncoming |
Ring from push / in-app |
BaxCalls.accept / decline / cancel / end |
Call actions |
BaxCalls.registerPushToken |
Register FCM / APNs / VoIP tokens |
BaxCalls.setIncomingPollEnabled |
Enable / disable foreground invite poll |
BaxCalls.refreshCapabilities |
Server push capability flags |
BaxCallView |
Default in-call media UI |
BaxCalls.instance.events |
Lifecycle stream |
BaxCallTheme / BaxCallStrings |
Native ring chrome + i18n copy |
See the Calls guide for the full lifecycle. Prefer kit methods over calling HTTP yourself.
Example app
cd example
flutter run \
--dart-define=BAXCLOUD_PROJECT_ID=… \
--dart-define=BAXCLOUD_API_KEY=bax_pk_…
Support
License
See LICENSE.
Libraries
- baxcloud_calls_uikit_sdk
- BaxCloud Call UI Kit.
- version