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