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 primitives 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- **definition**: `() -> ViewDefinition` — A builder of the view definition. 240 241<CodeBlocksTable> 242 243```swift 244View(UITextView.self) { 245 Prop("text") { ... } 246} 247``` 248 249```kotlin 250View(TextView::class) { 251 Prop("text") { ... } 252} 253``` 254 255</CodeBlocksTable> 256 257> 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. 258 259</APIBox> 260<APIBox header="Prop"> 261 262Defines a setter for the view prop of given name. 263 264#### Arguments 265 266- **name**: `String` — Name of view prop that you want to define a setter. 267- **setter**: `(view: ViewType, value: ValueType) -> ()` — Closure that is invoked when the view rerenders. 268 269This property can only be used within a [`ViewManager`](#viewmanager) closure. 270 271<CodeBlocksTable> 272 273```swift 274Prop("background") { (view: UIView, color: UIColor) in 275 view.backgroundColor = color 276} 277``` 278 279```kotlin 280Prop("background") { view: View, @ColorInt color: Int -> 281 view.setBackgroundColor(color) 282} 283``` 284 285</CodeBlocksTable> 286 287> **Note** Props of function type (callbacks) are not supported yet. 288 289</APIBox> 290<APIBox header="OnCreate"> 291 292Defines 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. 293 294</APIBox> 295<APIBox header="OnDestroy"> 296 297Defines module's lifecycle listener that is called when the module is about to be deallocated. Use it instead of module's class destructor. 298 299</APIBox> 300<APIBox header="OnStartObserving"> 301 302Defines the function that is invoked when the first event listener is added. 303 304</APIBox> 305<APIBox header="OnStopObserving"> 306 307Defines the function that is invoked when all event listeners are removed. 308 309</APIBox> 310<APIBox header="OnAppContextDestroys"> 311 312Defines module's lifecycle listener that is called when the app context owning the module is about to be deallocated. 313 314</APIBox> 315<APIBox header="OnAppEntersForeground" platforms={["ios"]}> 316 317Defines the listener that is called when the app is about to enter the foreground mode. 318 319> **Note** This function is not available on Android — you may want to use [`OnActivityEntersForeground`](#onactivityentersforeground) instead. 320 321</APIBox> 322<APIBox header="OnAppEntersBackground" platforms={["ios"]}> 323 324Defines the listener that is called when the app enters the background mode. 325 326> **Note** This function is not available on Android — you may want to use [`OnActivityEntersBackground`](#onactivityentersbackground) instead. 327 328</APIBox> 329<APIBox header="OnAppBecomesActive" platforms={["ios"]}> 330 331Defines the listener that is called when the app becomes active again (after `OnAppEntersForeground`). 332 333> **Note** This function is not available on Android — you may want to use [`OnActivityEntersForeground`](#onactivityentersforeground) instead. 334 335</APIBox> 336<APIBox header="OnActivityEntersForeground" platforms={["android"]}> 337 338Defines the activity lifecycle listener that is called right after the activity is resumed. 339 340> **Note** This function is not available on iOS — you may want to use [`OnAppEntersForeground`](#onappentersforeground) instead. 341 342</APIBox> 343<APIBox header="OnActivityEntersBackground" platforms={["android"]}> 344 345Defines the activity lifecycle listener that is called right after the activity is paused. 346 347> **Note** This function is not available on iOS — you may want to use [`OnAppEntersBackground`](#onappentersbackground) instead. 348 349</APIBox> 350<APIBox header="OnActivityDestroys" platforms={["android"]}> 351 352Defines the activity lifecycle listener that is called when the activity owning the JavaScript context is about to be destroyed. 353 354> **Note** This function is not available on iOS — you may want to use [`OnAppEntersBackground`](#onappentersbackground) instead. 355 356</APIBox> 357 358## Argument Types 359 360Fundamentally, 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. 361 362<APIBox header="Primitives"> 363 364All 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. 365 366| Language | Supported primitive types | 367| -------- | ------------------------------------------------------------------------------------------------------------------------------ | 368| Swift | `Bool`, `Int`, `Int8`, `Int16`, `Int32`, `Int64`, `UInt`, `UInt8`, `UInt16`, `UInt32`, `UInt64`, `Float32`, `Double`, `String` | 369| Kotlin | `Boolean`, `Int`, `UInt`, `Float`, `Double`, `String`, `Pair` | 370 371</APIBox> 372<APIBox header="Convertibles"> 373 374_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. 375 376Some common iOS types from `CoreGraphics` and `UIKit` system frameworks are already made convertible. 377 378| Native iOS Type | TypeScript | 379| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 380| `URL` | `string` with a URL. When scheme is not provided, it's assumed to be a file URL. | 381| `CGFloat` | `number` | 382| `CGPoint` | `{ x: number, y: number }` or `number[]` with _x_ and _y_ coords | 383| `CGSize` | `{ width: number, height: number }` or `number[]` with _width_ and _height_ | 384| `CGVector` | `{ dx: number, dy: number }` or `number[]` with _dx_ and _dy_ vector differentials | 385| `CGRect` | `{ x: number, y: number, width: number, height: number }` or `number[]` with _x_, _y_, _width_ and _height_ values | 386| `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"` | 387 388Similarly, some common Android types from packages like `java.io`, `java.net`, or `android.graphics` are also made convertible. 389 390| Native Android Type | TypeScript | 391| --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 392| `java.net.URL` | `string` with a URL. Note that the scheme has to be provided | 393| `android.net.Uri`<br/>`java.net.URI` | `string` with a URI. Note that the scheme has to be provided | 394| `java.io.File`<br/>`java.nio.file.Path` | `string` with a path to the file | 395| `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"` | 396| `kotlin.Pair<A, B>` | Array with two values, where the first one is of type _A_ and the second is of type _B_ | 397 398</APIBox> 399<APIBox header="Records"> 400 401_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. 402It is a better way to represent a JavaScript object with the native type-safety. 403 404<CodeBlocksTable> 405 406```swift 407struct FileReadOptions: Record { 408 @Field 409 var encoding: String = "utf8" 410 411 @Field 412 var position: Int = 0 413 414 @Field 415 var length: Int? 416} 417 418// Now this record can be used as an argument of the functions or the view prop setters. 419Function("readFile") { (path: String, options: FileReadOptions) -> String in 420 // Read the file using given `options` 421} 422``` 423 424```kotlin 425class FileReadOptions : Record { 426 @Field 427 val encoding: String = "utf8" 428 429 @Field 430 val position: Int = 0 431 432 @Field 433 val length: Int? 434} 435 436// Now this record can be used as an argument of the functions or the view prop setters. 437Function("readFile") { path: String, options: FileReadOptions -> 438 // Read the file using given `options` 439} 440``` 441 442</CodeBlocksTable> 443</APIBox> 444<APIBox header="Enums"> 445 446With 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`. 447 448<CodeBlocksTable> 449 450```swift 451enum FileEncoding: String, Enumerable { 452 case utf8 453 case base64 454} 455 456struct FileReadOptions: Record { 457 @Field 458 var encoding: FileEncoding = .utf8 459 // ... 460} 461``` 462 463```kotlin 464// Note: the constructor must have an argument called value. 465enum class FileEncoding(val value: String) : Enumerable { 466 utf8("utf8"), 467 base64("base64") 468} 469 470class FileReadOptions : Record { 471 @Field 472 val encoding: FileEncoding = FileEncoding.utf8 473 // ... 474} 475``` 476 477</CodeBlocksTable> 478</APIBox> 479<APIBox header="Eithers"> 480 481There 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. 482They act as a container for a value of one of a couple of types. 483 484<CodeBlocksTable> 485 486```swift 487Function("foo") { (bar: Either<String, Int>) in 488 if let bar: String = bar.get() { 489 // `bar` is a String 490 } 491 if let bar: Int = bar.get() { 492 // `bar` is an Int 493 } 494} 495``` 496 497```kotlin 498Function("foo") { bar: Either<String, Int> -> 499 bar.get(String::class).let { 500 // `it` is a String 501 } 502 bar.get(Int::class).let { 503 // `it` is an Int 504 } 505} 506``` 507 508</CodeBlocksTable> 509 510The implementation for three Either types is currently provided out of the box, allowing you to use up to four different subtypes. 511 512- `Either<FirstType, SecondType>` — A container for one of two types. 513- `EitherOfThree<FirstType, SecondType, ThirdType>` — A container for one of three types. 514- `EitherOfFour<FirstType, SecondType, ThirdType, FourthType>` — A container for one of four types. 515 516> Either types are available as of SDK 47. 517 518</APIBox> 519 520## Native Classes 521 522<APIBox header="Module"> 523 524A base class for a native module. 525 526#### Properties 527 528<APIMethod 529 name="appContext" 530 comment="Provides access to the [`AppContext`](#appcontext)." 531 returnTypeName="AppContext" 532 isProperty={true} 533 isReturnTypeReference={true} 534/> 535 536#### Methods 537 538<APIMethod 539 name="sendEvent" 540 comment="Sends an event with a given name and a payload to JavaScript. See [`Sending events`](#sending-events)" 541 returnTypeName="void" 542 parameters={[ 543 { 544 name: 'eventName', 545 comment: 'The name of the JavaScript event', 546 typeName: 'string', 547 }, 548 { 549 name: 'payload', 550 comment: 'The event payload', 551 typeName: 'Android: Map<String, Any?> | Bundle\niOS: [String: Any?]', 552 }, 553 ]} 554/> 555 556</APIBox> 557 558<APIBox header="AppContext"> 559 560The app context is an interface to a single Expo app. 561 562#### Properties 563 564<APIMethod 565 name="constants" 566 comment="Provides access to app's constants from legacy module registry." 567 returnTypeName="Android: ConstantsInterface? iOS: EXConstantsInterface?" 568 isProperty={true} 569/> 570 571<APIMethod 572 name="permissions" 573 comment="Provides access to the permissions manager from legacy module registry." 574 returnTypeName="Android: Permissions? iOS: EXPermissionsInterface?" 575 isProperty={true} 576/> 577 578<APIMethod 579 name="imageLoader" 580 comment="Provides access to the image loader from the legacy module registry." 581 returnTypeName="Android: ImageLoaderInterface? iOS: EXImageLoaderInterface?" 582 isProperty={true} 583/> 584 585<APIMethod 586 name="barcodeScanner" 587 comment="Provides access to the bar code scanner manager from the legacy module registry." 588 returnTypeName="ImageLoaderInterface?" 589 isProperty={true} 590 platforms={['Android']} 591/> 592 593<APIMethod 594 name="camera" 595 comment="Provides access to the camera view manager from the legacy module registry." 596 returnTypeName="CameraViewInterface?" 597 isProperty={true} 598 platforms={['Android']} 599/> 600 601<APIMethod 602 name="font" 603 comment="Provides access to the font manager from the legacy module registry." 604 returnTypeName="FontManagerInterface?" 605 isProperty={true} 606 platforms={['Android']} 607/> 608 609<APIMethod 610 name="sensor" 611 comment="Provides access to the sensor manager from the legacy module registry." 612 returnTypeName="SensorServiceInterface?" 613 isProperty={true} 614 platforms={['Android']} 615/> 616 617<APIMethod 618 name="taskManager" 619 comment="Provides access to the task manager from the legacy module registry." 620 returnTypeName="TaskManagerInterface?" 621 isProperty={true} 622 platforms={['Android']} 623/> 624 625<APIMethod 626 name="activityProvider" 627 comment="Provides access to the activity provider from the legacy module registry." 628 returnTypeName="ActivityProvider?" 629 isProperty={true} 630 platforms={['Android']} 631/> 632 633<APIMethod 634 name="reactContext" 635 comment="Provides access to the react application context." 636 returnTypeName="Context?" 637 isProperty={true} 638 platforms={['Android']} 639/> 640 641<APIMethod 642 name="hasActiveReactInstance" 643 comment="Checks if there is an not-null, alive react native instance." 644 returnTypeName="Boolean" 645 isProperty={true} 646 platforms={['Android']} 647/> 648 649<APIMethod 650 name="utilities" 651 comment="Provides access to the utilities from legacy module registry." 652 returnTypeName="EXUtilitiesInterface?" 653 isProperty={true} 654 platforms={['iOS']} 655/> 656 657</APIBox> 658 659<APIBox header="ExpoView"> 660 661A base class that should be used by all exported views. 662 663On iOS, `ExpoView` extends the `RCTView` which handles some styles (e.g. borders) and accessibility. 664 665#### Properties 666 667<APIMethod 668 name="appContext" 669 comment="Provides access to the [`AppContext`](#appcontext)." 670 returnTypeName="AppContext" 671 isProperty={true} 672 isReturnTypeReference={true} 673/> 674 675<hr /> 676 677#### Extending `ExpoView` 678 679To 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, because provided view will be initialized by `expo-modules-core`. 680 681<CodeBlocksTable> 682 683```swift 684class LinearGradientView: ExpoView {} 685 686public class LinearGradientModule: Module { 687 public func definition() -> ModuleDefinition { 688 View(LinearGradientView.self) { 689 // ... 690 } 691 } 692} 693``` 694 695```kotlin 696class LinearGradientView( 697 context: Context, 698 appContext: AppContext, 699) : ExpoView(context, appContext) 700 701class LinearGradientModule : Module() { 702 override fun definition() = ModuleDefinition { 703 View(LinearGradientView::class) { 704 // ... 705 } 706 } 707} 708``` 709 710</CodeBlocksTable> 711 712</APIBox> 713 714## Guides 715 716<APIBox header="Sending events"> 717 718While 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. 719 720To 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: 721 722<CodeBlocksTable> 723 724```swift 725let CLIPBOARD_CHANGED_EVENT_NAME = "onClipboardChanged" 726 727public class ClipboardModule: Module { 728 public func definition() -> ModuleDefinition { 729 Events(CLIPBOARD_CHANGED_EVENT_NAME) 730 731 OnStartObserving { 732 NotificationCenter.default.addObserver( 733 self, 734 selector: #selector(self.clipboardChangedListener), 735 name: UIPasteboard.changedNotification, 736 object: nil 737 ) 738 } 739 740 OnStopObserving { 741 NotificationCenter.default.removeObserver( 742 self, 743 name: UIPasteboard.changedNotification, 744 object: nil 745 ) 746 } 747 } 748 749 @objc 750 private func clipboardChangedListener() { 751 sendEvent(CLIPBOARD_CHANGED_EVENT_NAME, [ 752 "contentTypes": availableContentTypes() 753 ]) 754 } 755} 756``` 757 758```kotlin 759const val CLIPBOARD_CHANGED_EVENT_NAME = "onClipboardChanged" 760 761class ClipboardModule : Module() { 762 override fun definition() = ModuleDefinition { 763 Events(CLIPBOARD_CHANGED_EVENT_NAME) 764 765 OnStartObserving { 766 clipboardManager?.addPrimaryClipChangedListener(listener) 767 } 768 769 OnStopObserving { 770 clipboardManager?.removePrimaryClipChangedListener(listener) 771 } 772 } 773 774 private val clipboardManager: ClipboardManager? 775 get() = appContext.reactContext?.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager 776 777 private val listener = ClipboardManager.OnPrimaryClipChangedListener { 778 clipboardManager?.primaryClipDescription?.let { clip -> 779 [email protected]( 780 CLIPBOARD_CHANGED_EVENT_NAME, 781 bundleOf( 782 "contentTypes" to availableContentTypes(clip) 783 ) 784 ) 785 } 786 } 787} 788``` 789 790</CodeBlocksTable> 791 792To subscribe to these events in JavaScript/TypeScript, you need to wrap the native module with `EventEmitter` class as shown: 793 794```ts TypeScript 795import { requireNativeModule, EventEmitter, Subscription } from 'expo-modules-core'; 796 797const ClipboardModule = requireNativeModule('Clipboard'); 798const emitter = new EventEmitter(ClipboardModule); 799 800export function addClipboardListener(listener: (event) => void): Subscription { 801 return emitter.addListener('onClipboardChanged', listener); 802} 803``` 804 805</APIBox> 806 807<APIBox header="View callbacks"> 808 809Some 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. 810 811To 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. 812 813> **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>}`. 814 815<CodeBlocksTable> 816 817```swift 818class CameraViewModule: Module { 819 public func definition() -> ModuleDefinition { 820 View(CamerView.self) { 821 Events( 822 "onCameraReady" 823 ) 824 825 // ... 826 } 827 } 828} 829 830class CameraView: ExpoView { 831 let onCameraReady = EventDispatcher() 832 833 func callOnCameraReady() { 834 onCameraReady([ 835 "message": "Camera was mounted" 836 ]); 837 } 838} 839``` 840 841```kotlin 842class CameraViewModule : Module() { 843 override fun definition() = ModuleDefinition { 844 View(ExpoCameraView::class) { 845 Events( 846 "onCameraReady" 847 ) 848 849 // ... 850 } 851 } 852} 853 854class CameraView( 855 context: Context, 856 appContext: AppContext 857) : ExpoView(context, appContext) { 858 val onCameraReady by EventDispatcher() 859 860 fun callOnCameraReady() { 861 onCameraReady(mapOf( 862 "message" to "Camera was mounted" 863 )); 864 } 865} 866``` 867 868</CodeBlocksTable> 869 870To subscribe to these events in JavaScript/TypeScript, you need to pass a function to the native view as shown: 871 872```ts TypeScript 873import { requireNativeViewManager } from 'expo-modules-core'; 874 875const CameraView = requireNativeViewManager('CameraView'); 876 877export default function MainView() { 878 const onCameraReady = event => { 879 console.log(event.nativeEvent); 880 }; 881 882 return <CameraView onCameraReady={onCameraReady} />; 883} 884``` 885 886Provided payload is available under the `nativeEvent` key. 887 888</APIBox> 889 890## Examples 891 892<CodeBlocksTable> 893 894```swift 895public class MyModule: Module { 896 public func definition() -> ModuleDefinition { 897 Name("MyFirstExpoModule") 898 899 Function("hello") { (name: String) in 900 return "Hello \(name)!" 901 } 902 } 903} 904``` 905 906```kotlin 907class MyModule : Module() { 908 override fun definition() = ModuleDefinition { 909 Name("MyFirstExpoModule") 910 911 Function("hello") { name: String -> 912 return "Hello $name!" 913 } 914 } 915} 916``` 917 918</CodeBlocksTable> 919 920For more examples from real modules, you can refer to Expo modules that already use this API on GitHub: 921 922- `expo-battery` ([Swift](https://github.com/expo/expo/tree/main/packages/expo-battery/ios)) 923- `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)) 924- `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)) 925- `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)) 926- `expo-haptics` ([Swift](https://github.com/expo/expo/tree/main/packages/expo-haptics/ios)) 927- `expo-image-manipulator` ([Swift](https://github.com/expo/expo/tree/main/packages/expo-image-manipulator/ios)) 928- `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)) 929- `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)) 930- `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)) 931- `expo-store-review` ([Swift](https://github.com/expo/expo/tree/main/packages/expo-store-review/ios)) 932- `expo-system-ui` ([Swift](https://github.com/expo/expo/tree/main/packages/expo-system-ui/ios/ExpoSystemUI)) 933- `expo-video-thumbnails` ([Swift](https://github.com/expo/expo/tree/main/packages/expo-video-thumbnails/ios)) 934- `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)) 935