xref: /expo/docs/pages/modules/module-api.mdx (revision 8886ca5a)
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