callx
Native incoming and outgoing calls for Flutter: iOS CallKit, Android Core-Telecom, VoIP and FCM push handling, and crash recovery in one shared native core. Calls ring even when your app is not running, answers are never lost on the lock screen, and cancelled calls never ring again.
Documentation · Flutter quick start · API reference · Status
Features
- Native owns the call. Pushes are received, reported to CallKit or Telecom, and every answer and hang-up is recorded natively, before the Flutter engine starts.
- Rings only when it should. Duplicate, expired, busy and already-cancelled invitations never ring; unanswered calls end at a ring deadline.
- Recovery built in. A durable journal, idempotent commands with explicit results, and replayable events after a crash, a hot restart or a reboot.
- Bring your own backend and media. No hosted service and no Firebase or media dependency. Add
callx_livekitfor LiveKit audio with no native code. - Same core as React Native.
@bear-block/callxruns the same Swift and Kotlin sources. - Private by default. MIT licensed, no telemetry.
Requirements
| Minimum | |
|---|---|
| Flutter | 3.41 (Dart 3.11) |
| iOS | 15.0, built with Xcode 26 or later |
| Android | API 29 (minSdk = 29) |
Install
flutter pub add callx
Set minSdk = 29 in android/app/build.gradle.kts. On iOS, enable Push Notifications and the
Audio and Voice over IP background modes, and add NSMicrophoneUsageDescription.
Bootstrap the native core
The core starts with the process, before any push can arrive.
// Android: Application.onCreate
CallxPlugin.bootstrap(this, CallxBootstrapConfig(accountGeneration = currentAccount()))
// iOS: application(_:didFinishLaunchingWithOptions:)
var config = CallxBootstrapConfig()
config.accountGeneration = currentAccount()
try CallxPlugin.bootstrap(config)
On Android, forward FCM messages from your FirebaseMessagingService to
CallxBootstrap.started?.ingress?.handlePush(...). The
Flutter quick start shows every file.
Use it
import 'package:callx/callx.dart';
final callx = Callx();
Future<void> start() async {
final capabilities = await callx.setup();
if (!capabilities.nativeCalling) return;
// Send this to your backend so it can push invitations to this device.
final token = await callx.pushToken(); // voip on iOS, fcm on Android
callx.snapshots.listen((snapshot) {
final call = snapshot.call; // incoming, connecting, active, held, ended…
});
}
Future<void> answer(String callId) async {
final result = await callx.answer(callId);
if (result.status != CommandStatus.applied) print(result.error?.message);
}
Your backend sends an APNs VoIP push or an FCM data message with a callx invitation; the
backend guide has the exact payloads.
Try it without a backend
import 'package:callx/callx_preview.dart';
final preview = CallxPreview();
await preview.callx.setup();
await preview.simulator.incoming(
const CallInput(callId: 'demo-1', displayName: 'Alex', handle: 'callx:alex'));
await preview.callx.answer('demo-1'); // connecting
await preview.simulator.mediaConnected(); // active
The simulator is an explicit opt-in; Callx() never falls back to it.
Scope
One live call at a time, voice, iOS and Android (web runs the simulator only). Callx does not host signaling, send pushes or carry media. See the roadmap and what has been verified on devices.
Support
Callx is free and independent. Sponsoring it funds the maintenance that keeps it working through every iOS and Android release. You can also help by sharing test results from your phone. Need help adding calls to your app? The maintainers take on integration work.
MIT licensed.
Optional call UI
Version 0.2.2 exports CallxCallOverlay, CallxCallScreen, CallxMiniCall and
CallxPresentationController from package:callx/callx_ui.dart. Mount the overlay above your app UI; incoming
stays on Home until accepted, Back minimizes inside the app, and end removes the call UI.
Colors/logo and command callbacks belong to your app. The controller never changes native
call state. See the call UI guide.
Libraries
- callx
- callx_preview
- Memory-only UX simulator: no OS UI, audio, push, network or durable replay.
- callx_ui
- Optional app-owned call presentation; importing this does not start native calls.