1--- 2title: Native Modules 3--- 4 5import { CodeBlocksTable } from '~/components/plugins/CodeBlocksTable'; 6import { APIBox } from '~/components/plugins/APIBox'; 7import { PlatformTags } from '~/ui/components/Tag'; 8import { APIMethod } from '~/components/plugins/api/APISectionMethods'; 9 10> **warning** Expo Modules APIs are in beta and subject to breaking changes. 11 12The native modules API is an abstraction layer on top of [JSI](https://reactnative.dev/architecture/glossary#javascript-interfaces-jsi) and other low-level primities that React Native is built upon. It is built with modern languages (Swift and Kotlin) and provides an easy to use and convenient API that is consistent across platforms where possible. 13 14## Definition Components 15 16As you might have noticed in the snippets on the [Get Started](./get-started.mdx) page, each module class must implement the `definition` function. 17The module definition consists of the DSL components that describe the module's functionality and behavior. 18 19<APIBox header="Name"> 20 21Sets the name of the module that JavaScript code will use to refer to the module. Takes a string as an argument. Can be inferred from module's class name, but it's recommended to set it explicitly for clarity. 22 23```swift Swift / Kotlin 24Name("MyModuleName") 25``` 26 27</APIBox> 28<APIBox header="Constants"> 29 30Sets constant properties on the module. Can take a dictionary or a closure that returns a dictionary. 31 32<CodeBlocksTable> 33 34```swift 35// Created from the dictionary 36Constants([ 37 "PI": Double.pi 38]) 39 40// or returned by the closure 41Constants { 42 return [ 43 "PI": Double.pi 44 ] 45} 46``` 47 48```kotlin 49// Passed as arguments 50Constants( 51 "PI" to kotlin.math.PI 52) 53 54// or returned by the closure 55Constants { 56 return@Constants mapOf( 57 "PI" to kotlin.math.PI 58 ) 59} 60``` 61 62</CodeBlocksTable> 63</APIBox> 64<APIBox header="Function"> 65 66Defines a native synchronous function that will be exported to JavaScript. Synchronous means that when the function is executed in JavaScript, its native code is run on the same thread and blocks further execution of the script until the native function returns. 67 68#### Arguments 69 70- **name**: `String` — Name of the function that you'll call from JavaScript. 71- **body**: `(args...) -> ReturnType` — The closure to run when the function is called. 72 73The function can receive up to 8 arguments. This is due to the limitations of generics in both Swift and Kotlin, because this component must be implemented separately for each arity. 74 75See the [Argument Types](#argument-types) section for more details on what types can be used in the function body. 76 77<CodeBlocksTable> 78 79```swift 80Function("syncFunction") { (message: String) in 81 return message 82} 83``` 84 85```kotlin 86Function("syncFunction") { message: String -> 87 return@Function message 88} 89``` 90 91</CodeBlocksTable> 92 93```js JavaScript 94import { requireNativeModule } from 'expo-modules-core'; 95 96// Assume that we have named the module "MyModule" 97const MyModule = requireNativeModule('MyModule'); 98 99function getMessage() { 100 return MyModule.syncFunction('bar'); 101} 102``` 103 104</APIBox> 105<APIBox header="AsyncFunction"> 106 107Defines a JavaScript function that always returns a `Promise` and whose native code is by default dispatched on the different thread than the JavaScript runtime runs on. 108 109#### Arguments 110 111- **name**: `String` — Name of the function that you'll call from JavaScript. 112- **body**: `(args...) -> ReturnType` — The closure to run when the function is called. 113 114If the type of the last argument is `Promise`, the function will wait for the promise to be resolved or rejected before the response is passed back to JavaScript. Otherwise, the function is immediately resolved with the returned value or rejected if it throws an exception. 115The function can receive up to 8 arguments (including the promise). 116 117See the [Argument Types](#argument-types) section for more details on what types can be used in the function body. 118 119It is recommended to use `AsyncFunction` over `Function` when it: 120 121- does I/O bound tasks such as sending network requests or interacting with the file system 122- needs to be run on different thread, e.g. the main UI thread for UI-related tasks 123- is an extensive or long-lasting operation that would block the JavaScript thread which in turn would reduce the responsiveness of the application 124 125<CodeBlocksTable> 126 127```swift 128AsyncFunction("asyncFunction") { (message: String, promise: Promise) in 129 DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) { 130 promise.resolve(message) 131 } 132} 133``` 134 135```kotlin 136AsyncFunction("asyncFunction") { message: String, promise: Promise -> 137 launch(Dispatchers.Main) { 138 promise.resolve(message) 139 } 140} 141``` 142 143</CodeBlocksTable> 144 145```js JavaScript 146import { requireNativeModule } from 'expo-modules-core'; 147 148// Assume that we have named the module "MyModule" 149const MyModule = requireNativeModule('MyModule'); 150 151async function getMessageAsync() { 152 return await MyModule.asyncFunction('bar'); 153} 154``` 155 156<hr /> 157 158#### Kotlin coroutines <PlatformTags prefix="" platforms={['android']} /> 159 160`AsyncFunction` can receive a suspendable body on Android. However, it has to be passed in the infix notation after the `Coroutine` block. You can read more about suspendable functions and coroutines on [coroutine overview](https://kotlinlang.org/docs/coroutines-overview.html). 161 162`AsyncFunction` with suspendable body can't receive `Promise` as an argument. It uses a suspension mechanism to execute asynchronous calls. 163The function is immediately resolved with the returned value of the provided suspendable block or rejected if it throws an exception. The function can receive up to 8 arguments. 164 165By default, suspend functions are dispatched on the module's coroutine scope. Moreover, every other suspendable function called from the body block is run within the same scope. 166This scope's lifecycle is bound to the module's lifecycle - all unfinished suspend functions will be canceled when the module is deallocated. 167 168```kotlin Kotlin 169AsyncFunction("suspendFunction") Coroutine { message: String -> 170 launch { 171 return@Coroutine message 172 } 173} 174``` 175 176</APIBox> 177<APIBox header="Events"> 178 179Defines event names that the module can send to JavaScript. 180 181> **Note**: This component can be used inside of the [`View`](#view) block to define callback names. See [`View callbacks`](#view-callbacks) 182 183<CodeBlocksTable> 184 185```swift 186Events("onCameraReady", "onPictureSaved", "onBarCodeScanned") 187``` 188 189```kotlin 190Events("onCameraReady", "onPictureSaved", "onBarCodeScanned") 191``` 192 193</CodeBlocksTable> 194 195See [Sending events](#sending-events) to learn how to send events from the native code to JavaScript/TypeScript. 196 197</APIBox> 198<APIBox header="ViewManager"> 199 200> **warning** **Deprecated**: To better integrate with [React Native's new architecture (Fabric)](https://reactnative.dev/architecture/fabric-renderer) and its recycling mechanism, as of SDK 47 the `ViewManager` component is deprecated in favor of [`View`](#view) with a view class passed as the first argument. This component will be removed in SDK 48. 201 202Enables the module to be used as a view manager. The view manager definition is built from the definition components used in the closure passed to `ViewManager`. Definition components that are accepted as part of the view manager definition: [`View`](#view), [`Prop`](#prop). 203 204<CodeBlocksTable> 205 206```swift 207ViewManager { 208 View { 209 MyNativeView() 210 } 211 212 Prop("isHidden") { (view: UIView, hidden: Bool) in 213 view.isHidden = hidden 214 } 215} 216``` 217 218```kotlin 219ViewManager { 220 View { context -> 221 MyNativeView(context) 222 } 223 224 Prop("isHidden") { view: View, hidden: Bool -> 225 view.isVisible = !hidden 226 } 227} 228``` 229 230</CodeBlocksTable> 231</APIBox> 232<APIBox header="View"> 233 234Enables the module to be used as a native view. Definition components that are accepted as part of the view definition: [`Prop`](#prop), [`Events`](#events). 235 236#### Arguments 237 238- **viewType** — The class of the native view that will be rendered. Note: On Android, the provided class must inherit from the [`ExpoView`](#expoview), on iOS it's optional. See [`Extending ExpoView`](#extending--expoview). 239 240<CodeBlocksTable> 241 242```swift 243View(UITextView.self) { 244 Prop("text") { ... } 245} 246``` 247 248```kotlin 249View(TextView::class) { 250 Prop("text") { ... } 251} 252``` 253 254</CodeBlocksTable> 255 256> Support for rendering SwiftUI views is planned. For now, you can use [`UIHostingController`](https://developer.apple.com/documentation/swiftui/uihostingcontroller) and add its content view to your UIKit view. 257 258</APIBox> 259<APIBox header="Prop"> 260 261Defines a setter for the view prop of given name. 262 263#### Arguments 264 265- **name**: `String` — Name of view prop that you want to define a setter. 266- **setter**: `(view: ViewType, value: ValueType) -> ()` — Closure that is invoked when the view rerenders. 267 268This property can only be used within a [`ViewManager`](#viewmanager) closure. 269 270<CodeBlocksTable> 271 272```swift 273Prop("background") { (view: UIView, color: UIColor) in 274 view.backgroundColor = color 275} 276``` 277 278```kotlin 279Prop("background") { view: View, @ColorInt color: Int -> 280 view.setBackgroundColor(color) 281} 282``` 283 284</CodeBlocksTable> 285 286> **Note** Props of function type (callbacks) are not supported yet. 287 288</APIBox> 289<APIBox header="OnCreate"> 290 291Defines module's lifecycle listener that is called right after module initialization. If you need to set up something when the module gets initialized, use this instead of module's class initializer. 292 293</APIBox> 294<APIBox header="OnDestroy"> 295 296Defines module's lifecycle listener that is called when the module is about to be deallocated. Use it instead of module's class destructor. 297 298</APIBox> 299<APIBox header="OnStartObserving"> 300 301Defines the function that is invoked when the first event listener is added. 302 303</APIBox> 304<APIBox header="OnStopObserving"> 305 306Defines the function that is invoked when all event listeners are removed. 307 308</APIBox> 309<APIBox header="OnAppContextDestroys"> 310 311Defines module's lifecycle listener that is called when the app context owning the module is about to be deallocated. 312 313</APIBox> 314<APIBox header="OnAppEntersForeground" platforms={["ios"]}> 315 316Defines the listener that is called when the app is about to enter the foreground mode. 317 318> **Note** This function is not available on Android — you may want to use [`OnActivityEntersForeground`](#onactivityentersforeground) instead. 319 320</APIBox> 321<APIBox header="OnAppEntersBackground" platforms={["ios"]}> 322 323Defines the listener that is called when the app enters the background mode. 324 325> **Note** This function is not available on Android — you may want to use [`OnActivityEntersBackground`](#onactivityentersbackground) instead. 326 327</APIBox> 328<APIBox header="OnAppBecomesActive" platforms={["ios"]}> 329 330Defines the listener that is called when the app becomes active again (after `OnAppEntersForeground`). 331 332> **Note** This function is not available on Android — you may want to use [`OnActivityEntersForeground`](#onactivityentersforeground) instead. 333 334</APIBox> 335<APIBox header="OnActivityEntersForeground" platforms={["android"]}> 336 337Defines the activity lifecycle listener that is called right after the activity is resumed. 338 339> **Note** This function is not available on iOS — you may want to use [`OnAppEntersForeground`](#onappentersforeground) instead. 340 341</APIBox> 342<APIBox header="OnActivityEntersBackground" platforms={["android"]}> 343 344Defines the activity lifecycle listener that is called right after the activity is paused. 345 346> **Note** This function is not available on iOS — you may want to use [`OnAppEntersBackground`](#onappentersbackground) instead. 347 348</APIBox> 349<APIBox header="OnActivityDestroys" platforms={["android"]}> 350 351Defines the activity lifecycle listener that is called when the activity owning the JavaScript context is about to be destroyed. 352 353> **Note** This function is not available on iOS — you may want to use [`OnAppEntersBackground`](#onappentersbackground) instead. 354 355</APIBox> 356 357## Argument Types 358 359Fundamentally, only primitive and serializable data can be passed back and forth between the runtimes. However, usually native modules need to receive custom data structures — more sophisticated than just the dictionary/map where the values are of unknown (`Any`) type and so each value has to be validated and casted on its own. The Expo Modules API provides protocols to make it more convenient to work with data objects, to provide automatic validation, and finally, to ensure native type-safety on each object member. 360 361<APIBox header="Primitives"> 362 363All functions and view prop setters accept all common primitive types in Swift and Kotlin as the arguments. This includes arrays, dictionaries/maps and optionals of these primitive types. 364 365| Language | Supported primitive types | 366| -------- | ------------------------------------------------------------------------------------------------------------------------------ | 367| Swift | `Bool`, `Int`, `Int8`, `Int16`, `Int32`, `Int64`, `UInt`, `UInt8`, `UInt16`, `UInt32`, `UInt64`, `Float32`, `Double`, `String` | 368| Kotlin | `Boolean`, `Int`, `UInt`, `Float`, `Double`, `String`, `Pair` | 369 370</APIBox> 371<APIBox header="Convertibles"> 372 373_Convertibles_ are native types that can be initialized from certain specific kinds of data received from JavaScript. Such types are allowed to be used as an argument type in `Function`'s body. For example, when the `CGPoint` type is used as a function argument type, its instance can be created from an array of two numbers `(x, y)` or a JavaScript object with numeric `x` and `y` properties. 374 375Some common iOS types from `CoreGraphics` and `UIKit` system frameworks are already made convertible. 376 377| Native iOS Type | TypeScript | 378| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 379| `URL` | `string` with a URL. When scheme is not provided, it's assumed to be a file URL. | 380| `CGFloat` | `number` | 381| `CGPoint` | `{ x: number, y: number }` or `number[]` with _x_ and _y_ coords | 382| `CGSize` | `{ width: number, height: number }` or `number[]` with _width_ and _height_ | 383| `CGVector` | `{ dx: number, dy: number }` or `number[]` with _dx_ and _dy_ vector differentials | 384| `CGRect` | `{ x: number, y: number, width: number, height: number }` or `number[]` with _x_, _y_, _width_ and _height_ values | 385| `CGColor`<br/>`UIColor` | Color hex strings (`#RRGGBB`, `#RRGGBBAA`, `#RGB`, `#RGBA`), named colors following the [CSS3/SVG specification](https://www.w3.org/TR/css-color-3/#svg-color) or `"transparent"` | 386 387Similarly, some common Android types from packages like `java.io`, `java.net`, or `android.graphics` are also made convertible. 388 389| Native Android Type | TypeScript | 390| --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 391| `java.net.URL` | `string` with a URL. Note that the scheme has to be provided | 392| `android.net.Uri`<br/>`java.net.URI` | `string` with a URI. Note that the scheme has to be provided | 393| `java.io.File`<br/>`java.nio.file.Path` | `string` with a path to the file | 394| `android.graphics.Color` | Color hex strings (`#RRGGBB`, `#RRGGBBAA`, `#RGB`, `#RGBA`), named colors following the [CSS3/SVG specification](https://www.w3.org/TR/css-color-3/#svg-color) or `"transparent"` | 395| `kotlin.Pair<A, B>` | Array with two values, where the first one is of type _A_ and the second is of type _B_ | 396 397</APIBox> 398<APIBox header="Records"> 399 400_Record_ is a convertible type and an equivalent of the dictionary (Swift) or map (Kotlin), but represented as a struct where each field can have its own type and provide a default value. 401It is a better way to represent a JavaScript object with the native type-safety. 402 403<CodeBlocksTable> 404 405```swift 406struct FileReadOptions: Record { 407 @Field 408 var encoding: String = "utf8" 409 410 @Field 411 var position: Int = 0 412 413 @Field 414 var length: Int? 415} 416 417// Now this record can be used as an argument of the functions or the view prop setters. 418Function("readFile") { (path: String, options: FileReadOptions) -> String in 419 // Read the file using given `options` 420} 421``` 422 423```kotlin 424class FileReadOptions : Record { 425 @Field 426 val encoding: String = "utf8" 427 428 @Field 429 val position: Int = 0 430 431 @Field 432 val length: Int? 433} 434 435// Now this record can be used as an argument of the functions or the view prop setters. 436Function("readFile") { path: String, options: FileReadOptions -> 437 // Read the file using given `options` 438} 439``` 440 441</CodeBlocksTable> 442</APIBox> 443<APIBox header="Enums"> 444 445With enums we can go even further with the above example (with `FileReadOptions` record) and limit supported encodings to `"utf8"` and `"base64"`. To use an enum as an argument or record field, it must represent a primitive value (e.g. `String`, `Int`) and conform to `Enumerable`. 446 447<CodeBlocksTable> 448 449```swift 450enum FileEncoding: String, Enumerable { 451 case utf8 452 case base64 453} 454 455struct FileReadOptions: Record { 456 @Field 457 var encoding: FileEncoding = .utf8 458 // ... 459} 460``` 461 462```kotlin 463// Note: the constructor must have an argument called value. 464enum class FileEncoding(val value: String) : Enumerable { 465 utf8("utf8"), 466 base64("base64") 467} 468 469class FileReadOptions : Record { 470 @Field 471 val encoding: FileEncoding = FileEncoding.utf8 472 // ... 473} 474``` 475 476</CodeBlocksTable> 477</APIBox> 478<APIBox header="Eithers"> 479 480There are some use cases where you want to pass various types for a single function argument. This is where Either types might come in handy. 481They act as a container for a value of one of a couple of types. 482 483<CodeBlocksTable> 484 485```swift 486Function("foo") { (bar: Either<String, Int>) in 487 if let bar: String = bar.get() { 488 // `bar` is a String 489 } 490 if let bar: Int = bar.get() { 491 // `bar` is an Int 492 } 493} 494``` 495 496```kotlin 497Function("foo") { bar: Either<String, Int> -> 498 bar.get(String::class).let { 499 // `it` is a String 500 } 501 bar.get(Int::class).let { 502 // `it` is an Int 503 } 504} 505``` 506 507</CodeBlocksTable> 508 509The implementation for three Either types is currently provided out of the box, allowing you to use up to four different subtypes. 510 511- `Either<FirstType, SecondType>` — A container for one of two types. 512- `EitherOfThree<FirstType, SecondType, ThirdType>` — A container for one of three types. 513- `EitherOfFour<FirstType, SecondType, ThirdType, FourthType>` — A container for one of four types. 514 515> Either types are available as of SDK 47. 516 517</APIBox> 518 519## Native Classes 520 521<APIBox header="Module"> 522 523A base class for a native module. 524 525#### Properties 526 527<APIMethod 528 name="appContext" 529 comment="Provides access to the [`AppContext`](#appcontext)." 530 returnTypeName="AppContext" 531 isProperty={true} 532 isReturnTypeReference={true} 533/> 534 535#### Methods 536 537<APIMethod 538 name="sendEvent" 539 comment="Sends an event with a given name and a payload to JavaScript. See [`Sending events`](#sending-events)" 540 returnTypeName="void" 541 parameters={[ 542 { 543 name: 'eventName', 544 comment: 'The name of the JavaScript event', 545 typeName: 'string', 546 }, 547 { 548 name: 'payload', 549 comment: 'The event payload', 550 typeName: 'Android: Map<String, Any?> | Bundle\niOS: [String: Any?]', 551 }, 552 ]} 553/> 554 555</APIBox> 556 557<APIBox header="AppContext"> 558 559The app context is an interface to a single Expo app. 560 561</APIBox> 562 563<APIBox header="ExpoView"> 564 565A base class that should be used by all exported views. 566 567On iOS, `ExpoView` extends the `RCTView` which handles some styles (e.g. borders) and accessibility. 568 569#### Properties 570 571<APIMethod 572 name="appContext" 573 comment="Provides access to the [`AppContext`](#appcontext)." 574 returnTypeName="AppContext" 575 isProperty={true} 576 isReturnTypeReference={true} 577/> 578 579<hr /> 580 581#### Extending `ExpoView` 582 583To export your view using the [`View`](#view) component, your custom class must inherit from the `ExpoView`. By doing that you will get access to the [`AppContext`](#appcontext) object. It's the only way of communicating with other modules and the JavaScript runtime. Also, you can't change constructor parameters, becuase provided view will be initialized by `expo-modules-core`. 584 585<CodeBlocksTable> 586 587```swift 588class LinearGradientView: ExpoView {} 589 590public class LinearGradientModule: Module { 591 public func definition() -> ModuleDefinition { 592 View(LinearGradientView.self) { 593 // ... 594 } 595 } 596} 597``` 598 599```kotlin 600class LinearGradientView( 601 context: Context, 602 appContext: AppContext, 603) : ExpoView(context, appContext) 604 605class LinearGradientModule : Module() { 606 override fun definition() = ModuleDefinition { 607 View(LinearGradientView::class) { 608 // ... 609 } 610 } 611} 612``` 613 614</CodeBlocksTable> 615 616</APIBox> 617 618## Guides 619 620<APIBox header="Sending events"> 621 622While JavaScript/TypeScript to Native communication is mostly covered by native functions, you might also want to let the JavaScript/TypeScript code know about certain system events, for example, when the clipboard content changes. 623 624To do this, in the module definition, you need to provide the event names that the module can send using the [Events](#events) definition component. After that, you can use the `sendEvent(eventName, payload)` function on the module instance to send the actual event with some payload. For example, a minimal clipboard implementation that sends native events may look like this: 625 626<CodeBlocksTable> 627 628```swift 629let CLIPBOARD_CHANGED_EVENT_NAME = "onClipboardChanged" 630 631public class ClipboardModule: Module { 632 public func definition() -> ModuleDefinition { 633 Events(CLIPBOARD_CHANGED_EVENT_NAME) 634 635 OnStartObserving { 636 NotificationCenter.default.addObserver( 637 self, 638 selector: #selector(self.clipboardChangedListener), 639 name: UIPasteboard.changedNotification, 640 object: nil 641 ) 642 } 643 644 OnStopObserving { 645 NotificationCenter.default.removeObserver( 646 self, 647 name: UIPasteboard.changedNotification, 648 object: nil 649 ) 650 } 651 } 652 653 @objc 654 private func clipboardChangedListener() { 655 sendEvent(CLIPBOARD_CHANGED_EVENT_NAME, [ 656 "contentTypes": availableContentTypes() 657 ]) 658 } 659} 660``` 661 662```kotlin 663const val CLIPBOARD_CHANGED_EVENT_NAME = "onClipboardChanged" 664 665class ClipboardModule : Module() { 666 override fun definition() = ModuleDefinition { 667 Events(CLIPBOARD_CHANGED_EVENT_NAME) 668 669 OnStartObserving { 670 clipboardManager?.addPrimaryClipChangedListener(listener) 671 } 672 673 OnStopObserving { 674 clipboardManager?.removePrimaryClipChangedListener(listener) 675 } 676 } 677 678 private val clipboardManager: ClipboardManager? 679 get() = appContext.reactContext?.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager 680 681 private val listener = ClipboardManager.OnPrimaryClipChangedListener { 682 clipboardManager?.primaryClipDescription?.let { clip -> 683 [email protected]( 684 CLIPBOARD_CHANGED_EVENT_NAME, 685 bundleOf( 686 "contentTypes" to availableContentTypes(clip) 687 ) 688 ) 689 } 690 } 691} 692``` 693 694</CodeBlocksTable> 695 696To subscribe to these events in JavaScript/TypeScript, you need to wrap the native module with `EventEmitter` class as shown: 697 698```ts TypeScript 699import { requireNativeModule, EventEmitter, Subscription } from 'expo-modules-core'; 700 701const ClipboardModule = requireNativeModule('Clipboard'); 702const emitter = new EventEmitter(ClipboardModule); 703 704export function addClipboardListener(listener: (event) => void): Subscription { 705 return emitter.addListener('onClipboardChanged', listener); 706} 707``` 708 709</APIBox> 710 711<APIBox header="View callbacks"> 712 713Some events are connected to a certain view. For example, the touch event should be sent only to the underlying JavaScript view which was pressed. In that case, you can't use `sendEvent` described in [`Sending events`](#sending-events). The `expo-modules-core` introduces a view callbacks mechanism to handle view-bound events. 714 715To use it, in the view definition, you need to provide the event names that the view can send using the [Events](#events) definition component. After that, you need to declare a property of type `EventDispatcher` in your view class. The name of the declared property has to be the same as the name exported in the `Events` component. Later, you can call it as a function and pass a payload of type `[String: Any?]` on iOS and `Map<String, Any?>` on Android. 716 717> **Note**: On Android, it's possible to specify the payload type. In case of types that don't convert into objects, the payload will be encapsulated and stored under the `payload` key: `{payload: <provided value>}`. 718 719<CodeBlocksTable> 720 721```swift 722class CameraViewModule: Module { 723 public func definition() -> ModuleDefinition { 724 View(CamerView.self) { 725 Events( 726 "onCameraReady" 727 ) 728 729 // ... 730 } 731 } 732} 733 734class CameraView: ExpoView { 735 let onCameraReady = EventDispatcher() 736 737 func callOnCameraReady() { 738 onCameraReady([ 739 "message": "Camera was mounted" 740 ]); 741 } 742} 743``` 744 745```kotlin 746class CameraViewModule : Module() { 747 override fun definition() = ModuleDefinition { 748 View(ExpoCameraView::class) { 749 Events( 750 "onCameraReady" 751 ) 752 753 // ... 754 } 755 } 756} 757 758class CameraView( 759 context: Context, 760 appContext: AppContext 761) : ExpoView(context, appContext) { 762 val onCameraReady by EventDispatcher() 763 764 fun callOnCameraReady() { 765 onCameraReady(mapOf( 766 "message" to "Camera was mounted" 767 )); 768 } 769} 770``` 771 772</CodeBlocksTable> 773 774To subscribe to these events in JavaScript/TypeScript, you need to pass a function to the native view as shown: 775 776```ts TypeScript 777import { requireNativeViewManager } from 'expo-modules-core'; 778 779const CameraView = requireNativeViewManager('CameraView'); 780 781export default function MainView() { 782 const onCameraReady = event => { 783 console.log(event.nativeEvent); 784 }; 785 786 return <CameraView onCameraReady={onCameraReady} />; 787} 788``` 789 790Provided payload is available under the `nativeEvent` key. 791 792</APIBox> 793 794## Examples 795 796<CodeBlocksTable> 797 798```swift 799public class MyModule: Module { 800 public func definition() -> ModuleDefinition { 801 Name("MyFirstExpoModule") 802 803 Function("hello") { (name: String) in 804 return "Hello \(name)!" 805 } 806 } 807} 808``` 809 810```kotlin 811class MyModule : Module() { 812 override fun definition() = ModuleDefinition { 813 Name("MyFirstExpoModule") 814 815 Function("hello") { name: String -> 816 return "Hello $name!" 817 } 818 } 819} 820``` 821 822</CodeBlocksTable> 823 824For more examples from real modules, you can refer to Expo modules that already use this API on GitHub: 825 826- `expo-battery` ([Swift](https://github.com/expo/expo/tree/main/packages/expo-battery/ios)) 827- `expo-cellular` ([Swift](https://github.com/expo/expo/tree/main/packages/expo-cellular/ios), [Kotlin](https://github.com/expo/expo/tree/main/packages/expo-cellular/android/src/main/java/expo/modules/cellular)) 828- `expo-clipboard` ([Swift](https://github.com/expo/expo/tree/main/packages/expo-clipboard/ios), [Kotlin](https://github.com/expo/expo/tree/main/packages/expo-clipboard/android/src/main/java/expo/modules/clipboard)) 829- `expo-crypto` ([Swift](https://github.com/expo/expo/tree/main/packages/expo-crypto/ios), [Kotlin](https://github.com/expo/expo/tree/main/packages/expo-crypto/android/src/main/java/expo/modules/crypto)) 830- `expo-haptics` ([Swift](https://github.com/expo/expo/tree/main/packages/expo-haptics/ios)) 831- `expo-image-manipulator` ([Swift](https://github.com/expo/expo/tree/main/packages/expo-image-manipulator/ios)) 832- `expo-image-picker` ([Swift](https://github.com/expo/expo/tree/main/packages/expo-image-picker/ios), [Kotlin](https://github.com/expo/expo/tree/main/packages/expo-image-picker/android/src/main/java/expo/modules/imagepicker)) 833- `expo-linear-gradient` ([Swift](https://github.com/expo/expo/tree/main/packages/expo-linear-gradient/ios), [Kotlin](https://github.com/expo/expo/tree/main/packages/expo-linear-gradient/android/src/main/java/expo/modules/lineargradient)) 834- `expo-localization` ([Swift](https://github.com/expo/expo/tree/main/packages/expo-localization/ios), [Kotlin](https://github.com/expo/expo/tree/main/packages/expo-localization/android/src/main/java/expo/modules/localization)) 835- `expo-store-review` ([Swift](https://github.com/expo/expo/tree/main/packages/expo-store-review/ios)) 836- `expo-system-ui` ([Swift](https://github.com/expo/expo/tree/main/packages/expo-system-ui/ios/ExpoSystemUI)) 837- `expo-video-thumbnails` ([Swift](https://github.com/expo/expo/tree/main/packages/expo-video-thumbnails/ios)) 838- `expo-web-browser` ([Swift](https://github.com/expo/expo/blob/main/packages/expo-web-browser/ios), [Kotlin](https://github.com/expo/expo/blob/main/packages/expo-web-browser/android/src/main/java/expo/modules/webbrowser)) 839