Brownie: A Type-Safe Shared-State Workflow for Brownfield React Native

Mixing two technologies that allow for the same outcomes, such as React Native and Swift or Kotlin, creates a challenge: shall we allow the data to live duplicated over two realms, or create a single source of truth with automatic synchronization. Spoiler alert: whenever possible pick the latter.

When we introduced Brownie back in January, the goal was clear: make shared state in brownfield React Native apps predictable, type-safe, and practical for production teams by having a single source of truth that is always in-sync between native and JavaScript threads. We started with iOS to experiment whether the idea is sound.

Today we’re introducing Android support, making Brownie ready for cross-platform use in iOS and Android host apps.

Getting started with Brownie

Brownie enforces not only runtime synchronization, but also build-time guarantees for store shape through code generation. That’s why we start with a single source of truth: a TypeScript store schema in a *.brownie.ts file.

import type { BrownieStore } from "@callstack/brownie";

interface AppStore extends BrownieStore {
  counter: number;
}

declare module "@callstack/brownie" {
  interface BrownieStores {
    AppStore: AppStore;
  }
}

From there, brownfield codegen command, that comes with React Native Brownfield library, generates native store types, and your UI layers consume the same shared state with platform-native APIs.

React Native (TypeScript)

On the React Native side, you can use useStore with selectors to get access to that store, and get updates focused, type-safe, re-rendering React state only when necessary:

import { useStore } from "@callstack/brownie";

const [counter, setState] = useStore(
  "AppStore",
  (s) => s.counter,
);

function handlePress() {
  setState((prev) => ({
    counter: prev.counter + 1,
  }));
}

iOS (SwiftUI/UIKit)

On the native iOS side, all you need to do is to register the store once with the initial state, and then read and update using SwiftUI's @UseStore or UIKit subscriptions:

import Brownie
import ReactBrownfield

// register the store
AppStore.register(AppStore(counter: 0))

Then update the store as needed using SwiftUI’s @UseStore directive:

import Brownie
import ReactBrownfield

struct CounterView: View {
  @UseStore(\AppStore.counter) var counter

  var body: some View {
    VStack {
      Text("Count: \(Int(counter))")
      Button("Increment") {
        $counter.set { $0 + 1 }
      }
    }
  }
}

The @UseStore and AppStore are included with the artifacts packaged by the Brownfield CLI.

Android (Kotlin)

On the native Android side, we need to register the store during startup like this:

import com.callstack.brownie.registerStoreIfNeeded

registerStoreIfNeeded(storeName = AppStore.STORE_NAME) {
  AppStore(counter = 0.0)
}

Then update the store as required using subscribe and store.set:

import com.brownfield.example.AppStore
import com.callstack.brownie.Store
import com.callstack.brownie.StoreManager
import com.callstack.brownie.store
import com.callstack.brownie.subscribe

private fun appStore(): Store<BrownfieldStore>? {
    return StoreManager.shared.store(AppStore.STORE_NAME)
}

@Composable
fun GreetingCard() {
    var counter by remember { mutableIntStateOf(0) }

    DisposableEffect(Unit) {
        val store = appStore()
        val unsubscribe = store?.subscribe(
            selector = { state -> state.counter.toInt() },
            onChange = { updatedCounter -> counter = updatedCounter }
        ) ?: {}

        onDispose {
            unsubscribe()
        }
    }

    Column {
        Text( text = "Count $counter")
        Button(onClick = {
            brownieStore()?.set { state ->
                state.copy(counter = state.counter + 1)
            }
        }) {
            Text("Increment counter")
        }
    }
}

The APIs in import statements are packaged together with the artifacts generated by the brownfield CLI.

Android usage supports:

  • Easy Path (recommended): generated types with Gson-based serialization
  • Advanced Path: custom serializers for compatibility rules, custom formats, or encrypted payloads

How it all works End-to-End

You’ve seen that Brownie relies on code generation, and (not surprisingly) requires you to write some code in TypeScript, Kotlin, and Swift. Let us quickly show you how this all fits together in a full pipeline:

  1. Define store schema in *.brownie.ts file
  2. Run Brownfield packaging and codegen for your target platform artifacts
  3. Register store in native app startup (Swift/Kotlin)
  4. Read and mutate state from React Native and native UI layers
  5. Subscribe to selected state slices for reactive updates

This keeps state updates consistent across the embedded React Native surface and native host UI on both platforms.

Migration from alpha versions

What's new since Alpha

The main update is a more mature, end-to-end workflow that offers:

  • Stable APIs ready for production Brownfield apps
  • Android support alongside iOS, including generated Kotlin types
  • A clearer setup path from schema definition to native registration
  • Kotlin integration with both an Easy Path and an Advanced Path

Brownie has moved past an iOS-first phase and now provides one state bridge across React Native, Swift, and Kotlin.

Migrating from Alpha

If you evaluated Brownie during the alpha phase, the main shift is moving from an experimental, iOS-first setup to a stable, cross-platform approach.

As an alpha user you should already have Brownie configured for iOS.

The setup flow is more cohesive now. Rather than stitching together early examples, it’s best to follow the current Getting Started guide, which reflects the unified onboarding and packaging process. The API surface has matured as well. Instead of relying on early patterns, use the documented APIs, useStore, native registration helpers, and typed store access, which are stable and consistent across platforms.

With that in mind, enabling Android support is straightforward:

  • Update package.json

In the below code snippet, replace rnapp with your react-native app identifier and brownfieldlib with your Brownfield Android module name:

"brownie": {
  "kotlin": "./android/BrownfieldLib/src/main/java/com/rnapp/brownfieldlib/Generated/",
  "kotlinPackageName": "com.rnapp.brownfieldlib"
},
  • Add Gson dependency

In your Brownfield Android module, add the Gson dependency:

api("com.google.code.gson:gson:2.13.1")

That is all you need to do. Your already existing Brownie store, which you previously could only use for iOS is now ready to be used in Android.

// package AAR

npx brownfield package:android --module-name :YourModuleName --variant release

// Publish AAR

npx brownfield publish:android --module-name :YourModuleName
For further documentation, head over to getting started with Brownie.

If your existing store schema already lives in *.brownie.ts, migration is mostly about updating platform integration steps and rebuilding artifacts with the current CLI flow.

How this impacts Brownfield integrations

The success of migrating from one technology stack to another depends on reducing accidental complexity at integration boundaries. Brownie helps with that by providing a single schema contract across React Native and native code, generating type-safe models in Swift and Kotlin, and maintaining a consistent state synchronization model regardless of platform.

That means less glue code, fewer runtime mismatches, and a smoother path for incremental migration.

If you’re integrating React Native into an existing native app today, this is a strong point to adopt Brownie: stable APIs, iOS and Android support, and a setup flow designed for production teams. Head over to the documentation to dive further.

And to make migrations and creating PoCs of React Native running side-by-side your native apps easier for you, we’ve recently released agent skills for React Native Brownfield library.

npx skills add callstackincubator/agent-skills --skill react-native-brownfield-migration

Just tell your agent to use the skill and give React Native a try in a matter of minutes.

Insights

Learn more about Brownfield

cover

Aug 27Article

React Native in .NET MAUI: A Practical Brownfield Integration Guide

Learn how to embed React Native inside a .NET MAUI app with a brownfield setup. This guide covers runtime packaging, lifecycle wiring, navigation, and shared state across both runtimes.

cover

Aug 3Article

Migration to React Native in 2026 Starts With a Delivery Question

Choosing between brownfield and greenfield React Native migration? Learn where risk sits, when to use a checkpoint, and what to prove before committing.

cover

Jul 8Article

How we made Hybrid iOS apps compile 39% faster with React Native Brownfield

See how React Native Brownfield uses iOS prebuilts to cut packaging times by up to 39% for hybrid apps, with details on setup, internals, and benchmark results.

cover

Jun 18Article

How We Made the Brownfield Gradle Plugin 22% Faster

See how task-driven Gradle optimization made the Brownfield Gradle Plugin 22% faster for clean Android release builds.

cover

Jun 3Article

AI-Supported Brownfield Migration to React Native

See how AI-supported workflows cut brownfield React Native evaluation from weeks to days by speeding up setup, planning, implementation, and QA.

cover

May 18Article

Unlocking Expo Updates in an Isolated Brownfield Architecture with SDK 55

Learn how to enable Expo Updates in an Isolated Brownfield setup with Expo SDK 55, including iOS packaging, Android patching, and OTA testing.

cover

Apr 23Talk

State of Brownfield in React Native

A practical look at Brownfield React Native adoption in mature native apps, architecture, tooling, and real-world tradeoffs.

cover

Apr 2Webinar

AI-Assisted Migrations to React Native: From Months to Days

Reduce migration time from months to days by using AI in real React Native workflows. Learn when to migrate incrementally and when starting fresh is the better option.

cover

Nov 17Talk

Vega Developer Meetup

Learn how to integrate React Native into Android TV apps at the Vega Developer Meetup. Workshop, roadmap, and community talks, live in Munich.

cover

May 16Podcast

You Don’t Need a Rewrite, You Need React Native Brownfield

Discover how modern React Native Brownfield integration enables cross-platform power in native apps, without rewriting from scratch.

cover

Jan 13Podcast

Lessons from Shopify's Migration to React Native with Mustafa Ali

Learn how Shopify's migration to React Native led to 86% code unification, reduced build times, and seamless feature parity across platforms.

cover

Jun 14Podcast

Migration to React Native

The 13th episode of React Universe On Air (formerly known as The React Native Show) is fully focused on migrating from different technologies to React Native, the two most popular approaches to this process (greenfield and brownfield software development), and our state-of-the-art brownfield approach.

cover

Mar 19Tutorial

The Hard Way vs. The React Native AI SDK Way: Stop Writing Custom Modules for Every Model

Learn how React Native AI uses a unified provider model to switch between cloud and on-device LLMs without rewriting application logic, enabling reliable AI experiences online and offline.

cover

Mar 12Tutorial

Build Smarter Apps: Tool Calling & AI Orchestration Explained

Learn how on-device models in React Native can call tools, interact with external APIs, and return structured outputs you can use directly in application logic.

cover

Mar 5Tutorial

MLC LLM + React Native: On-Device AI Without the Pain

Learn how to run third-party on-device LLMs in React Native using MLC LLM. Choose your models, run them efficiently across platforms, and keep a unified JavaScript API with React Native AI.

cover

Feb 24Tutorial

Intelligent Fallbacks with Apple Intelligence in React Native

Learn how to use Apple’s built-in Foundation Models in React Native apps, reduce memory usage, and enable fast, fully on-device AI with React Native AI Apple.

cover

Feb 10Tutorial

What Is the React Native AI SDK? A Complete Intro & Quickstart

Learn why on-device AI matters for React Native apps, how local LLMs behave offline, and what problems React Native AI is designed to solve from day one.

cover

Jan 29Tutorial

Implementing an Android TurboModule with Kotlin

Learn how to implement a React Native TurboModule on Android, from the TypeScript spec and Codegen to the Kotlin implementation and package registration.

cover

Jan 19Tutorial

Working with Different Threads in Swift TurboModules

Learn how JavaScript, native module, background, and main threads interact in React Native TurboModules, and how to use GCD queues to run async work safely in Swift.

cover

Jan 9Tutorial

How to Add Type-Safe Constants to Swift TurboModules

Learn how to expose typed, immutable constants from a Swift TurboModule using Codegen, and get full end-to-end type safety between native and JavaScript.

cover

Dec 30Tutorial

Adding Event Emitters to Your TurboModule in Swift

Learn how to emit native events from a Swift TurboModule and subscribe to them in JavaScript, including proper memory management and Codegen integration.

cover

Dec 22Tutorial

Writing Your First TurboModule in Swift

Learn how to build a fully working TurboModule in Swift, integrate it with Codegen, and bridge it through Objective‑C inside an Expo app.