{"id":18694794,"url":"https://github.com/voize-gmbh/reakt-native-toolkit","last_synced_at":"2025-05-16T03:05:22.625Z","repository":{"id":65373107,"uuid":"568889436","full_name":"voize-gmbh/reakt-native-toolkit","owner":"voize-gmbh","description":"Combine React Native with Kotlin Multiplatform (KMP)","archived":false,"fork":false,"pushed_at":"2025-05-13T21:43:12.000Z","size":1218,"stargazers_count":154,"open_issues_count":10,"forks_count":5,"subscribers_count":6,"default_branch":"main","last_synced_at":"2025-05-13T23:06:38.054Z","etag":null,"topics":["kotlin","kotlin-multiplatform","kotlin-multiplatform-mobile","react","react-native"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/voize-gmbh.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2022-11-21T16:17:47.000Z","updated_at":"2025-05-13T21:43:16.000Z","dependencies_parsed_at":"2024-09-15T11:31:52.251Z","dependency_job_id":"7518794e-27f4-4cc5-9f19-43ee3f8fb54e","html_url":"https://github.com/voize-gmbh/reakt-native-toolkit","commit_stats":{"total_commits":184,"total_committers":5,"mean_commits":36.8,"dds":0.4184782608695652,"last_synced_commit":"5c9a2539093096c56a82116a68fdfab0c338a83f"},"previous_names":[],"tags_count":47,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/voize-gmbh%2Freakt-native-toolkit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/voize-gmbh%2Freakt-native-toolkit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/voize-gmbh%2Freakt-native-toolkit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/voize-gmbh%2Freakt-native-toolkit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/voize-gmbh","download_url":"https://codeload.github.com/voize-gmbh/reakt-native-toolkit/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254436950,"owners_count":22070947,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["kotlin","kotlin-multiplatform","kotlin-multiplatform-mobile","react","react-native"],"created_at":"2024-11-07T11:12:43.340Z","updated_at":"2025-05-16T03:05:17.616Z","avatar_url":"https://github.com/voize-gmbh.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"# reakt-native-toolkit\n\n[![NPM](https://img.shields.io/npm/v/reakt-native-toolkit?color=blue)](https://www.npmjs.com/package/reakt-native-toolkit)\n[![Maven Central](https://img.shields.io/maven-central/v/de.voize/reakt-native-toolkit?color=blue)](https://search.maven.org/artifact/de.voize/reakt-native-toolkit)\n\nThis toolkit allows you to combine React Native with Kotlin Multiplatform (KMP) by generating native modules for iOS and Android from Kotlin common code and supplying you with utilities to expose Kotlin Flows directly to React Native.\n\n## Installation\n\n**Prerequisite:** Your project must be a Kotlin Multiplatform project, see [our guide](docs/project-setup.md) on how to setup Kotlin Multiplatform in your existing React Native project.\nStarting with kotlin 1.9.0 your project must have android and ios targets configured.\nThe toolkit was tested with React Native 0.69 and 0.72 but may work with other versions as well.\n\nAdd the KSP gradle plugin to your multiplatform project's `build.gradle.kts` file, if you have subprojects, add it to the subject project's `build.gradle.kts` file.\n\n```kotlin\n// android/shared/build.gradle.kts\n\nplugins {\n    // from gradlePluginPortal()\n    id(\"com.google.devtools.ksp\") version \"2.0.21-1.0.25\"\n}\n```\n\nThen add the `reakt-native-toolkit` to the `commonMain` source set dependencies.\nAlso add the generated common source set to the `commonMain` source set:\n\n```kotlin\n// android/shared/build.gradle.kts\n\n// for React Native \u003e=0.71.0 use version x.x.x\n// for React Native \u003c0.71.0 use version x.x.x-legacy\nval reaktNativeToolkitVersion = \"\u003cversion\u003e\"\n\nval commonMain by getting {\n    kotlin.srcDir(\"build/generated/ksp/metadata/commonMain/kotlin\")\n    dependencies {\n        implementation(\"de.voize:reakt-native-toolkit:$reaktNativeToolkitVersion\")\n    }\n}\n```\n\nThen add `reakt-native-toolkit-ksp` to the KSP configurations:\n\n```kotlin\n// android/shared/build.gradle.kts\n\ndependencies {\n    add(\"kspCommonMainMetadata\", \"de.voize:reakt-native-toolkit-ksp:$reaktNativeToolkitVersion\")\n    add(\"kspAndroid\", \"de.voize:reakt-native-toolkit-ksp:$reaktNativeToolkitVersion\")\n    add(\"kspIosX64\", \"de.voize:reakt-native-toolkit-ksp:$reaktNativeToolkitVersion\")\n    add(\"kspIosArm64\", \"de.voize:reakt-native-toolkit-ksp:$reaktNativeToolkitVersion\")\n    // (if needed) add(\"kspIosSimulatorArm64\", \"de.voize:reakt-native-toolkit-ksp:$reaktNativeToolkitVersion\")\n}\n```\n\nAnd configure the ksp task dependencies and copy the generated typescript files to your react native project:\n\n```kotlin\n// android/shared/build.gradle.kts\ntasks.withType\u003corg.jetbrains.kotlin.gradle.dsl.KotlinCompile\u003c*\u003e\u003e().configureEach {\n    if(name != \"kspCommonMainKotlinMetadata\") {\n        dependsOn(\"kspCommonMainKotlinMetadata\")\n    } else {\n        finalizedBy(\"copyGeneratedTypescriptFiles\")\n    }\n}\n\ntasks.register\u003cCopy\u003e(\"copyGeneratedTypescriptFiles\") {\n    dependsOn(\"kspCommonMainKotlinMetadata\")\n    from(\"build/generated/ksp/metadata/commonMain/resources/reaktNativeToolkit/typescript\")\n    into(\"../../src/generated\")\n}\n```\n\nAdd copyGeneratedTypescriptFiles task as a dependency to your react native bundle task:\n\n```kotlin\n// android/app/build.gradle\n// React Native 0.71.x and above with Hermes\ntasks.withType(com.facebook.react.tasks.BundleHermesCTask).configureEach {\n    dependsOn(\":shared:copyGeneratedTypescriptFiles\")\n}\n\n// React Native 0.70.x and below\ntasks.named(\"bundleReleaseJsAndAssets\") {\n    dependsOn(\":shared:copyGeneratedTypescriptFiles\")\n}\n```\n\nIn the project that consumes the module, the generated TypeScript files require the JavaScript utilities, such as `useFlow`:\n\n```bash\nyarn add reakt-native-toolkit\n```\n\n## Usage\n\n### Native Module generation\n\nTo generate a native module, annotate a Kotlin class in the `commonMain` source set with `@ReactNativeModule` and add the `@ReactNativeMethod` annotation to the methods you want to expose to React Native.\n\n```kotlin\nimport de.voize.reaktnativetoolkit.annotation.ReactNativeMethod\nimport de.voize.reaktnativetoolkit.annotation.ReactNativeModule\n\n@ReactNativeModule(\"Calculator\")\nclass CalculatorRNModule {\n    @ReactNativeMethod\n    fun add(a: Int, b: Int): Int {\n        return a + b\n    }\n}\n```\n\nThe toolkit will generate the following for you\n\n- `CalculatorRNModuleAndroid.kt` for `androidMain`\n- `CalculatorRNModuleIOS.kt` for `iosMain`\n- `Calculator` and `CalculatorInterface` Typescript code\n\n#### Register generated Android module\n\nYou can now add `CalculatorRNModuleAndroid` to your native modules package in `androidMain`:\n\n```kotlin\nimport com.facebook.react.ReactPackage\nimport com.facebook.react.bridge.NativeModule\nimport com.facebook.react.bridge.ReactApplicationContext\nimport com.facebook.react.uimanager.ViewManager\nimport com.example.calculator.CalculatorRNModuleAndroid\nimport kotlinx.coroutines.CoroutineScope\n\nclass MyRNPackage(coroutineScope: CoroutineScope) : ReactPackage {\n    override fun createViewManagers(reactContext: ReactApplicationContext): List\u003cViewManager\u003c*, *\u003e\u003e {\n        return emptyList()\n    }\n\n    override fun createNativeModules(reactContext: ReactApplicationContext): List\u003cNativeModule\u003e {\n        return listOf\u003cNativeModule\u003e(\n            CalculatorRNModuleAndroid(reactContext, coroutineScope),\n            // ...\n        )\n    }\n}\n```\n\n#### Register generated iOS module\n\nThe `CalculatorRNModuleIOS` class will be compiled into your KMP projects shared framework and can be consumed in your iOS project in the `extraModules` of your `RCTBridgeDelegate`:\n\n```swift\nimport shared // your KMP project's shared framework\n\n@UIApplicationMain\nclass AppDelegate: UIResponder, UIApplicationDelegate, RCTBridgeDelegate {\n  // ...\n\n    func extraModules(for bridge: RCTBridge!) -\u003e [RCTBridgeModule]! {\n        return [CalculatorRNModuleIOS(...)]\n    }\n}\n```\n\nTo do dependency injection or to supply the `coroutineScope` property you can wrap your `RNModuleIOS` classes in Kotlin in the `iosMain` source set and call constructors there:\n\n```kotlin\nimport com.example.calculator.CalculatorRNModuleIOS\nimport kotlinx.coroutines.CoroutineScope\nimport react_native.RCTBridgeModuleProtocol\n\nclass MyIOSRNModules {\n    val coroutineScope = CoroutineScope(Dispatchers.Default)\n\n    fun createNativeModules(): List\u003cRCTBridgeModuleProtocol\u003e {\n        return listOf(\n            CalculatorRNModuleIOS(coroutineScope),\n            // ...\n        )\n    }\n}\n```\n\nAnd use this wrapper in Objective-C in the `extraModules` function, similar to above except calling the `createNativeModules` function:\n\n```swift\nreturn [[[MyIOSRNModules alloc] init] createNativeModules];\n```\n\nOr in Swift:\n\n```swift\nimport shared // your KMP project's shared framework\n\n@UIApplicationMain\nclass AppDelegate: UIResponder, UIApplicationDelegate, RCTBridgeDelegate {\n    // ...\n\n    func extraModules(for bridge: RCTBridge!) -\u003e [RCTBridgeModule]! {\n        return MyIOSRNModules().createNativeModules()\n    }\n}\n```\n\n#### Use your native module in React Native\n\nThe toolkit will generate a Typescript object for you and you can just import it from the generation destination (usually `src/generated/modules`):\n\n```typescript\nimport { Calculator } from \"src/generated/modules\";\n\nconst result = await Calculator.add(1, 2);\n```\n\n#### Exception handling\n\nExceptions thrown in your native module will be propagated to JS and converted to JS errors.\nYou can customize the JS errors and log exception in Kotlin by implementing an ExceptionInterceptor in Kotlin:\n\n```kotlin\nimport de.voize.reaktnativetoolkit.util.exceptionInterceptor\n\nfun setExceptionInterceptor() {\n    exceptionInterceptor = { exception -\u003e\n        logger.error(exception) { \"Error in native module\" }\n        mapOf(\n            \"NATIVE_STACK_TRACE\" to it.stackTraceToString(),\n        )\n    }\n}\n```\n\nThe return value (Map\u003cString,Any?\u003e) of the interceptor will be added to the `userInfo` property of the JS error.\n\n```typescript\nconsole.log(error.userInfo.NATIVE_STACK_TRACE);\n```\n\nBe aware Boolean values are converted to 0 or 1 in JS on iOS.\n\n### Streamline dependency injection using Module Providers\n\nIf you want your RN module to use some external functionality you would pass it in via the constructor:\n\n```kotlin\nimport de.voize.reaktnativetoolkit.annotation.ReactNativeMethod\nimport de.voize.reaktnativetoolkit.annotation.ReactNativeModule\n\n@ReactNativeModule(\"Calculator\")\nclass CalculatorRNModule(analytics: Analytics) {\n    @ReactNativeMethod\n    fun add(a: Int, b: Int): Int {\n        analytics.log(\"add\", mapOf(\"a\" to a, \"b\" to b))\n        return a + b\n    }\n}\n```\n\nThe dependency must then be injected when creating the native module instance.\n\n#### Common\n\nTo avoid duplicating your dependency injection in Android and iOS you can use the toolkits _Module Providers_. For each `@ReactNativeModule` annotated class, the toolkit will generate a `ReactNativeModuleProvider` which can be used from common code.\n\nThe provider has the same constructor as the class annotated with `@ReactNativeModule`.\n\n```kotlin\n// commonMain\n\nfun getRNModuleProviders(analytics: Analytics): List\u003cReactNativeModuleProvider\u003e {\n    return listOf(\n        CalculatorRNModuleProvider(analytics),\n        CalendarRNModuleProvider(analytics),\n        PermissionRNModuleProvider(analytics),\n        // ...\n    )\n}\n```\n\nThis allows you to create a list of all your native modules in common code and in the platform specific code you can get the actual native module instances from the provider via `getModule`.\n\n##### Android\n\n```kotlin\n// androidMain\n\nclass MyRNPackage(coroutineScope: CoroutineScope, analytics: Analytics) : ReactPackage {\n    // ...\n\n    override fun createNativeModules(\n        reactContext: ReactApplicationContext\n    ): List\u003cNativeModule\u003e {\n        return getRNModuleProviders(analytics).getModules(reactContext, coroutineScope)\n    }\n}\n```\n\n##### iOS\n\n```kotlin\n// iosMain\n\nclass MyIOSRNModules(analytics: Analytics) {\n    val coroutineScope = CoroutineScope(Dispatchers.Default)\n\n    fun createNativeModules(): List\u003cRCTBridgeModuleProtocol\u003e {\n        return getRNModuleProviders(analytics).getModules(coroutineScope)\n    }\n}\n```\n\nThe `getModule` function takes different parameters depending on the platform.\nFor android you need to pass in the `ReactApplicationContext` and the `CoroutineScope` and for iOS you need to pass in the `CoroutineScope`.\n\nThere are also helper `getModules` to convert a `List\u003cReactNativeModuleProvider\u003e` to a list of native modules for each platform.\n`ReactNativeModuleProvider` is very useful for large projects with many native modules.\n\n#### Platform specific dependency injection\n\n#### Android\n\n```kotlin\n// androidMain\n\nclass MyRNPackage(coroutineScope: CoroutineScope, analytics: Analytics) : ReactPackage {\n    // ...\n\n    override fun createNativeModules(reactContext: ReactApplicationContext): List\u003cNativeModule\u003e {\n        return listOf\u003cNativeModule\u003e(\n            CalculatorRNModuleAndroid(reactContext, coroutineScope, analytics),\n            CalendarRNModuleAndroid(reactContext, coroutineScope, analytics),\n            PermissionRNModuleAndroid(reactContext, coroutineScope, analytics),\n            // ...\n        )\n    }\n}\n```\n\nRegister the package in `MainApplication` -- in the `getPackages` function of `ReactNativeHost`, instantiate the package, passing any required dependencies, and add it.\nExample:\n\n```kotlin\nadd(RNPackage(coroutineScope, analyics))\n```\n\n#### iOS\n\n```kotlin\n// iosMain\n\nclass MyIOSRNModules(analytics: Analytics) {\n    val coroutineScope = CoroutineScope(Dispatchers.Default)\n\n    fun createNativeModules(): List\u003cRCTBridgeModuleProtocol\u003e {\n        return listOf(\n            CalculatorRNModuleIOS(coroutineScope, analytics),\n            CalendarRNModuleIOS(coroutineScope, analytics),\n            PermissionRNModuleIOS(coroutineScope, analytics),\n            // ...\n        )\n    }\n}\n```\n\n### Expose Kotlin flows to React Native\n\nThe toolkit allows you to directly expose Kotlin Flows to React Native and supplies you with utilities to interact with them.\nThis is usefull for flows which represent a state.\n\nA flow exposed to React Native could look like this:\n\n```kotlin\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport de.voize.reaktnativetoolkit.annotation.ReactNativeFlow\nimport de.voize.reaktnativetoolkit.annotation.ReactNativeMethod\nimport de.voize.reaktnativetoolkit.annotation.ReactNativeModule\n\n@ReactNativeModule(\"Counter\")\nclass CounterRNModule {\n    private val counter = MutableStateFlow(0)\n\n    @ReactNativeFlow\n    suspend fun count(): Flow\u003cInt\u003e = counter\n\n    @ReactNativeMethod\n    suspend fun increment() {\n        counter.update { it + 1 }\n    }\n}\n```\n\nA flow is exposed via a `@ReactNativeFlow` annotated function.\n\nOn the JS side we interact with this flow using the `useFlow` hook:\n\n```typescript\nimport { Counter } from \"./generated/modules\";\n\nfunction useCounter() {\n  const count = Counter.useCount();\n\n  return {\n    count,\n    increment: Counter.increment,\n  };\n}\n```\n\nNOTE: React Native 0.74+ generates code that requires `react-native-get-random-values` to be installed.\nCall `import 'react-native-get-random-values';` as early as possible in the application initialization.\nFor Expo users, install the `expo-standard-web-crypto` package, which includes the necessary polyfill.\nInitialize it as follows, again as early as possible:\n\n```typescript\nimport { polyfillWebCrypto } from \"expo-standard-web-crypto\";\n\n// useFlow from the devhaus mobile-sdk uses uuid which uses crypto.getRandomValues which is not available in expo,\n// so polyfill it with the expo-standard-web-crypto module\n// https://github.com/expo/expo/tree/main/packages/expo-standard-web-crypto\npolyfillWebCrypto();\n```\n\nWith `Counter.useCount()` we can \"subscribe\" to the flow value. The hook will trigger a rerender whenever the flow value changes.\nFor each subscription the native flow is consumed multiple times, a new subscription is created after each value change. Make sure your flow returns the same value (\"state\") for each new subscription and does not return initial values or replay values.\n\n`useFlow` deserializes the returned values, but does not apply the custom mapping in JS, such as mapping strings to date types.\n\n#### How do `ReactNativeFlow` and the generated hooks work?\n\nThe `@ReactNativeFlow` annotation generates a native module method with an additional `previous` argument.\nThe generated code calls `toReact(previous)` on the returned flow and returns the result of the `toReact` call.\nThe extension function `Flow\u003cT\u003e.toReact(previous)` will JSON serialize the value of the flow and suspend until the value is different from the `previous` ([see in source](https://github.com/voize-gmbh/reakt-native-toolkit/blob/main/kotlin/reakt-native-toolkit/src/commonMain/kotlin/de/voize/reaktnativetoolkit/util/flowToReact.kt#L51)).\nThis is why `previous` is a string.\n\nIn JS for each flow a property is added to the native module, with the Type `Next\u003cT\u003e` which is a type alias for `(subscriptionId: string, currentValue: string | null) =\u003e Promise\u003cstring\u003e`.\nAdditionally a hook is generated for each flow with the name `use{FlowName}` which uses the `useFlow` util hook internally.\nThe hook manages the subscription to the flow and calls the native module method with the current value of the flow.\nThe `useFlow` hook initiates an interaction loop with this suspension: It initially calls the native module method with `null` as the `previous` value and suspends until the native module responds with a new value. It then calls the native module again with the new value and suspends again until the native module responds with a new value. This loop continues until the component is unmounted.\nFlow values are JSON serialized and deserialized when they are sent to and from the native module.\nThe generated hook maps the deserialized value to the correct type and returns it.\n\n### Mapping Kotlin types to JS types\n\nThe toolkit will automatically map Kotlin types to JS types and vice versa.\nPrimitive types like `Int`, `Double`, `Boolean` and `String` are mapped to their JS equivalents.\n`List` and `Map` are mapped to their JS equivalents.\nComplex types are mapped to JS objects with matching generated Typescript interfaces.\nThe mapping of date time types can be configured via the `@JSType` annotation or via ksp arguments.\nBy default all date time types are mapped to their string representation in js.\nThe default for `Instant` can be overridden with the ksp argument `reakt.native.toolkit.defaultInstantJsType`.\nEither `string` or `date` can be used as the value for this argument or the `@JSType` annotation.\n\n```kotlin\n// build.gradle.kts\nksp {\n    arg(\"reakt.native.toolkit.defaultInstantJsType\", \"string\")\n}\n```\n\n```kotlin\nimport kotlinx.datetime.Instant\nimport kotlinx.serialization.Serializable\nimport de.voize.reaktnativetoolkit.annotation.JSType\n\n@Serializable\ndata class Test(\n    val instant: Instant, // mapped to string in js\n    val date: @JSType(\"date\") Instant, // mapped to date in js\n    val string: @JSType(\"string\") Instant, // mapped to string in js\n)\n```\n\n### Configuration for multiple projects\n\nIf you have multiple projects including libraries and applications, types cannot be resolved cross project boundaries by default.\nFor example, if you have a library project with a data class and a consuming application project, the consuming application project cannot resolve the typescript type of the data class from the library project.\nBy default `any` is used as the type for unresolved types.\nTo get better type safety follow the steps below.\n\n#### Library project\n\nAll projects which expose types you want to export to typescript must be explicitly annotated with `@ExportTypescriptType` annotation.\n\n```kotlin\nimport de.voize.reaktnativetoolkit.annotation.ExportTypescriptType\nimport kotlinx.serialization.Serializable\n\n@ExportTypescriptType\n@Serializable\ndata class Test(\n    val value: Int\n)\n```\n\nAlso add the `reakt-native-toolkit-ksp` dependency to the library project.\nThis will generate the typescript types for the library project.\nYou need to copy the generated typescript files to the consuming application project.\n\n#### Consuming application project\n\nConfigure the root namespace for each library project in the consuming application project.\nThis is needed to resolve the typescript types based on the namespace of the library project.\nFor each namespace configure the module name and if the typescript was generated by the toolkit.\n\n```kotlin\n// build.gradle.kts\nksp {\n    arg(\"reakt.native.toolkit.externalNamespaces\", \"org.example.lib1\")\n    arg(\"reakt.native.toolkit.externalNamespaceModuleName.org.example.lib1\", \"./path/to/generated/models/lib1\")\n    arg(\"reakt.native.toolkit.externalNamespaceGeneratedByToolkit.org.example.lib\", \"true\")\n}\n```\n\n### Handling exceptions thrown by flows in JS\n\nIf a flow which is used in JS throws an exception, the exception is propagated from Kotlin to the `useFlow` hook. You can set an global error interceptor for useFlow with `setErrorInterceptor`:\n\n```typescript\nimport { setErrorInterceptor } from \"reakt-native-toolkit\";\n\nsetErrorInterceptor(async (error, flowName) =\u003e {\n  console.error(`Error in flow ${flowName}: ${error}`);\n});\n```\n\n### Using event emitter\n\nWith the toolkit you can also use event emitter in your common class module and the platform-specific event emitter setup is generated for you.\n\nTo use events in your module you only need to add a property of type `EventEmitter` to your constructor and specify the event names you want to use in the `@ReactNativeModule` annotation `supportedEvents` argument.\nThen you can emit events by calling `sendEvent` on your `EventEmitter`.\n\n```kotlin\nimport de.voize.reaktnativetoolkit.annotation.ReactNativeModule\nimport de.voize.reaktnativetoolkit.util.EventEmitter\n\n@ReactNativeModule(\"Notifications\", supportedEvents = [\"notify\"])\nclass Notifications(\n    private val eventEmitter: EventEmitter,\n    private val someNotificationService: NotificationService,\n) {\n    init {\n        someDependency.onNotification { message -\u003e\n            eventEmitter.sendEvent(\n                \"notify\",\n                mapOf(\"message\" to message)\n            )\n        }\n    }\n}\n```\n\nThe second argument of `sendEvent` can be null, a primitive type, a list or a map.\n\nTo consume the events in JS, use `addEventListener` on the module.\n\n```typescript\nimport { Notifications } from \"src/generated/modules\";\n\nconst subscription = Notifications.addEventListener(\"notify\", (event) =\u003e {\n  console.log(event.message);\n});\n\n// ...\nsubscription.remove();\n```\n\nThe toolkit generates native modules that are compatible with the standard React Native `NativeEventEmitter`, so alternatively the standard React Native approach may be used (see [here](https://reactnative.dev/docs/native-modules-ios#sending-events-to-javascript) and [here](https://reactnative.dev/docs/native-modules-android#sending-events-to-javascript)). Example:\n\n```\nimport { NativeEventEmitter, NativeModules } from \"react-native\";\n\nconst Notifications = NativeModules.Notifications;\nconst eventEmitter = new NativeEventEmitter(Notifications);\nconst subscription = eventEmitter.addListener(\"notify\", (event) =\u003e {\n  console.log(event.message);\n});\n```\n\nThe react native module can check if a listener is registered in JS with `EventEmitter.hasListeners`.\n`hasListeners` is a kotlin `Flow` which allows you to react to changes in the listener count (start publishing when the first listener is registered and stop publishing when the last listener is removed).\n\n```kotlin\nimport de.voize.reaktnativetoolkit.annotation.ReactNativeModule\nimport de.voize.reaktnativetoolkit.util.EventEmitter\nimport kotlinx.coroutines.flow.collect\nimport kotlinx.coroutines.flow.transformLatest\nimport kotlinx.coroutines.flow.launchIn\nimport kotlinx.coroutines.CoroutineScope\n\n@ReactNativeModule(\"Notifications\", supportedEvents = [\"notify\"])\nclass Notifications(\n    private val eventEmitter: EventEmitter,\n    private val someNotificationService: NotificationService,\n    private val messages: Flow\u003cStrings\u003e,\n    private val lifecycleScope: CoroutineScope,\n) {\n    init {\n        eventEmitter.hasListeners.transformLatest\u003cBoolean, Unit\u003e { hasListeners -\u003e\n            if (hasListeners) {\n                // do the publishing in this coroutine scope, whihc is cancelled when the last listener is removed\n                messages.collect { message -\u003e\n                    eventEmitter.sendEvent(\n                        \"notify\",\n                        mapOf(\"message\" to message)\n                    )\n                }\n            }\n            // don't forget to handle exceptions, else it will cancel lifecycleScope and everything it contains\n        }.launchIn(lifecycleScope)\n    }\n}\n```\n\n### Render Compose Multiplatform components in React Native (experimental)\n\nThe toolkit allows you to render Compose Multiplatform components in React Native by annotating any Compose component function with the `@ReactNativeViewManager` annotation. The toolkit will then generate the necessary _view managers_ for [iOS](https://reactnative.dev/docs/native-components-ios) and [Android](https://reactnative.dev/docs/native-components-android) you which allows you to render the component in React Native.\n\n#### Prerequisites\n\nMake sure the Kotlin multiplatform module in your project includes the `org.jetbrains.compose` Gradle plugin with a version compatible with your Kotlin version.\n\n```kotlin\n// android/shared/build.gradle.kts\nplugins {\n    ...\n    id(\"org.jetbrains.compose\") version \"...\"\n}\n```\n\nMake sure you added the Compose dependencies required for you to your `commonMain` source set.\n\n```kotlin\n// android/shared/build.gradle.kts\n\nval commonMain by getting {\n    dependencies {\n        ...\n\n        implementation(compose.ui)\n        implementation(compose.material)\n        // ... other compose dependencies\n    }\n}\n```\n\n#### Usage\n\nTo render a Compose component in React Native, annotate a Compose component function in the commonMain source set with `@ReactNativeViewManager` and specify the name of the view manager in the annotation.\n\n```kotlin\n// commonMain\n\nimport androidx.compose.material.Text\nimport androidx.compose.runtime.Composable\nimport de.voize.reaktnativetoolkit.annotation.ReactNativeViewManager\nimport de.voize.reaktnativetoolkit.annotation.ReactNativeProp\nimport kotlinx.coroutines.flow.Flow\n\n@Composable\n@ReactNativeViewManager(\"MyComposeView\")\ninternal fun MyComposeView(\n    @ReactNativeProp\n    val message: Flow\u003cString\u003e,\n    @ReactNativeProp\n    val onButtonPress: () -\u003e Unit,\n    analytics: Analytics,\n) {\n    val message by message.collectAsState(\"\")\n\n    Column {\n        Text(\"Message from React in Compose: $message\")\n        Button(onClick = {\n            onButtonPress()\n            analytics.log(\"Button pressed\")\n        }) {\n            Text(\"Press me\")\n        }\n    }\n}\n```\n\nYou can annotate parameters that should be passed from React Native as props using the `@ReactNativeProp` annotation. Props can be either `Flow`s of primitive or serializable types or callbacks. Callbacks can have primitive or serializable argument types and must return `Unit`.\n\nJust like with native modules, you can also use the generated platform `Provider`s to streamline dependency injection in common code.\n\n```kotlin\n// commonMain\n\nimport de.voize.reaktnativetoolkit.util.ReactNativeViewManagerProvider\n\nfun getReactNativeViewManagerProviders(analytics: Analytics): List\u003cReactNativeViewManagerProvider\u003e {\n    return listOf(\n        MyComposeViewRNViewManagerProvider(analytics),\n    )\n}\n```\n\n```kotlin\n// androidMain\n\nimport com.myrnproject.shared.getReactNativeViewManagerProviders\nimport de.voize.reaktnativetoolkit.util.getViewManagers\n\nclass MyRNPackage(private val analytics: Analytics) : ReactPackage {\n\n    override fun createNativeModules(reactContext: ReactApplicationContext): List\u003cNativeModule\u003e {\n        // ... native modules\n    }\n\n    override fun createViewManagers(reactContext: ReactApplicationContext): List\u003cViewManager\u003c*, *\u003e\u003e {\n        return getReactNativeViewManagerProviders(\n            analytics\n        ).getViewManagers(reactContext)\n    }\n}\n```\n\n```kotlin\n// iosMain\n\nimport de.voize.reaktnativetoolkit.util.ReactNativeIOSViewWrapperFactory\nimport de.voize.reaktnativetoolkit.util.getModules\nimport de.voize.reaktnativetoolkit.util.getViewWrapperFactories\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Dispatchers\nimport react_native.RCTBridgeModuleProtocol\n\nclass IOSRNModules(private val analytics: Analytics) {\n    private val coroutineScope = CoroutineScope(Dispatchers.Default)\n\n    fun createNativeModules(): List\u003cRCTBridgeModuleProtocol\u003e {\n        // ...\n    }\n\n    fun createViewWrapperFactories(): Map\u003cString, ReactNativeIOSViewWrapperFactory\u003e {\n        return getReactNativeViewManagerProviders(\n            persistentConfig,\n        ).getViewWrapperFactories()\n    }\n}\n```\n\n```objc\n#import \u003cshared/shared.h\u003e\n#import \"ReactNativeViewManagers.h\"\n\n@implementation AppDelegate\n    // ...\n\n    - (NSArray\u003cid\u003cRCTBridgeModule\u003e\u003e *)extraModulesForBridge:(RCTBridge *)bridge\n    {\n        SharedIOSRNModules* iOSRNModules = [[SharedIOSRNModules alloc] init];\n        NSArray\u003cid\u003cRCTBridgeModule\u003e\u003e *rnNativeModules = [iOSRNModules createNativeModules];\n        NSArray\u003cid\u003cRCTBridgeModule\u003e\u003e *rnViewManagers = [ReactNativeViewManagers getRNViewManagers:[iOSRNModules createViewWrapperFactories]];\n        return [rnNativeModules arrayByAddingObjectsFromArray:rnViewManagers];\n    }\n}\n```\n\nYou also need to reference the generated Objective-C files in your iOS project.\nFor this, open your XCode workspace, right click on your target and select \"Add files to ...\". Then select the directory in `android/shared/build/generated/ksp/metadata/commonMain/resources/reaktNativeToolkit/objc` and add it with the \"Create groups\" option enabled and the \"Copy items if needed\" option disabled.\n\nIf you experience symbol conflicts with the generated Objective-C files, e.g. because you use the `@ReactNativeViewManager` annotation in multiple modules, you can use set a namespace for the generated Objective-C files that will prefix the generated filenames and classes:\n\n```kotlin\nksp {\n    arg(\"reakt.native.toolkit.iosObjcNamespace\", \"My\")\n}\n```\n\nYou might have to an initial build to generate the Objective-C files before you can add them to your XCode project:\n\n```\ncd android\n./gradlew :shared:kspCommonMainKotlinMetadata\n```\n\nNow you can use the `MyComposeView` component from `generated/nativeViews.tsx` in your React Native code.\n\n```typescript\n// App.tsx\n\nimport React from \"react\";\n\nimport { MyComposeView } from \"./generated/nativeViews\";\n\nexport const App = () =\u003e {\n  return \u003cMyComposeView /\u003e;\n};\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvoize-gmbh%2Freakt-native-toolkit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvoize-gmbh%2Freakt-native-toolkit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvoize-gmbh%2Freakt-native-toolkit/lists"}