xref: /expo/docs/pages/modules/module-api.mdx (revision 241c53c0)
1---
2title: Module API Reference
3description: An API reference of Expo modules API.
4---
5
6import { CodeBlocksTable } from '~/components/plugins/CodeBlocksTable';
7import { APIBox } from '~/components/plugins/APIBox';
8import { PlatformTags } from '~/ui/components/Tag';
9import { APIMethod } from '~/components/plugins/api/APISectionMethods';
10
11The 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.
12
13## Definition Components
14
15As you might have noticed in the snippets on the [Get Started](./get-started.mdx) page, each module class must implement the `definition` function.
16The module definition consists of the DSL components that describe the module's functionality and behavior.
17
18<APIBox header="Name">
19
20Sets 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.
21
22```swift Swift / Kotlin
23Name("MyModuleName")
24```
25
26</APIBox>
27<APIBox header="Constants">
28
29Sets constant properties on the module. Can take a dictionary or a closure that returns a dictionary.
30
31<CodeBlocksTable>
32
33```swift
34// Created from the dictionary
35Constants([
36  "PI": Double.pi
37])
38
39// or returned by the closure
40Constants {
41  return [
42    "PI": Double.pi
43  ]
44}
45```
46
47```kotlin
48// Passed as arguments
49Constants(
50  "PI" to kotlin.math.PI
51)
52
53// or returned by the closure
54Constants {
55  return@Constants mapOf(
56    "PI" to kotlin.math.PI
57  )
58}
59```
60
61</CodeBlocksTable>
62</APIBox>
63<APIBox header="Function">
64
65Defines 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.
66
67#### Arguments
68
69- **name**: `String` — Name of the function that you'll call from JavaScript.
70- **body**: `(args...) -> ReturnType` — The closure to run when the function is called.
71
72The 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.
73
74See the [Argument Types](#argument-types) section for more details on what types can be used in the function body.
75
76<CodeBlocksTable>
77
78```swift
79Function("syncFunction") { (message: String) in
80  return message
81}
82```
83
84```kotlin
85Function("syncFunction") { message: String ->
86  return@Function message
87}
88```
89
90</CodeBlocksTable>
91
92```js JavaScript
93import { requireNativeModule } from 'expo-modules-core';
94
95// Assume that we have named the module "MyModule"
96const MyModule = requireNativeModule('MyModule');
97
98function getMessage() {
99  return MyModule.syncFunction('bar');
100}
101```
102
103</APIBox>
104<APIBox header="AsyncFunction">
105
106Defines 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.
107
108#### Arguments
109
110- **name**: `String` — Name of the function that you'll call from JavaScript.
111- **body**: `(args...) -> ReturnType` — The closure to run when the function is called.
112
113If 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.
114The function can receive up to 8 arguments (including the promise).
115
116See the [Argument Types](#argument-types) section for more details on what types can be used in the function body.
117
118It is recommended to use `AsyncFunction` over `Function` when it:
119
120- does I/O bound tasks such as sending network requests or interacting with the file system
121- needs to be run on different thread, e.g. the main UI thread for UI-related tasks
122- is an extensive or long-lasting operation that would block the JavaScript thread which in turn would reduce the responsiveness of the application
123
124<CodeBlocksTable>
125
126```swift
127AsyncFunction("asyncFunction") { (message: String, promise: Promise) in
128  DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
129    promise.resolve(message)
130  }
131}
132```
133
134```kotlin
135AsyncFunction("asyncFunction") { message: String, promise: Promise ->
136  launch(Dispatchers.Main) {
137    promise.resolve(message)
138  }
139}
140```
141
142</CodeBlocksTable>
143
144```js JavaScript
145import { requireNativeModule } from 'expo-modules-core';
146
147// Assume that we have named the module "MyModule"
148const MyModule = requireNativeModule('MyModule');
149
150async function getMessageAsync() {
151  return await MyModule.asyncFunction('bar');
152}
153```
154
155---
156
157#### Kotlin coroutines <PlatformTags prefix="" platforms={['android']} />
158
159`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).
160
161`AsyncFunction` with suspendable body can't receive `Promise` as an argument. It uses a suspension mechanism to execute asynchronous calls.
162The 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.
163
164By 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.
165This scope's lifecycle is bound to the module's lifecycle - all unfinished suspend functions will be canceled when the module is deallocated.
166
167```kotlin Kotlin
168AsyncFunction("suspendFunction") Coroutine { message: String ->
169  launch {
170    return@Coroutine message
171  }
172}
173```
174
175</APIBox>
176<APIBox header="Events">
177
178Defines event names that the module can send to JavaScript.
179
180> **Note**: This component can be used inside of the [`View`](#view) block to define callback names. See [`View callbacks`](#view-callbacks)
181
182<CodeBlocksTable>
183
184```swift
185Events("onCameraReady", "onPictureSaved", "onBarCodeScanned")
186```
187
188```kotlin
189Events("onCameraReady", "onPictureSaved", "onBarCodeScanned")
190```
191
192</CodeBlocksTable>
193
194See [Sending events](#sending-events) to learn how to send events from the native code to JavaScript/TypeScript.
195
196</APIBox>
197<APIBox header="ViewManager">
198
199> **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.
200
201Enables 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).
202
203<CodeBlocksTable>
204
205```swift
206ViewManager {
207  View {
208    MyNativeView()
209  }
210
211  Prop("isHidden") { (view: UIView, hidden: Bool) in
212    view.isHidden = hidden
213  }
214}
215```
216
217```kotlin
218ViewManager {
219  View { context ->
220    MyNativeView(context)
221  }
222
223  Prop("isHidden") { view: View, hidden: Bool ->
224    view.isVisible = !hidden
225  }
226}
227```
228
229</CodeBlocksTable>
230</APIBox>
231<APIBox header="View">
232
233Enables the module to be used as a native view. Definition components that are accepted as part of the view definition: [`Prop`](#prop), [`Events`](#events).
234
235#### Arguments
236
237- **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).
238- **definition**: `() -> ViewDefinition` — A builder of the view definition.
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
375---
376
377Some common iOS types from `CoreGraphics` and `UIKit` system frameworks are already made convertible.
378
379| Native iOS Type         | TypeScript                                                                                                                                                                        |
380| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
381| `URL`                   | `string` with a URL. When scheme is not provided, it's assumed to be a file URL.                                                                                                  |
382| `CGFloat`               | `number`                                                                                                                                                                          |
383| `CGPoint`               | `{ x: number, y: number }` or `number[]` with _x_ and _y_ coords                                                                                                                  |
384| `CGSize`                | `{ width: number, height: number }` or `number[]` with _width_ and _height_                                                                                                       |
385| `CGVector`              | `{ dx: number, dy: number }` or `number[]` with _dx_ and _dy_ vector differentials                                                                                                |
386| `CGRect`                | `{ x: number, y: number, width: number, height: number }` or `number[]` with _x_, _y_, _width_ and _height_ values                                                                |
387| `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"` |
388
389---
390
391Similarly, some common Android types from packages like `java.io`, `java.net`, or `android.graphics` are also made convertible.
392
393| Native Android Type                                                           | TypeScript                                                                                                                                                                        |
394| ----------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
395| `java.net.URL`                                                                | `string` with a URL. Note that the scheme has to be provided                                                                                                                      |
396| `android.net.Uri`<br/>`java.net.URI`                                          | `string` with a URI. Note that the scheme has to be provided                                                                                                                      |
397| `java.io.File`<br/>`java.nio.file.Path` (is only available on Android API 26) | `string` with a path to the file                                                                                                                                                  |
398| `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"` |
399| `kotlin.Pair<A, B>`                                                           | Array with two values, where the first one is of type _A_ and the second is of type _B_                                                                                           |
400
401</APIBox>
402<APIBox header="Records">
403
404_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.
405It is a better way to represent a JavaScript object with the native type-safety.
406
407<CodeBlocksTable>
408
409```swift
410struct FileReadOptions: Record {
411  @Field
412  var encoding: String = "utf8"
413
414  @Field
415  var position: Int = 0
416
417  @Field
418  var length: Int?
419}
420
421// Now this record can be used as an argument of the functions or the view prop setters.
422Function("readFile") { (path: String, options: FileReadOptions) -> String in
423  // Read the file using given `options`
424}
425```
426
427```kotlin
428class FileReadOptions : Record {
429  @Field
430  val encoding: String = "utf8"
431
432  @Field
433  val position: Int = 0
434
435  @Field
436  val length: Int?
437}
438
439// Now this record can be used as an argument of the functions or the view prop setters.
440Function("readFile") { path: String, options: FileReadOptions ->
441  // Read the file using given `options`
442}
443```
444
445</CodeBlocksTable>
446</APIBox>
447<APIBox header="Enums">
448
449With 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`.
450
451<CodeBlocksTable>
452
453```swift
454enum FileEncoding: String, Enumerable {
455  case utf8
456  case base64
457}
458
459struct FileReadOptions: Record {
460  @Field
461  var encoding: FileEncoding = .utf8
462  // ...
463}
464```
465
466```kotlin
467// Note: the constructor must have an argument called value.
468enum class FileEncoding(val value: String) : Enumerable {
469  utf8("utf8"),
470  base64("base64")
471}
472
473class FileReadOptions : Record {
474  @Field
475  val encoding: FileEncoding = FileEncoding.utf8
476  // ...
477}
478```
479
480</CodeBlocksTable>
481</APIBox>
482<APIBox header="Eithers">
483
484There 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.
485They act as a container for a value of one of a couple of types.
486
487<CodeBlocksTable>
488
489```swift
490Function("foo") { (bar: Either<String, Int>) in
491  if let bar: String = bar.get() {
492    // `bar` is a String
493  }
494  if let bar: Int = bar.get() {
495    // `bar` is an Int
496  }
497}
498```
499
500```kotlin
501Function("foo") { bar: Either<String, Int> ->
502  bar.get(String::class).let {
503    // `it` is a String
504  }
505  bar.get(Int::class).let {
506    // `it` is an Int
507  }
508}
509```
510
511</CodeBlocksTable>
512
513The implementation for three Either types is currently provided out of the box, allowing you to use up to four different subtypes.
514
515- `Either<FirstType, SecondType>` — A container for one of two types.
516- `EitherOfThree<FirstType, SecondType, ThirdType>` — A container for one of three types.
517- `EitherOfFour<FirstType, SecondType, ThirdType, FourthType>` — A container for one of four types.
518
519> Either types are available as of SDK 47.
520
521</APIBox>
522
523## Native Classes
524
525<APIBox header="Module">
526
527A base class for a native module.
528
529#### Properties
530
531<APIMethod
532  name="appContext"
533  comment="Provides access to the [`AppContext`](#appcontext)."
534  returnTypeName="AppContext"
535  isProperty={true}
536  isReturnTypeReference={true}
537/>
538
539#### Methods
540
541<APIMethod
542  name="sendEvent"
543  comment="Sends an event with a given name and a payload to JavaScript. See [`Sending events`](#sending-events)"
544  returnTypeName="void"
545  parameters={[
546    {
547      name: 'eventName',
548      comment: 'The name of the JavaScript event',
549      typeName: 'string',
550    },
551    {
552      name: 'payload',
553      comment: 'The event payload',
554      typeName: 'Android: Map<String, Any?> | Bundle\niOS: [String: Any?]',
555    },
556  ]}
557/>
558
559</APIBox>
560
561<APIBox header="AppContext">
562
563The app context is an interface to a single Expo app.
564
565#### Properties
566
567<APIMethod
568  name="constants"
569  comment="Provides access to app's constants from legacy module registry."
570  returnTypeName="Android: ConstantsInterface? iOS: EXConstantsInterface?"
571  isProperty={true}
572/>
573
574<APIMethod
575  name="permissions"
576  comment="Provides access to the permissions manager from legacy module registry."
577  returnTypeName="Android: Permissions? iOS: EXPermissionsInterface?"
578  isProperty={true}
579/>
580
581<APIMethod
582  name="imageLoader"
583  comment="Provides access to the image loader from the legacy module registry."
584  returnTypeName="Android: ImageLoaderInterface? iOS: EXImageLoaderInterface?"
585  isProperty={true}
586/>
587
588<APIMethod
589  name="barcodeScanner"
590  comment="Provides access to the bar code scanner manager from the legacy module registry."
591  returnTypeName="ImageLoaderInterface?"
592  isProperty={true}
593  platforms={['Android']}
594/>
595
596<APIMethod
597  name="camera"
598  comment="Provides access to the camera view manager from the legacy module registry."
599  returnTypeName="CameraViewInterface?"
600  isProperty={true}
601  platforms={['Android']}
602/>
603
604<APIMethod
605  name="font"
606  comment="Provides access to the font manager from the legacy module registry."
607  returnTypeName="FontManagerInterface?"
608  isProperty={true}
609  platforms={['Android']}
610/>
611
612<APIMethod
613  name="sensor"
614  comment="Provides access to the sensor manager from the legacy module registry."
615  returnTypeName="SensorServiceInterface?"
616  isProperty={true}
617  platforms={['Android']}
618/>
619
620<APIMethod
621  name="taskManager"
622  comment="Provides access to the task manager from the legacy module registry."
623  returnTypeName="TaskManagerInterface?"
624  isProperty={true}
625  platforms={['Android']}
626/>
627
628<APIMethod
629  name="activityProvider"
630  comment="Provides access to the activity provider from the legacy module registry."
631  returnTypeName="ActivityProvider?"
632  isProperty={true}
633  platforms={['Android']}
634/>
635
636<APIMethod
637  name="reactContext"
638  comment="Provides access to the react application context."
639  returnTypeName="Context?"
640  isProperty={true}
641  platforms={['Android']}
642/>
643
644<APIMethod
645  name="hasActiveReactInstance"
646  comment="Checks if there is an not-null, alive react native instance."
647  returnTypeName="Boolean"
648  isProperty={true}
649  platforms={['Android']}
650/>
651
652<APIMethod
653  name="utilities"
654  comment="Provides access to the utilities from legacy module registry."
655  returnTypeName="EXUtilitiesInterface?"
656  isProperty={true}
657  platforms={['iOS']}
658/>
659
660</APIBox>
661
662<APIBox header="ExpoView">
663
664A base class that should be used by all exported views.
665
666On iOS, `ExpoView` extends the `RCTView` which handles some styles (e.g. borders) and accessibility.
667
668#### Properties
669
670<APIMethod
671  name="appContext"
672  comment="Provides access to the [`AppContext`](#appcontext)."
673  returnTypeName="AppContext"
674  isProperty={true}
675  isReturnTypeReference={true}
676/>
677
678#### Extending `ExpoView`
679
680To 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`.
681
682<CodeBlocksTable>
683
684```swift
685class LinearGradientView: ExpoView {}
686
687public class LinearGradientModule: Module {
688  public func definition() -> ModuleDefinition {
689    View(LinearGradientView.self) {
690      // ...
691    }
692  }
693}
694```
695
696```kotlin
697class LinearGradientView(
698  context: Context,
699  appContext: AppContext,
700) : ExpoView(context, appContext)
701
702class LinearGradientModule : Module() {
703  override fun definition() = ModuleDefinition {
704    View(LinearGradientView::class) {
705      // ...
706    }
707  }
708}
709```
710
711</CodeBlocksTable>
712
713</APIBox>
714
715## Guides
716
717<APIBox header="Sending events">
718
719While 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.
720
721To 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:
722
723<CodeBlocksTable>
724
725```swift
726let CLIPBOARD_CHANGED_EVENT_NAME = "onClipboardChanged"
727
728public class ClipboardModule: Module {
729  public func definition() -> ModuleDefinition {
730    Events(CLIPBOARD_CHANGED_EVENT_NAME)
731
732    OnStartObserving {
733      NotificationCenter.default.addObserver(
734        self,
735        selector: #selector(self.clipboardChangedListener),
736        name: UIPasteboard.changedNotification,
737        object: nil
738      )
739    }
740
741    OnStopObserving {
742      NotificationCenter.default.removeObserver(
743        self,
744        name: UIPasteboard.changedNotification,
745        object: nil
746      )
747    }
748  }
749
750  @objc
751  private func clipboardChangedListener() {
752    sendEvent(CLIPBOARD_CHANGED_EVENT_NAME, [
753      "contentTypes": availableContentTypes()
754    ])
755  }
756}
757```
758
759```kotlin
760const val CLIPBOARD_CHANGED_EVENT_NAME = "onClipboardChanged"
761
762class ClipboardModule : Module() {
763  override fun definition() = ModuleDefinition {
764    Events(CLIPBOARD_CHANGED_EVENT_NAME)
765
766    OnStartObserving {
767      clipboardManager?.addPrimaryClipChangedListener(listener)
768    }
769
770    OnStopObserving {
771      clipboardManager?.removePrimaryClipChangedListener(listener)
772    }
773  }
774
775  private val clipboardManager: ClipboardManager?
776    get() = appContext.reactContext?.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager
777
778  private val listener = ClipboardManager.OnPrimaryClipChangedListener {
779    clipboardManager?.primaryClipDescription?.let { clip ->
780      [email protected](
781        CLIPBOARD_CHANGED_EVENT_NAME,
782        bundleOf(
783          "contentTypes" to availableContentTypes(clip)
784        )
785      )
786    }
787  }
788}
789```
790
791</CodeBlocksTable>
792
793To subscribe to these events in JavaScript/TypeScript, you need to wrap the native module with `EventEmitter` class as shown:
794
795```ts TypeScript
796import { requireNativeModule, EventEmitter, Subscription } from 'expo-modules-core';
797
798const ClipboardModule = requireNativeModule('Clipboard');
799const emitter = new EventEmitter(ClipboardModule);
800
801export function addClipboardListener(listener: (event) => void): Subscription {
802  return emitter.addListener('onClipboardChanged', listener);
803}
804```
805
806</APIBox>
807
808<APIBox header="View callbacks">
809
810Some 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.
811
812To 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.
813
814> **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>}`.
815
816<CodeBlocksTable>
817
818```swift
819class CameraViewModule: Module {
820  public func definition() -> ModuleDefinition {
821    View(CamerView.self) {
822      Events(
823        "onCameraReady"
824      )
825
826      // ...
827    }
828  }
829}
830
831class CameraView: ExpoView {
832  let onCameraReady = EventDispatcher()
833
834  func callOnCameraReady() {
835    onCameraReady([
836      "message": "Camera was mounted"
837    ]);
838  }
839}
840```
841
842```kotlin
843class CameraViewModule : Module() {
844  override fun definition() = ModuleDefinition {
845    View(ExpoCameraView::class) {
846      Events(
847        "onCameraReady"
848      )
849
850      // ...
851    }
852  }
853}
854
855class CameraView(
856  context: Context,
857  appContext: AppContext
858) : ExpoView(context, appContext) {
859  val onCameraReady by EventDispatcher()
860
861  fun callOnCameraReady() {
862    onCameraReady(mapOf(
863      "message" to "Camera was mounted"
864    ));
865  }
866}
867```
868
869</CodeBlocksTable>
870
871To subscribe to these events in JavaScript/TypeScript, you need to pass a function to the native view as shown:
872
873```ts TypeScript
874import { requireNativeViewManager } from 'expo-modules-core';
875
876const CameraView = requireNativeViewManager('CameraView');
877
878export default function MainView() {
879  const onCameraReady = event => {
880    console.log(event.nativeEvent);
881  };
882
883  return <CameraView onCameraReady={onCameraReady} />;
884}
885```
886
887Provided payload is available under the `nativeEvent` key.
888
889</APIBox>
890
891## Examples
892
893<CodeBlocksTable>
894
895```swift
896public class MyModule: Module {
897  public func definition() -> ModuleDefinition {
898    Name("MyFirstExpoModule")
899
900    Function("hello") { (name: String) in
901      return "Hello \(name)!"
902    }
903  }
904}
905```
906
907```kotlin
908class MyModule : Module() {
909  override fun definition() = ModuleDefinition {
910    Name("MyFirstExpoModule")
911
912    Function("hello") { name: String ->
913      return "Hello $name!"
914    }
915  }
916}
917```
918
919</CodeBlocksTable>
920
921For more examples from real modules, you can refer to Expo modules that already use this API on GitHub:
922
923- `expo-battery` ([Swift](https://github.com/expo/expo/tree/main/packages/expo-battery/ios))
924- `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))
925- `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))
926- `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))
927- `expo-device` ([Swift](https://github.com/expo/expo/tree/main/packages/expo-device/ios))
928- `expo-haptics` ([Swift](https://github.com/expo/expo/tree/main/packages/expo-haptics/ios))
929- `expo-image-manipulator` ([Swift](https://github.com/expo/expo/tree/main/packages/expo-image-manipulator/ios))
930- `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))
931- `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))
932- `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))
933- `expo-store-review` ([Swift](https://github.com/expo/expo/tree/main/packages/expo-store-review/ios))
934- `expo-system-ui` ([Swift](https://github.com/expo/expo/tree/main/packages/expo-system-ui/ios/ExpoSystemUI))
935- `expo-video-thumbnails` ([Swift](https://github.com/expo/expo/tree/main/packages/expo-video-thumbnails/ios))
936- `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))
937