Skip to content

Try without a backend ​

Both packages include a simulator: an in-memory backend that implements the same contract as the native core. Use it to build your call screens, write widget and component tests, or just see how states and results behave. It starts no microphone and contacts no network.

The simulator is an explicit opt-in. Callx never falls back to it silently: new Callx() and Callx() always use the native core.

ts
import {createCallxPreview} from '@bear-block/callx/preview';

const {callx, simulator} = createCallxPreview();
const stop = callx.observe(snapshot => console.log(snapshot.sequence, snapshot.call?.state));
await callx.setup();

// What a push would do in production:
await simulator.incoming({callId: 'demo-1', displayName: 'Alex', handle: 'callx:alex'});
// state: incoming

const result = await callx.answer('demo-1');
console.log(result.status, result.execution); // applied preview
// state: connecting (answered is not connected)

await simulator.mediaConnected();
// state: active

await callx.setMuted('demo-1', true);
await callx.setHeld('demo-1', true);  // state: held
await callx.setHeld('demo-1', false); // state: active
await callx.end('demo-1');            // state: ended, endReason: localHangup

stop();          // Detaches the observer; it is not a hang-up.
callx.dispose();
dart
import 'package:callx/callx.dart';
import 'package:callx/callx_preview.dart';

final preview = CallxPreview();
final callx = preview.callx;
final subscription = callx.snapshots.listen(
  (snapshot) => print('${snapshot.sequence}: ${snapshot.call?.state.name}'),
);
await callx.setup();

// What a push would do in production:
await preview.simulator.incoming(
  const CallInput(callId: 'demo-1', displayName: 'Alex', handle: 'callx:alex'),
);

final result = await callx.answer('demo-1'); // connecting
print('${result.status.name} ${result.execution.name}'); // applied preview

await preview.simulator.mediaConnected(); // active
await callx.setMuted('demo-1', true);
await callx.setHeld('demo-1', true);
await callx.setHeld('demo-1', false);
await callx.end('demo-1');

await subscription.cancel();
await callx.dispose();

The simulator also offers remoteAnswered() (the other side picked up your outgoing call), remoteEnded() and reset().

Run the example apps ​

The repository has a full example app for each framework, with a simulator mode and a device mode wired to real CallKit and Core-Telecom:

sh
git clone https://github.com/bear-block/callx && cd callx
npm ci
npm run ios:rn          # or: ios:flutter, android:rn, android:flutter
FlutterReact Native
Flutter exampleReact Native example

Released under the MIT License. No telemetry, in the library or on this site.