Skip to content

Quick start

This walks the shortest path from nothing to a working round trip: define a contract, generate the native bindings, implement it natively (Kotlin or Swift), consume it from React Native.

  1. Define a contract in a *.contract.ts file. Import only from the pure @axion/bridgekit/contract entrypoint.

    connect-host.contract.ts
    import { defineContract, t } from '@axion/bridgekit/contract';
    export const ConnectHost = defineContract('connect.host', {
    methods: {
    isLoggedIn: t.query(t.boolean()),
    installEsim: t.query(
    t.object({ url: t.string(), iccId: t.string() }),
    t.literals('success', 'already-installed', 'cancelled', 'error'),
    { timeoutMs: null },
    ),
    },
    streams: { otpCodes: t.stream(t.string()) },
    state: { connectivity: t.state(t.object({ online: t.boolean() }), { online: false }) },
    });
  2. Generate the native bindings with the axion CLI. Output is committed, readable code — no hidden directories. Pick the target platform with --platform:

    Terminal window
    # Android (default platform is kotlin)
    axion bridgekit generate --contracts 'src/**/*.contract.ts' --out-dir bridgekit/generated
    # iOS — same contracts, Swift bindings
    axion bridgekit generate --contracts 'src/**/*.contract.ts' --platform swift --out-dir ios/contracts/generated
  3. Implement the provider on the side that owns the capability. Here native owns it — implement the generated interface (Kotlin) or protocol (Swift):

    class ConnectHostProvider : ConnectHost {
    override suspend fun isLoggedIn(): Boolean = session.isLoggedIn()
    override suspend fun installEsim(params: InstallEsimParams): InstallEsimResult = /* ... */
    override fun otpCodes(): Flow<String> = smsRetriever.codes()
    override val connectivity = MutableStateFlow(Connectivity(online = true))
    }
    BridgeKit.default.provide(ConnectHostContract, Scope.Global) { ConnectHostProvider() }
  4. Consume it from React Native with a typed proxy and the state/stream hooks.

    import { useBridge, useBridgeState } from '@axion/bridgekit';
    function Connect() {
    const connect = useBridge(ConnectHost);
    const { value: net } = useBridgeState(ConnectHost, 'connectivity');
    const onInstall = async () => {
    const result = await connect.installEsim({ url, iccId });
    // result: 'success' | 'already-installed' | 'cancelled' | 'error'
    };
    return <Button disabled={!net.online} onPress={onInstall} />;
    }
  5. Subscribe to a stream — no useEffect dance, no event-emitter names.

    const codes = connect.otpCodes();
    const unsubscribe = codes.subscribe((code) => setOtp(code));
    // …or: for await (const code of codes) { ... }

When RN provides and native consumes, the shape is identical — only the implementer moves:

useProvideBridge(LiaFeature, { getUnreadCount: () => store.unread });

Next: wire BridgeKit into a real project in Installation, understand the building blocks in Defining a contract, or see a complete real app in the demo walkthrough.