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 @malopezr7/bridgekit/contract entrypoint.

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

    Terminal window
    # Android (default platform is kotlin)
    bridgekit generate --contracts 'src/**/*.contract.ts' --out-dir bridgekit/generated
    # iOS — same contracts, Swift bindings
    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 AppHostProvider : AppHost {
    override suspend fun isLoggedIn(): Boolean = session.isLoggedIn()
    override suspend fun saveFile(params: SaveFileParams): SaveFileResult = /* ... */
    override fun notifications(): Flow<String> = pushService.messages()
    override val connectivity = MutableStateFlow(Connectivity(online = true))
    }
    BridgeKit.default.provide(AppHostContract, Scope.Global) { AppHostProvider() }
  4. Consume it from React Native with a typed proxy and the state/stream hooks.

    import { useBridge, useBridgeState } from '@malopezr7/bridgekit';
    function FileScreen() {
    const app = useBridge(AppHost);
    const { value: net } = useBridgeState(AppHost, 'connectivity');
    const onInstall = async () => {
    const result = await app.saveFile({ url, name });
    // result: 'success' | 'already-exists' | 'cancelled' | 'error'
    };
    return <Button disabled={!net.online} onPress={onInstall} />;
    }
  5. Subscribe to a stream — no useEffect dance, no event-emitter names.

    const notes = app.notifications();
    const unsubscribe = notes.subscribe((note) => addNotification(note));
    // …or: for await (const note of notes) { ... }

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

useProvideBridge(ExampleFeature, { 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.