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