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_livekit for LiveKit audio and video with no native code.
  • Same core as React Native. @bear-block/callx runs the same Swift and Kotlin sources.
  • Phone features. Speaker, earpiece and headset switching, keypad tones (DTMF), caller-name updates, and call back from iOS Recents/Siri and Android missed-call notifications (guide).
  • 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 or video, 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

Since 0.2.2, Callx 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.