# Tutorials

## Tutorial: Create a native module

A tutorial on creating a native module that persists settings with Expo Modules API.

In this tutorial, you build a module that stores the user's preferred app theme: dark, light, or system. On Android, use [`SharedPreferences`](https://developer.android.com/reference/android/content/SharedPreferences), and on iOS, use [`UserDefaults`](https://developer.apple.com/documentation/foundation/userdefaults). You can implement web support with [`localStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage), but this tutorial does not cover that.
Video Tutorial: [Watch: How to create a native module with the Expo Modules API](https://www.youtube.com/watch?v=CdaQSlyGik8)
---
Step 1: 
## Initialize a new module
First, create a new module. For this tutorial, the module is named `expo-settings` or `ExpoSettings`. You can choose a different name, but adjust the instructions to match your choice.
```sh
$ npx create-expo-module expo-settings
```
> **info** Since you aren't going to actually ship this library, you can hit <kbd>return</kbd> for all the prompts to accept the default values.
Step 2: 
## Set up workspace
Clean up the default module to start with a clean slate. Delete the view module since this guide does not use it.
```sh
$ cd expo-settings
$ rm ios/ExpoSettingsView.swift
$ rm android/src/main/java/expo/modules/settings/ExpoSettingsView.kt
$ rm src/ExpoSettingsView.tsx
$ rm src/ExpoSettingsView.web.tsx src/ExpoSettingsModule.web.ts
```
Locate the following files and replace their contents with the provided minimal boilerplate:
```kotlin android/src/main/java/expo/modules/settings/ExpoSettingsModule.kt
package expo.modules.settings
import expo.modules.kotlin.modules.Module
import expo.modules.kotlin.modules.ModuleDefinition
class ExpoSettingsModule : Module() {
  override fun definition() = ModuleDefinition {
    Name("ExpoSettings")
    Function("getTheme") {
      return@Function "system"
    }
  }
}
```
```swift ios/ExpoSettingsModule.swift
import ExpoModulesCore
public class ExpoSettingsModule: Module {
  public func definition() -> ModuleDefinition {
    Name("ExpoSettings")
    Function("getTheme") { () -> String in
      "system"
    }
  }
}
```
```ts src/ExpoSettings.types.ts
export type ExpoSettingsModuleEvents = {};
```
```ts src/ExpoSettingsModule.ts
import { NativeModule, requireNativeModule } from 'expo';
import { ExpoSettingsModuleEvents } from './ExpoSettings.types';
declare class ExpoSettingsModule extends NativeModule<ExpoSettingsModuleEvents> {
  getTheme: () => string;
}
// This call loads the native module object from the JSI.
export default requireNativeModule<ExpoSettingsModule>('ExpoSettings');
```
```ts src/index.ts
import ExpoSettingsModule from './ExpoSettingsModule';
export function getTheme(): string {
  return ExpoSettingsModule.getTheme();
}
```
```tsx example/App.tsx
import * as Settings from 'expo-settings';
import { Text, View } from 'react-native';
export default function App() {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text>Theme: {Settings.getTheme()}</Text>
    </View>
  );
}
```
Step 3: 
## Run the example project
Start the TypeScript compiler to watch for changes.
```sh
$ npm run build
```
In a separate terminal window, run the example app.
```sh
$ cd example
$ npx expo run:android
$ npx expo run:ios
```
You should see the text "Theme: system" in the center of the screen when you launch the example app. The value `"system"` comes from synchronously calling the `getTheme()` function in the native module. You will change this value in the next step.
Step 4: 
## Get, set, and persist the theme preference value
### Android native module
To read the value, look for a `SharedPreferences` string under the key `"theme"`. If the key does not exist, default to `"system"`. Use the `reactContext` (a React Native [ContextWrapper](https://developer.android.com/reference/android/content/ContextWrapper)) to access the `SharedPreferences` instance with `getSharedPreferences()`.
To set the value, use the `edit()` method of `SharedPreferences` to get an `Editor` instance. Then, use `putString()` to set the value. Ensure the `setTheme` function accepts a value of type `String`.
```kotlin android/src/main/java/expo/modules/settings/ExpoSettingsModule.kt
package expo.modules.settings
import android.content.Context
import android.content.SharedPreferences
import expo.modules.kotlin.modules.Module
import expo.modules.kotlin.modules.ModuleDefinition
class ExpoSettingsModule : Module() {
  override fun definition() = ModuleDefinition {
    Name("ExpoSettings")
    Function("setTheme") { theme: String ->
      getPreferences().edit().putString("theme", theme).commit()
    }
    Function("getTheme") {
      return@Function getPreferences().getString("theme", "system")
    }
  }
  private val context
  get() = requireNotNull(appContext.reactContext)
  private fun getPreferences(): SharedPreferences {
    return context.getSharedPreferences(context.packageName + ".settings", Context.MODE_PRIVATE)
  }
}
```
### iOS native module
To read the value on iOS, look for a `UserDefaults` string under the key `"theme"`. If the key does not exist, default to `"system"`.
To set the value, use the `set(_:forKey:)` method of `UserDefaults`. Ensure the `setTheme` function accepts a value of type `String`.
```swift ios/ExpoSettingsModule.swift
import ExpoModulesCore
public class ExpoSettingsModule: Module {
  public func definition() -> ModuleDefinition {
    Name("ExpoSettings")
    Function("setTheme") { (theme: String) -> Void in
      UserDefaults.standard.set(theme, forKey:"theme")
    }
    Function("getTheme") { () -> String in
      UserDefaults.standard.string(forKey: "theme") ?? "system"
    }
  }
}
```
### TypeScript module
Update the **ExpoSettingsModule.ts** to add a TypeScript interface for the `ExpoSettingsModule` native module to update the theme.
```ts src/ExpoSettingsModule.ts
import { NativeModule, requireNativeModule } from 'expo';
import { ExpoSettingsModuleEvents } from './ExpoSettings.types';
declare class ExpoSettingsModule extends NativeModule<ExpoSettingsModuleEvents> {
  setTheme: (theme: string) => void;
  getTheme: () => string;
}
// This call loads the native module object from the JSI.
export default requireNativeModule<ExpoSettingsModule>('ExpoSettings');
```
Now, call your native modules from TypeScript.
```ts src/index.ts
import ExpoSettingsModule from './ExpoSettingsModule';
export function getTheme(): string {
  return ExpoSettingsModule.getTheme();
}
export function setTheme(theme: string): void {
  return ExpoSettingsModule.setTheme(theme);
}
```
### Example app
You can now use the Settings API in your example app.
```tsx example/App.tsx
import * as Settings from 'expo-settings';
import { Button, Text, View } from 'react-native';
export default function App() {
  const theme = Settings.getTheme();
  // Toggle between dark and light theme
  const nextTheme = theme === 'dark' ? 'light' : 'dark';
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text>Theme: {Settings.getTheme()}</Text>
      <Button title={`Set theme to ${nextTheme}`} onPress={() => Settings.setTheme(nextTheme)} />
    </View>
  );
}
```
When you rebuild and run the app, the "system" theme is still set. Pressing the button does nothing, but when you reload the app, the theme changes. This happens because the app does not fetch the new theme value or re-render. You will fix this in the next step.
Step 5: 
## Emit change events for the theme value
Ensure developers using your API can react to theme value changes by emitting a change event whenever the value updates. Use the [Events](/modules/module-api/#events) definition component to describe the events your module emits, `sendEvent` to emit the event from native code, and the [EventEmitter](/modules/module-api/#sending-events) API to subscribe to events in JavaScript. The event payload is `{ theme: string }`.
### Android native module
Events payloads are represented as [`Bundle`](https://developer.android.com/reference/android/os/Bundle.html) instances on Android, which you can create using the [`bundleOf`](<https://developer.android.com/reference/kotlin/androidx/core/os/package-summary#bundleOf(kotlin.Array)>) function.
```kotlin android/src/main/java/expo/modules/settings/ExpoSettingsModule.kt
package expo.modules.settings
import android.content.Context
import android.content.SharedPreferences
import androidx.core.os.bundleOf
import expo.modules.kotlin.modules.Module
import expo.modules.kotlin.modules.ModuleDefinition
class ExpoSettingsModule : Module() {
  override fun definition() = ModuleDefinition {
    Name("ExpoSettings")
    Events("onChangeTheme")
    Function("setTheme") { theme: String ->
      getPreferences().edit().putString("theme", theme).commit()
      this@ExpoSettingsModule.sendEvent("onChangeTheme", bundleOf("theme" to theme))
    }
    Function("getTheme") {
      return@Function getPreferences().getString("theme", "system")
    }
  }
  private val context
  get() = requireNotNull(appContext.reactContext)
  private fun getPreferences(): SharedPreferences {
    return context.getSharedPreferences(context.packageName + ".settings", Context.MODE_PRIVATE)
  }
}
```
### iOS native module
```swift ios/ExpoSettingsModule.swift
import ExpoModulesCore
public class ExpoSettingsModule: Module {
  public func definition() -> ModuleDefinition {
    Name("ExpoSettings")
    Events("onChangeTheme")
    Function("setTheme") { (theme: String) -> Void in
      UserDefaults.standard.set(theme, forKey:"theme")
      sendEvent("onChangeTheme", [
        "theme": theme
      ])
    }
    Function("getTheme") { () -> String in
      UserDefaults.standard.string(forKey: "theme") ?? "system"
    }
  }
}
```
### TypeScript module
```ts src/ExpoSettings.types.ts
export type ThemeChangeEvent = {
  theme: string;
};
export type ExpoSettingsModuleEvents = {
  onChangeTheme: (params: ThemeChangeEvent) => void;
};
```
```ts src/index.ts
import { EventSubscription } from 'expo-modules-core';
import ExpoSettingsModule from './ExpoSettingsModule';
import { ThemeChangeEvent } from './ExpoSettings.types';
export function addThemeListener(listener: (event: ThemeChangeEvent) => void): EventSubscription {
  return ExpoSettingsModule.addListener('onChangeTheme', listener);
}
export function getTheme(): string {
  return ExpoSettingsModule.getTheme();
}
export function setTheme(theme: string): void {
  return ExpoSettingsModule.setTheme(theme);
}
```
### Example app
```tsx example/App.tsx
import * as Settings from 'expo-settings';
import { useEffect, useState } from 'react';
import { Button, Text, View } from 'react-native';
export default function App() {
  const [theme, setTheme] = useState<string>(Settings.getTheme());
  useEffect(() => {
    const subscription = Settings.addThemeListener(({ theme: newTheme }) => {
      setTheme(newTheme);
    });
    return () => subscription.remove();
  }, [setTheme]);
  // Toggle between dark and light theme
  const nextTheme = theme === 'dark' ? 'light' : 'dark';
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text>Theme: {Settings.getTheme()}</Text>
      <Button title={`Set theme to ${nextTheme}`} onPress={() => Settings.setTheme(nextTheme)} />
    </View>
  );
}
```
Step 6: 
## Improve type safety with Enums
It's easy to make mistakes when using the `Settings.setTheme()` API in its current form, as it allows any string value. Improve the type safety of this API by using an enum to restrict the possible values to `system`, `light`, and `dark`.
### Android native module
```kotlin android/src/main/java/expo/modules/settings/ExpoSettingsModule.kt
package expo.modules.settings
import android.content.Context
import android.content.SharedPreferences
import androidx.core.os.bundleOf
import expo.modules.kotlin.modules.Module
import expo.modules.kotlin.modules.ModuleDefinition
import expo.modules.kotlin.types.Enumerable
class ExpoSettingsModule : Module() {
  override fun definition() = ModuleDefinition {
    Name("ExpoSettings")
    Events("onChangeTheme")
    Function("setTheme") { theme: Theme ->
      getPreferences().edit().putString("theme", theme.value).commit()
      this@ExpoSettingsModule.sendEvent("onChangeTheme", bundleOf("theme" to theme.value))
    }
    Function("getTheme") {
      return@Function getPreferences().getString("theme", Theme.SYSTEM.value)
    }
  }
  private val context
  get() = requireNotNull(appContext.reactContext)
  private fun getPreferences(): SharedPreferences {
    return context.getSharedPreferences(context.packageName + ".settings", Context.MODE_PRIVATE)
  }
}
enum class Theme(val value: String) : Enumerable {
  LIGHT("light"),
  DARK("dark"),
  SYSTEM("system")
}
```
### iOS native module
```swift ios/ExpoSettingsModule.swift
import ExpoModulesCore
public class ExpoSettingsModule: Module {
  public func definition() -> ModuleDefinition {
    Name("ExpoSettings")
    Events("onChangeTheme")
    Function("setTheme") { (theme: Theme) -> Void in
      UserDefaults.standard.set(theme.rawValue, forKey:"theme")
      sendEvent("onChangeTheme", [
        "theme": theme.rawValue
      ])
    }
    Function("getTheme") { () -> String in
      UserDefaults.standard.string(forKey: "theme") ?? Theme.system.rawValue
    }
  }
  enum Theme: String, Enumerable {
    case light
    case dark
    case system
  }
}
```
### TypeScript module
```ts src/ExpoSettings.types.ts
export type Theme = 'light' | 'dark' | 'system';
export type ThemeChangeEvent = {
  theme: Theme;
};
export type ExpoSettingsModuleEvents = {
  onChangeTheme: (params: ThemeChangeEvent) => void;
};
```
```ts src/ExpoSettingsModule.ts
import { NativeModule, requireNativeModule } from 'expo';
import { ExpoSettingsModuleEvents, Theme } from './ExpoSettings.types';
declare class ExpoSettingsModule extends NativeModule<ExpoSettingsModuleEvents> {
  setTheme: (theme: Theme) => void;
  getTheme: () => Theme;
}
// This call loads the native module object from the JSI.
export default requireNativeModule<ExpoSettingsModule>('ExpoSettings');
```
```ts src/index.ts
import { EventSubscription } from 'expo-modules-core';
import ExpoSettingsModule from './ExpoSettingsModule';
import { Theme, ThemeChangeEvent } from './ExpoSettings.types';
export function addThemeListener(listener: (event: ThemeChangeEvent) => void): EventSubscription {
  return ExpoSettingsModule.addListener('onChangeTheme', listener);
}
export function getTheme(): Theme {
  return ExpoSettingsModule.getTheme();
}
export function setTheme(theme: Theme): void {
  return ExpoSettingsModule.setTheme(theme);
}
```
### Example app
If you change `Settings.setTheme(nextTheme)` to `Settings.setTheme("not-a-real-theme")`, TypeScript will raise an error. If you ignore the error and press the button, you will see the following runtime error:
```text
 ERROR  Error: FunctionCallException: Calling the 'setTheme' function has failed (at ExpoModulesCore/SyncFunctionComponent.swift:76)
â Caused by: ArgumentCastException: Argument at index '0' couldn't be cast to type Enum<Theme> (at ExpoModulesCore/JavaScriptUtils.swift:41)
â Caused by: EnumNoSuchValueException: 'not-a-real-theme' is not present in Theme enum, it must be one of: 'light', 'dark', 'system' (at ExpoModulesCore/Enumerable.swift:37)
```
The last line of the error message shows that `not-a-real-theme` is not a valid value for the `Theme` enum. The only valid values are `light`, `dark`, and `system`.
Congratulations! You have created your first Expo Module for Android and iOS.
## Next steps


## Tutorial: Create a native view

A tutorial on creating a native view that renders a WebView with Expo Modules API.

In this tutorial, you'll build an example module with a native view that renders a WebView. For Android, you'll use the [`WebView`](https://developer.android.com/reference/android/webkit/WebView) component, and for iOS, [`WKWebView`](https://developer.apple.com/documentation/webkit/wkwebview). Web support can be implemented using an [`iframe`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe) and is left as an exercise for you.
Step 1: 
## Initialize a new module
Create a new module by running the following command and name the example module `expo-web-view`:
```sh
$ npx create-expo-module expo-web-view
```
> **info** Since this is an example library and won't be published, press <kbd>return</kbd> for all prompts to accept the default values.
Step 2: 
## Set up workspace
Clean up the default module to start with a clean slate by deleting the following files:
```sh
$ cd expo-web-view
$ rm src/ExpoWebView.types.ts src/ExpoWebViewModule.ts
$ rm src/ExpoWebView.web.tsx src/ExpoWebViewModule.web.ts
```
Locate the following files and replace them with the provided minimal boilerplate:
```kotlin android/src/main/java/expo/modules/webview/ExpoWebViewModule.kt
package expo.modules.webview
import expo.modules.kotlin.modules.Module
import expo.modules.kotlin.modules.ModuleDefinition
class ExpoWebViewModule : Module() {
  override fun definition() = ModuleDefinition {
    Name("ExpoWebView")
    View(ExpoWebView::class) {}
  }
}
```
```swift ios/ExpoWebViewModule.swift
import ExpoModulesCore
public class ExpoWebViewModule: Module {
  public func definition() -> ModuleDefinition {
    Name("ExpoWebView")
    View(ExpoWebView.self) {}
  }
}
```
```tsx src/ExpoWebView.tsx
import { ViewProps } from 'react-native';
import { requireNativeViewManager } from 'expo-modules-core';
import * as React from 'react';
export type Props = ViewProps;
const NativeView: React.ComponentType<Props> = requireNativeViewManager('ExpoWebView');
export default function ExpoWebView(props: Props) {
  return <NativeView {...props} />;
}
```
```tsx src/index.ts
export { default as WebView, Props as WebViewProps } from './ExpoWebView';
```
```tsx example/App.tsx
import { WebView } from 'expo-web-view';
export default function App() {
  return <WebView style={{ flex: 1, backgroundColor: 'purple' }} />;
}
```
Step 3: 
## Run the example project
To ensure everything is working, start the TypeScript compiler to watch for changes and rebuild the module's JavaScript:
```sh
$ npm run build
```
```sh
$ cd example
$ npx expo run:android
$ npx expo run:ios
```
You should now see a blank purple screen. While it's not very exciting, it's a good start. Next, turn it into a WebView.
Step 4: 
## Add the system WebView as a subview
Add the system `WebView` with a hardcoded URL as a subview of `ExpoWebView`. The `ExpoWebView` class extends `ExpoView`, which extends `RCTView` from React Native, and eventually extends `View` on Android and `UIView` on iOS.
Ensure that the `WebView` subview uses the same layout as `ExpoWebView`, whose layout is calculated by React Native's layout engine.
### Android view
On Android, use `LayoutParams` to set the WebView's layout to match the `ExpoWebView` layout. You can do this when you instantiate the WebView.
```kotlin android/src/main/java/expo/modules/webview/ExpoWebView.kt
package expo.modules.webview
import android.content.Context
import android.webkit.WebView
import android.webkit.WebViewClient
import expo.modules.kotlin.AppContext
import expo.modules.kotlin.views.ExpoView
class ExpoWebView(context: Context, appContext: AppContext) : ExpoView(context, appContext) {
  internal val webView = WebView(context).also {
    it.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
    it.webViewClient = object : WebViewClient() {}
    addView(it)
    it.loadUrl("https://docs.expo.dev/modules/")
  }
}
```
### iOS view
On iOS, set `clipsToBounds` to `true` and ensure the WebView's `frame` matches the bounds of `ExpoWebView` in `layoutSubviews`. The `init` method is called when the view is created, and `layoutSubviews` is called when the layout changes.
```swift ios/ExpoWebView.swift
import ExpoModulesCore
import WebKit
class ExpoWebView: ExpoView {
  let webView = WKWebView()
  required init(appContext: AppContext? = nil) {
    super.init(appContext: appContext)
    clipsToBounds = true
    addSubview(webView)
    let url =  URL(string:"https://docs.expo.dev/modules/")!
    let urlRequest = URLRequest(url:url)
    webView.load(urlRequest)
  }
  override func layoutSubviews() {
    webView.frame = bounds
  }
}
```
### Example app
No changes are required. Rebuild and run the app using the following commands:
```sh
$ npx expo prebuild --clean
$ npx expo run:android
$ npx expo run:ios
```
After that, you'll see the [Expo Modules API overview page](/modules/overview) rendered. If the changes aren't reflected, try reinstalling the app.
Step 5: 
## Add a prop to set the URL
To set a prop on the view, define the prop name and setter inside `ExpoWebViewModule`. In this case, you can access the `webView` property directly for convenience. However, in real-world scenarios, keep the logic inside the `ExpoWebView` class to minimize how much `ExpoWebViewModule` knows about its internals.
Use the [Prop definition component](/modules/module-api/#prop) to define the prop. In the prop setter block, you can access both the view and the prop. Specify that the URL is of type `URL` &mdash; the Expo modules API will convert strings to the native `URL` type.
### Android module
```kotlin android/src/main/java/expo/modules/webview/ExpoWebViewModule.kt
package expo.modules.webview
import expo.modules.kotlin.modules.Module
import expo.modules.kotlin.modules.ModuleDefinition
import java.net.URL
class ExpoWebViewModule : Module() {
  override fun definition() = ModuleDefinition {
    Name("ExpoWebView")
    View(ExpoWebView::class) {
      Prop("url") { view: ExpoWebView, url: URL? ->
        view.webView.loadUrl(url.toString())
      }
    }
  }
}
```
### iOS module
```swift ios/ExpoWebViewModule.swift
import ExpoModulesCore
public class ExpoWebViewModule: Module {
  public func definition() -> ModuleDefinition {
    Name("ExpoWebView")
    View(ExpoWebView.self) {
      Prop("url") { (view, url: URL) in
        if view.webView.url != url {
          let urlRequest = URLRequest(url: url)
          view.webView.load(urlRequest)
        }
      }
    }
  }
}
```
### TypeScript module
Next, add the `url` prop to the `Props` type.
```tsx src/ExpoWebView.tsx
import { ViewProps } from 'react-native';
import { requireNativeViewManager } from 'expo-modules-core';
import * as React from 'react';
export type Props = {
  url?: string;
} & ViewProps;
const NativeView: React.ComponentType<Props> = requireNativeViewManager('ExpoWebView');
export default function ExpoWebView(props: Props) {
  return <NativeView {...props} />;
}
```
### Example app
Finally, pass a `URL` to your `WebView` component in the example app.
```tsx example/App.tsx
import { WebView } from 'expo-web-view';
export default function App() {
  return <WebView style={{ flex: 1 }} url="https://expo.dev" />;
}
```
Rebuild the example app:
```sh
$ npx expo prebuild --clean
$ npx expo run:android
$ npx expo run:ios
```
After that, you'll see the [Expo homepage](https://expo.dev) in the WebView.
Step 6: 
## Add an event to notify when the page has loaded
[View callbacks](/modules/module-api/#view-callbacks) allow developers to listen for events on components. They are typically registered through props on the component, for example: `<Image onLoad={...} />`. Use the [Events definition component](/modules/module-api/#events) to define an event for your WebView. Call it `onLoad`.
### Android view and module
On Android, override the `onPageFinished` function. Then, call the `onLoad` event handler that you defined in the module.
```kotlin android/src/main/java/expo/modules/webview/ExpoWebView.kt
package expo.modules.webview
import android.content.Context
import android.webkit.WebView
import android.webkit.WebViewClient
import expo.modules.kotlin.AppContext
import expo.modules.kotlin.viewevent.EventDispatcher
import expo.modules.kotlin.views.ExpoView
class ExpoWebView(context: Context, appContext: AppContext) : ExpoView(context, appContext) {
  private val onLoad by EventDispatcher()
  internal val webView = WebView(context).also {
    it.layoutParams = LayoutParams(
      LayoutParams.MATCH_PARENT,
      LayoutParams.MATCH_PARENT
    )
    it.webViewClient = object : WebViewClient() {
      override fun onPageFinished(view: WebView, url: String) {
        onLoad(mapOf("url" to url))
      }
    }
    addView(it)
  }
}
```
Indicate in `ExpoWebViewModule` that the `View` has an `onLoad` event.
```kotlin android/src/main/java/expo/modules/webview/ExpoWebViewModule.kt
package expo.modules.webview
import expo.modules.kotlin.modules.Module
import expo.modules.kotlin.modules.ModuleDefinition
import java.net.URL
class ExpoWebViewModule : Module() {
  override fun definition() = ModuleDefinition {
    Name("ExpoWebView")
    View(ExpoWebView::class) {
      Events("onLoad")
      Prop("url") { view: ExpoWebView, url: URL? ->
        view.webView.loadUrl(url.toString())
      }
    }
  }
}
```
### iOS view and module
On iOS, implement `webView(_:didFinish:)` and make `ExpoWebView` extend `WKNavigationDelegate`. Then, call `onLoad` from that delegate method.
```swift ios/ExpoWebView.swift
import ExpoModulesCore
import WebKit
class ExpoWebView: ExpoView, WKNavigationDelegate {
  let webView = WKWebView()
  let onLoad = EventDispatcher()
  required init(appContext: AppContext? = nil) {
    super.init(appContext: appContext)
    clipsToBounds = true
    webView.navigationDelegate = self
    addSubview(webView)
  }
  override func layoutSubviews() {
    webView.frame = bounds
  }
  func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
    if let url = webView.url {
      onLoad([
        "url": url.absoluteString
      ])
    }
  }
}
```
Indicate in `ExpoWebViewModule` that the `View` has an `onLoad` event.
```swift ios/ExpoWebViewModule.swift
import ExpoModulesCore
public class ExpoWebViewModule: Module {
  public func definition() -> ModuleDefinition {
    Name("ExpoWebView")
    View(ExpoWebView.self) {
      Events("onLoad")
      Prop("url") { (view, url: URL) in
        if view.webView.url != url {
          let urlRequest = URLRequest(url: url)
          view.webView.load(urlRequest)
        }
      }
    }
  }
}
```
### TypeScript module
Event payloads are included within the `nativeEvent` property of the event. To access the `url` from the `onLoad` event, read `event.nativeEvent.url`.
```tsx src/ExpoWebView.tsx
import { ViewProps } from 'react-native';
import { requireNativeViewManager } from 'expo-modules-core';
import * as React from 'react';
export type OnLoadEvent = {
  url: string;
};
export type Props = {
  url?: string;
  onLoad?: (event: { nativeEvent: OnLoadEvent }) => void;
} & ViewProps;
const NativeView: React.ComponentType<Props> = requireNativeViewManager('ExpoWebView');
export default function ExpoWebView(props: Props) {
  return <NativeView {...props} />;
}
```
### Example app
Update the example app to show an alert when the page has loaded. Copy the following code, then rebuild and run your app, and you'll see the alert!
```tsx example/App.tsx
import { WebView } from 'expo-web-view';
export default function App() {
  return (
    <WebView
      style={{ flex: 1 }}
      url="https://expo.dev"
      onLoad={event => alert(`loaded ${event.nativeEvent.url}`)}
    />
  );
}
```
Step 7: 
## Bonus: Build a web browser UI around it
Now that you have a WebView, build a web browser UI around it. Try rebuilding a browser UI, and feel free to add new native capabilities as needed (for example, support for back or reload buttons). If you need inspiration, see the example below.
Note: example/App.tsx
---
```tsx App.tsx
import { useState } from 'react';
import { ActivityIndicator, Platform, Text, TextInput, View } from 'react-native';
import { WebView } from 'expo-web-view';
export default function App() {
  const [inputUrl, setInputUrl] = useState('https://docs.expo.dev/modules/');
  const [url, setUrl] = useState(inputUrl);
  const [isLoading, setIsLoading] = useState(true);
  return (
    <View style={{ flex: 1, paddingTop: Platform.OS === 'ios' ? 80 : 30 }}>
      <TextInput
        value={inputUrl}
        onChangeText={setInputUrl}
        returnKeyType="go"
        autoCapitalize="none"
        onSubmitEditing={() => {
          if (inputUrl !== url) {
            setUrl(inputUrl);
            setIsLoading(true);
          }
        }}
        keyboardType="url"
        style={{
          color: '#fff',
          backgroundColor: '#000',
          borderRadius: 10,
          marginHorizontal: 10,
          paddingHorizontal: 20,
          height: 60,
        }}
      />
      <WebView
        url={url.startsWith('https://') || url.startsWith('http://') ? url : `https://${url}`}
        onLoad={() => setIsLoading(false)}
        style={{ flex: 1, marginTop: 20 }}
      />
      <LoadingView isLoading={isLoading} />
    </View>
  );
}
function LoadingView({ isLoading }: { isLoading: boolean }) {
  if (!isLoading) {
    return null;
  }
  return (
    <View
      style={{
        position: 'absolute',
        bottom: 0,
        left: 0,
        right: 0,
        height: 80,
        backgroundColor: 'rgba(0,0,0,0.5)',
        paddingBottom: 10,
        justifyContent: 'center',
        alignItems: 'center',
        flexDirection: 'row',
      }}>
      <ActivityIndicator animating={isLoading} color="#fff" style={{ marginRight: 10 }} />
      <Text style={{ color: '#fff' }}>Loading...</Text>
    </View>
  );
}
```
---
Congratulations! You've created your first Expo module with a native view for Android and iOS.
## Next steps


## Tutorial: Create a module with a config plugin

A tutorial on creating a native module with a config plugin using Expo Modules API.

[Config plugins](/config-plugins/introduction/) let you customize native Android and iOS projects generated with `npx expo prebuild` in [Continuous Native Generation (CNG)](/workflow/continuous-native-generation/) projects. You can use them to add properties to native config files, copy assets to native projects, or apply advanced configurations, such as adding an [app extension target](/build-reference/app-extensions/).
As an app developer, config plugins help you apply customizations not exposed in the default [app config](/workflow/configuration). As a library author, they enable you to configure native projects automatically for developers using your library.
This tutorial explains how to create a new config plugin from scratch and read custom values that your plugin injects into **AndroidManifest.xml** and **Info.plist** from an Expo module.
Step 1: 
## Initialize a module
Start by initializing a new Expo module project with `create-expo-module`. This sets up scaffolding for Android, iOS, and TypeScript and includes an example project to test the module within an app. Run the following command to get started:
```sh
$ npx create-expo-module expo-native-configuration
```
This guide uses the name `expo-native-configuration`/`ExpoNativeConfiguration` for the module project. However, you can choose any name you prefer.
Step 2: 
## Set up workspace
In this example, you don't need the view module included by `create-expo-module`. Clean up the default module with the following command:
```sh
$ cd expo-native-configuration
$ rm android/src/main/java/expo/modules/nativeconfiguration/ExpoNativeConfigurationView.kt
$ rm ios/ExpoNativeConfigurationView.swift
$ rm src/ExpoNativeConfigurationView.tsx src/ExpoNativeConfiguration.types.ts
$ rm src/ExpoNativeConfigurationView.web.tsx src/ExpoNativeConfigurationModule.web.ts
```
Locate the following files and replace them with the provided minimal boilerplate:
- **android/src/main/java/expo/modules/nativeconfiguration/ExpoNativeConfigurationModule.kt**
- **ios/ExpoNativeConfigurationModule.swift**
- **src/ExpoNativeConfigurationModule.ts**
- **src/index.ts**
- **example/App.tsx**
- **package.json**
```kotlin android/src/main/java/expo/modules/nativeconfiguration/ExpoNativeConfigurationModule.kt
package expo.modules.nativeconfiguration
import expo.modules.kotlin.modules.Module
import expo.modules.kotlin.modules.ModuleDefinition
class ExpoNativeConfigurationModule : Module() {
  override fun definition() = ModuleDefinition {
    Name("ExpoNativeConfiguration")
    Function("getApiKey") {
      return@Function "api-key"
    }
  }
}
```
```swift ios/ExpoNativeConfigurationModule.swift
import ExpoModulesCore
public class ExpoNativeConfigurationModule: Module {
  public func definition() -> ModuleDefinition {
    Name("ExpoNativeConfiguration")
    Function("getApiKey") { () -> String in
      "api-key"
    }
  }
}
```
```ts src/ExpoNativeConfigurationModule.ts
import { NativeModule, requireNativeModule } from 'expo';
declare class ExpoNativeConfigurationModule extends NativeModule {
  getApiKey(): string;
}
// This call loads the native module object from the JSI.
export default requireNativeModule<ExpoNativeConfigurationModule>('ExpoNativeConfiguration');
```
```ts src/index.ts
import ExpoNativeConfigurationModule from './ExpoNativeConfigurationModule';
export function getApiKey(): string {
  return ExpoNativeConfigurationModule.getApiKey();
}
```
```tsx example/App.tsx
import * as ExpoNativeConfiguration from 'expo-native-configuration';
import { Text, View } from 'react-native';
export default function App() {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text>API key: {ExpoNativeConfiguration.getApiKey()}</Text>
    </View>
  );
}
```
```json package.json
{
  "dependencies": {
    "expo-native-configuration": "file:.."
  }
}
```
Step 3: 
## Run the example project
In the root of your project, run the TypeScript compiler to watch for changes and rebuild the module's JavaScript:
```sh
$ npm run build
```
In another terminal window, compile and run the example app:
```sh
$ cd example
$ rm -rf node_modules && npm install
$ npx expo run:android
$ npx expo run:ios
```
You should see a screen with the text "API key: api-key".
Step 4: 
## Create a new config plugin
[Plugins](/config-plugins/introduction/#plugin-function) are synchronous functions that accept an `ExpoConfig` and return a modified `ExpoConfig`. By convention, these functions are prefixed with the word `with`. Name your plugin `withMyApiKey` or use a different name, as long as it follows this convention.
Here is an example of a basic config plugin function:
```js
const withMyApiKey = config => {
  return config;
};
```
You can also use `mods`, which are async functions that modify files in native projects, such as source code or configuration files (plist, xml). The `mods` object is different from the rest of the app config because it doesn't serialize after the initial reading. This allows you to perform actions _during_ code generation.
When writing config plugins, follow these considerations:
- Plugins must be synchronous, and their return value must be serializable, except for any `mods` that are added.
- `plugins` are invoked whenever the `getConfig` method from `expo/config` reads the configuration. In contrast, `mods` are invoked only during the "syncing" phase of `npx expo prebuild`.
> Although optional, use [`expo-module-scripts`](https://www.npmjs.com/package/expo-module-scripts) to simplify plugin development. It provides a recommended default configuration for TypeScript and Jest. For more information, see the [config plugins guide](https://github.com/expo/expo/tree/main/packages/expo-module-scripts#-config-plugin).
Start creating your plugin with this minimal boilerplate. Create a **plugin** directory for writing the plugin in TypeScript and add an **app.plugin.js** file in the project root, which will be the plugin's entry point.
### Create a plugin/tsconfig.json file
```json plugin/tsconfig.json
{
  "extends": "expo-module-scripts/tsconfig.plugin",
  "compilerOptions": {
    "outDir": "build",
    "rootDir": "src"
  },
  "include": ["./src"],
  "exclude": ["**/__mocks__/*", "**/__tests__/*"]
}
```
### Create a plugin/src/index.ts file for your plugin
```ts plugin/src/index.ts
import { ConfigPlugin } from 'expo/config-plugins';
const withMyApiKey: ConfigPlugin = config => {
  console.log('my custom plugin');
  return config;
};
export default withMyApiKey;
```
### Create an app.plugin.js file in the root directory
```js app.plugin.js
// This file configures the entry file for your plugin.
module.exports = require('./plugin/build');
```
At the root of your project, run `npm run build plugin` to start the TypeScript compiler in watch mode. Next, configure your example project to use your plugin by adding the following line to the **example/app.json** file:
```json example/app.json
{
  "expo": {
    "plugins": ["../app.plugin.js"]
  }
}
```
When you run the `npx expo prebuild` command inside your **example** directory, the terminal logs "my custom plugin" through a console statement.
```sh
$ cd example
$ npx expo prebuild --clean
```
To inject your custom API keys into **AndroidManifest.xml** and **Info.plist**, use helper [`mods` provided by `expo/config-plugins`](/config-plugins/plugins-and-mods/#what-are-mods). These make it easy to modify native files. For this example, use `withAndroidManifest` and `withInfoPlist`.
As the name suggests, `withAndroidManifest` allows you to read and modify the **AndroidManifest.xml** file. Use `AndroidConfig` helpers to add a metadata item to the main application, as shown below:
```ts
const withMyApiKey: ConfigPlugin<{ apiKey: string }> = (config, { apiKey }) => {
  config = withAndroidManifest(config, config => {
    const mainApplication = AndroidConfig.Manifest.getMainApplicationOrThrow(config.modResults);
    AndroidConfig.Manifest.addMetaDataItemToMainApplication(
      mainApplication,
      'MY_CUSTOM_API_KEY',
      apiKey
    );
    return config;
  });
  return config;
};
```
Similarly, you can use `withInfoPlist` to modify the **Info.plist** values. Using the `modResults` property, you can add custom values as shown in the code snippet below:
```ts
const withMyApiKey: ConfigPlugin<{ apiKey: string }> = (config, { apiKey }) => {
  config = withInfoPlist(config, config => {
    config.modResults['MY_CUSTOM_API_KEY'] = apiKey;
    return config;
  });
  return config;
};
```
You can create a custom plugin by merging everything into a single function:
```ts plugin/src/index.ts
import {
  withInfoPlist,
  withAndroidManifest,
  AndroidConfig,
  ConfigPlugin,
} from 'expo/config-plugins';
const withMyApiKey: ConfigPlugin<{ apiKey: string }> = (config, { apiKey }) => {
  config = withInfoPlist(config, config => {
    config.modResults['MY_CUSTOM_API_KEY'] = apiKey;
    return config;
  });
  config = withAndroidManifest(config, config => {
    const mainApplication = AndroidConfig.Manifest.getMainApplicationOrThrow(config.modResults);
    AndroidConfig.Manifest.addMetaDataItemToMainApplication(
      mainApplication,
      'MY_CUSTOM_API_KEY',
      apiKey
    );
    return config;
  });
  return config;
};
export default withMyApiKey;
```
With the plugin ready to use, update the example app to pass your API key to the plugin as a configuration option. Modify the `plugins` field in **example/app.json** as shown below:
```json example/app.json
{
  "expo": {
    "plugins": [["../app.plugin.js", { "apiKey": "custom_secret_api" }]]
  }
}
```
After making this change, test that the plugin works correctly by running `npx expo prebuild --clean` inside the **example** directory. This command executes your plugin and updates native files, injecting `"MY_CUSTOM_API_KEY"` into **AndroidManifest.xml** and **Info.plist**. You can verify this by checking the contents of **example/android/app/src/main/AndroidManifest.xml** and **example/ios/exponativeconfigurationexample/Info.plist**.
Step 5: 
## Read native values from the module
Now, make your native module read the fields added to **AndroidManifest.xml** and **Info.plist** by using platform-specific methods to access their contents.
On Android, access metadata information from the **AndroidManifest.xml** file using the `packageManager` class. To read the `"MY_CUSTOM_API_KEY"` value, update the **android/src/main/java/expo/modules/nativeconfiguration/ExpoNativeConfigurationModule.kt** file:
```kotlin android/src/main/java/expo/modules/nativeconfiguration/ExpoNativeConfigurationModule.kt
package expo.modules.nativeconfiguration
import expo.modules.kotlin.modules.Module
import expo.modules.kotlin.modules.ModuleDefinition
import android.content.pm.PackageManager
class ExpoNativeConfigurationModule() : Module() {
  override fun definition() = ModuleDefinition {
    Name("ExpoNativeConfiguration")
    Function("getApiKey") {
      val applicationInfo = appContext?.reactContext?.packageManager?.getApplicationInfo(appContext?.reactContext?.packageName.toString(), PackageManager.GET_META_DATA)
      return@Function applicationInfo?.metaData?.getString("MY_CUSTOM_API_KEY")
    }
  }
}
```
On iOS, you can read the content of an **Info.plist** property using the `Bundle.main.object(forInfoDictionaryKey: "")` method. To access the `"MY_CUSTOM_API_KEY"` value added earlier, update the **ios/ExpoNativeConfigurationModule.swift** file as shown:
```swift ios/ExpoNativeConfigurationModule.swift
import ExpoModulesCore
public class ExpoNativeConfigurationModule: Module {
  public func definition() -> ModuleDefinition {
    Name("ExpoNativeConfiguration")
    Function("getApiKey") {
     return Bundle.main.object(forInfoDictionaryKey: "MY_CUSTOM_API_KEY") as? String
    }
  }
}
```
Step 6: 
## Run your module
With your native modules reading the fields added to the native files, you can now run the example app and access your custom API key using the `ExamplePlugin.getApiKey()` function.
```sh
$ cd example
$ npx expo prebuild
$ npx expo run:android
$ npx expo run:ios
```
## Next steps
Congratulations, you have created a config plugin that interacts with an Expo module for Android and iOS!
If you want to challenge yourself and make the plugin more versatile, this exercise is open for you. Modify the plugin to allow any arbitrary set of config keys and values to be passed in, and add functionality to read arbitrary keys from the module.


## How to use a standalone Expo module

Learn how to use a standalone module created with create-expo-module in your project by using a monorepo or publishing the package to npm.

**The recommended way to create an Expo module** in an existing project is described in the [Expo Modules API: Get Started](/modules/get-started/) guide. This tutorial explains two additional methods for using a module created with `create-expo-module` in an existing project:
- [Configure a monorepo](#use-a-monorepo)
- [Publish the module to npm](#publish-the-module-to-npm)
These methods are useful if you still want to keep the module separate from the application or share it with other developers.
## Use a monorepo
Your project should use the following structure:
- **apps**: A directory to store multiple projects, including React Native apps.
- **packages**: A directory to keep different packages used by your apps.
- **package.json**: This is the root package file that contains the Yarn workspaces configuration.
> **info** To learn how to configure your project as a monorepo, check out the [Working with monorepos](/guides/monorepos/) guide.
Step 1: 
### Initialize a new module
Once you have set up the basic monorepo structure, create a new module using `create-expo-module` with the flag `--no-example` to skip creating the example app:
```sh
$ npx create-expo-module packages/expo-settings --no-example
```
Step 2: 
### Set up a workspace dependency
Add your native module from **packages** to your apps' dependencies. Update the **package.json** file in each app inside the **apps** directory that will use your native module and add your native module to the existing entries of dependencies:
```json package.json
{
  "dependencies": {
    "expo-settings": "*"
  }
}
```
Step 3: 
### Run the module
Run one of your apps to ensure everything works. Then, start the TypeScript compiler in **packages/expo-settings** to watch for changes and rebuild the module's JavaScript:
```sh
$ cd packages/expo-settings
$ npm run build
```
Open another terminal window, select an app from the **apps** directory, and run the `prebuild` command with the `--clean` option. Repeat these steps for each app in your monorepo to use the new module.
```sh
$ npx expo prebuild --clean
```
Compile and run the app with the following command:
```sh
$ npx expo run:android
$ npx expo run:ios
```
You can now use the module in your app. To test it, edit the **app/(tabs)/index.tsx** file in your app and render the text message from the `expo-settings` module:
```tsx app/(tabs)/index.tsx
import React from 'react';
import { Text, View } from 'react-native';
import * as Settings from 'expo-settings';
export default function TabOneScreen() {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text>{Settings.hello()}</Text>
    </View>
  );
}
```
After this configuration, the app displays the text "Hello world! ð".
## Publish the module to npm
You can publish the module on npm and install it as a dependency in your project by following the steps below.
Step 1: 
### Initialize a new module
Start by creating a new module with `create-expo-module`. Follow the prompts carefully, as you will publish this library, and choose a unique name for your npm package.
```sh
$ npx create-expo-module expo-settings
```
Step 2: 
### Run the example project
Run one of your apps to ensure everything works. Then, start the TypeScript compiler in the root of your project to watch for changes and rebuild the module's JavaScript:
```sh
$ npm run build
```
Open another terminal window, compile and run the example app:
```sh
$ cd example
$ npx expo run:android
$ npx expo run:ios
```
Step 3: 
### Publish the package to npm
To publish your package to npm, you need an npm account. If you don't have one, create an account on [the npm website](https://www.npmjs.com/signup). After creating an account, log in by running the following command:
```sh
$ npm login
```
Navigate to the root of your module, then run the following command to publish it:
```sh
$ npm publish
```
Your module will now be published to npm and can be installed in other projects using `npm install`.
Apart from publishing your module to npm, you can use it in your project in the following ways:
- **Create a tarball**: Use `npm pack` to create a tarball of your module, then install it in your project by running `npm install /path/to/tarball`. This method is helpful for testing your module locally before publishing it or sharing it with others who don't have access to the npm registry.
- **Run a local npm registry**: Use a tool such as [Verdaccio](https://verdaccio.org/) to host a local npm registry. You can install your module from this registry, which is useful for managing internal packages within a company or organization.
- **Publish a private package**: [Use a private registry with EAS Build](/build-reference/private-npm-packages/) to manage private modules securely.
Step 4: 
### Test the published module
To test the published module in a new project, create a new app and install the module as a dependency by running the following command:
```sh
$ npx create-expo-app my-app
$ cd my-app
$ npx expo install expo-settings
```
You can now use the module in your app! To test it, edit **app/(tabs)/index.tsx** and render the text message from **expo-settings**.
```tsx app/(tabs)/index.tsx
import React from 'react';
import * as Settings from 'expo-settings';
import { Text, View } from 'react-native';
export default function TabOneScreen() {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text>{Settings.hello()}</Text>
    </View>
  );
}
```
Finally, prebuild your project and run the app by executing the following commands:
```sh
$ npx expo prebuild --clean
$ npx expo run:android
$ npx expo run:ios
```
After this configuration, you see the text "Hello world! ð" displayed in the app.
## Next steps


## Wrap third-party native libraries

Learn how to create a simple wrapper around two separate native libraries using Expo Modules API.

Expo modules make it possible to easily use native, external libraries built for Android and iOS in React Native projects. This tutorial focuses on utilizing the Expo Modules API to create radial charts using two similar libraries accessible on both native platforms.
- [MPAndroidChart by PhilJay](https://github.com/PhilJay/MPAndroidChart)
- [Charts by Daniel Cohen Gindi](https://github.com/danielgindi/Charts)
The iOS library is inspired by the Android library, so they both have similar API and functionality. This makes them a good example for this tutorial.
Video Tutorial: [How to wrap native libraries](https://www.youtube.com/watch?v=M8eNfH1o0eE)
---
Step 1: 
## Create a new module
The following steps assume that the new module is created inside a new Expo project. However, you can create a new module inside an existing project by following the alternative instructions.
For In an existing Expo project: 
Alternatively, you can use the new module as a view inside the existing Expo project directory. Run the following command in your project's directory:
```sh
$ npx create-expo-module --local expo-radial-chart
```
Now, open the newly created `modules/expo-radial-chart` directory to start editing the native code.
For Start with a new module: 
Create a new empty Expo module that can be published on npm and utilized in any Expo app by running the following command:
```sh
$ npx create-expo-module expo-radial-chart
```
> **info** **Tip**: If you aren't going to ship this library, press <kbd>return</kbd> for all the prompts to accept the default values in the terminal window.
Now, open the newly created `expo-radial-chart` directory to start editing the native code.
Step 2: 
## Run the example project
To verify that everything is functioning correctly, let's run the example project.
For In an existing Expo project: 
If you started with an existing Expo project, run the following commands from your Expo project's root directory:
```sh
$ npx expo run:android
$ npx expo run:ios
```
For In a new module: 
If you started with a new module project, open a terminal window, start the TypeScript compiler to watch for changes, and rebuild the module JavaScript:
```sh
$ npm run build
```
In another terminal window, compile and run the example app:
```sh
$ cd example-expo-app
$ npx expo run:android
$ npx expo run:ios
```
Step 3: 
## Add native dependencies
Add the native dependencies to the module by editing the **expo-radial-chart/android/build.gradle** and **expo-radial-chart/ios/ExpoRadialChart.podspec** files:
<DiffBlock source="/static/diffs/third-party-library/android-mpandroidchart-dependency.diff" />
<DiffBlock source="/static/diffs/third-party-library/ios-dgcharts-dependency.diff" />
Note: Are you trying to use a  dependency?
---
  For SDK 52 and later: 
    Inside the **android** directory, create another directory called **libs** and place the **.aar** file inside it. Then, add the file as a Gradle project from autolinking:
<DiffBlock source="/static/diffs/third-party-library/expo-module-config-gradle-aar.diff" />
    Finally, add the dependency to the `dependencies` list in the **android/build.gradle** file, using the dependency's specified name with `${project.name}$` prefix:
<DiffBlock source="/static/diffs/third-party-library/android-build-gradle-aar-project.diff" />
  For SDK 51 and earlier: 
    Inside the **android** directory, create another directory called **libs** and place the **.aar** file inside it. Then, add the directory as a repository:
<DiffBlock source="/static/diffs/third-party-library/android-build-gradle-flatdir.diff" />
Finally, add the dependency to the `dependencies` list. Instead of the filename, use the package path, which includes the `@aar` at the end:
<DiffBlock source="/static/diffs/third-party-library/android-build-gradle-mpandroidchart-aar.diff" />
---
Note: Are you trying to use an  or  dependency?
---
On iOS, you can also use dependencies bundled as a framework by using the `vendored_frameworks` config option.
<DiffBlock source="/static/diffs/third-party-library/ios-podspec-vendored-framework.diff" />
> **info** **Note**: The file pattern used to specify the path to the framework is relative to the podspec file,
> and doesn't support traversing the parent directory (`..`), meaning you need to place the framework inside the **ios** directory
> (or a subdirectory of **ios**).
Once the framework is added, make sure that the `source_files` option file pattern doesn't match any files inside the framework. One way to achieve this is to move your iOS source Swift files (that is `ExpoRadialChartView.swift` and `ExpoRadialChartModule.swift`) into a **src** directory separate from where you placed your framework(s) and update the `source_files` option to only match the **src** directory:
<DiffBlock source="/static/diffs/third-party-library/ios-podspec-source-files-src.diff" />
Your **ios** directory should end up with a file structure similar to this:
```
âââ Frameworks/
â   âââ MyFramework.framework
âââ src/
â   âââ ExpoRadialChartView.swift
â   âââ ExpoRadialChartModule.swift
âââ ExpoRadialChart.podspec
```
---
Step 4: 
## Define an API
To use the module in the app, define the types for the props. It accepts a list of series &mdash; each with a color and a percentage value.
```ts src/ExpoRadialChart.types.ts
import { ViewStyle } from 'react-native/types';
export type ChangeEventPayload = {
  value: string;
};
type Series = {
  color: string;
  percentage: number;
};
export type ExpoRadialChartViewProps = {
  style?: ViewStyle;
  data: Series[];
};
```
Since the module isn't implemented for web in this example, let's replace the **src/ExpoRadialChartView.web.tsx** file:
```tsx src/ExpoRadialChartView.web.tsx
import * as React from 'react';
export default function ExpoRadialChartView() {
  return <div>Not implemented</div>;
}
```
Step 5: 
## Implement the module on Android
Now you can implement the native functionality by editing the placeholder files with the following changes:
1. Create a `PieChart` instance and set its `layoutParams` to match the parent view. Then, add it to the view hierarchy using the `addView` function.
2. Define a `setChartData` function that accepts a list of `Series` objects. You can iterate over the list, create a `PieEntry` for each series and store the colors in a separate list.
3. Create a `PieDataSet`, use it to create a `PieData` object, and set it as data on the `PieChart` instance.
```kotlin android/src/main/java/expo/modules/radialchart/ExpoRadialChartView.kt
package expo.modules.radialchart
import android.content.Context
import android.graphics.Color
import androidx.annotation.ColorInt
import com.github.mikephil.charting.charts.PieChart
import com.github.mikephil.charting.data.PieData
import com.github.mikephil.charting.data.PieDataSet
import com.github.mikephil.charting.data.PieEntry
import expo.modules.kotlin.AppContext
import expo.modules.kotlin.records.Field
import expo.modules.kotlin.records.Record
import expo.modules.kotlin.views.ExpoView
class Series : Record {
  @Field
  val color: String = "#ff0000"
  @Field
  val percentage: Float = 0.0f
}
class ExpoRadialChartView(context: Context, appContext: AppContext) : ExpoView(context, appContext) {
  internal val chartView = PieChart(context).also {
    it.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
    addView(it)
  }
  fun setChartData(data: ArrayList<Series>) {
    val entries: ArrayList<PieEntry> = ArrayList()
    val colors: ArrayList<Int> = ArrayList()
    for (series in data) {
      entries.add(PieEntry(series.percentage))
      colors.add(Color.parseColor(series.color))
    }
    val dataSet = PieDataSet(entries, "DataSet");
    dataSet.colors = colors;
    val pieData = PieData(dataSet);
    chartView.data = pieData;
    chartView.invalidate();
  }
}
```
You also need to use the [`Prop`](/modules/module-api/#prop) function to define the `data` prop and call the native `setChartData` function when the prop changes:
```kotlin android/src/main/java/expo/modules/radialchart/ExpoRadialChartModule.kt
package expo.modules.radialchart
import expo.modules.kotlin.modules.Module
import expo.modules.kotlin.modules.ModuleDefinition
class ExpoRadialChartModule : Module() {
  override fun definition() = ModuleDefinition {
    Name("ExpoRadialChart")
    View(ExpoRadialChartView::class) {
      Prop("data") { view: ExpoRadialChartView, prop: ArrayList<Series> ->
        view.setChartData(prop);
      }
    }
  }
}
```
Step 6: 
## Implement the module on iOS
Now you can implement the native functionality by editing the placeholder files with the following changes:
1. Create a new `PieChartView` instance and use the `addSubview` function to add it to the view hierarchy.
2. Set the `clipsToBounds` property and override the `layoutSubviews` function to make sure the chart view is always the same size as the parent view.
3. Create a `setChartData` function that accepts a list of series, creates a `PieChartDataSet` instance with the data, and assigns it to the `data` property of the `PieChartView` instance.
```swift ios/ExpoRadialChartView.swift
import ExpoModulesCore
import DGCharts
struct Series: Record {
  @Field
  var color: UIColor = UIColor.black
  @Field
  var percentage: Double = 0
}
class ExpoRadialChartView: ExpoView {
  let chartView = PieChartView()
  required init(appContext: AppContext? = nil) {
    super.init(appContext: appContext)
    clipsToBounds = true
    addSubview(chartView)
  }
  override func layoutSubviews() {
    chartView.frame = bounds
  }
  func setChartData(data: [Series]) {
    let set1 = PieChartDataSet(entries: data.map({ (series: Series) -> PieChartDataEntry in
      return PieChartDataEntry(value: series.percentage)
    }))
    set1.colors = data.map({ (series: Series) -> UIColor in
      return series.color
    })
    let chartData: PieChartData = [set1]
    chartView.data = chartData
  }
}
```
You also need to use the [`Prop`](/modules/module-api/#prop) function to define the `data` prop and call the native `setChartData` function when the prop changes:
```swift ios/ExpoRadialChartModule.swift
import ExpoModulesCore
public class ExpoRadialChartModule: Module {
  public func definition() -> ModuleDefinition {
    Name("ExpoRadialChart")
    View(ExpoRadialChartView.self) {
      Prop("data") { (view: ExpoRadialChartView, prop: [Series]) in
        view.setChartData(data: prop)
      }
    }
  }
}
```
Step 7: 
## Write an example app to use the module
You can update the app inside the **app** directory to test the module. Use the `ExpoRadialChartView` component to render a pie chart with three slices:
```tsx app/(tabs)/index.tsx
import { ExpoRadialChartView } from '@/modules/expo-radial-chart';
import { StyleSheet } from 'react-native';
export default function App() {
  return (
    <ExpoRadialChartView
      style={styles.container}
      data={[
        {
          color: '#ff0000',
          percentage: 0.5,
        },
        {
          color: '#00ff00',
          percentage: 0.2,
        },
        {
          color: '#0000ff',
          percentage: 0.3,
        },
      ]}
    />
  );
}
const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
});
```
> **info** **Tip**: If you created a new module, make sure to update the import statement to: `import { ExpoRadialChartView } from 'expo-radial-chart';`
Step 8: 
## Rebuild and launch your application
To make sure your app builds successfully on both platforms, rerun the build commands from step 2. After the app is successfully built on any of the platform you'll see a pie chart with three slices:
Congratulations! You have created your first simple wrapper around two separate third-party native libraries using Expo Modules API.
## Next step


## Integrate in an existing library

Learn how to integrate Expo Modules API into an existing React Native library.

There are cases where you may want to integrate the Expo Modules API into an existing React Native library. For example, it might be useful to incrementally rewrite your library or to take advantage of [Android lifecycle listeners](/modules/android-lifecycle-listeners/) and [iOS AppDelegate subscribers](/modules/appdelegate-subscribers/) to automatically set up the library.
This guide will help you set up your existing React Native library to access Expo Modules API.
## Prerequisites
Create the [**expo-module.config.json**](/modules/module-config/) file at the root of your project and add an empty object `{}` inside it. You will fill it in later to enable specific features.
Creating this file is necessary for [Expo Autolinking](/modules/autolinking/) to recognize your library as an Expo module and automatically link your native code.
Step 1: 
## Add the `expo-modules-core` native dependency
Add `expo-modules-core` as a dependency in your **build.gradle** and **podspec** files:
```groovy
// ...
dependencies {
  // ...
  implementation project(':expo-modules-core')
}
```
```ruby