19ec76667SBrent Vatne---
29ec76667SBrent Vatnetitle: 'Tutorial: Creating a native view'
34d2795ecSAman Mittalsidebar_title: Create a native view
44d2795ecSAman Mittaldescription: A tutorial on creating a native view that renders a WebView with Expo modules API.
59ec76667SBrent Vatne---
69ec76667SBrent Vatne
79ec76667SBrent Vatneimport { Terminal } from '~/ui/components/Snippet';
89ec76667SBrent Vatneimport { Collapsible } from '~/ui/components/Collapsible';
99ec76667SBrent Vatne
10162a2b31SAman MittalIn this tutorial, we are going to build a module with a native view that will render a WebView. We will be using the [WebView](https://developer.android.com/reference/android/webkit/WebView) component for Android and [WKWebView](https://developer.apple.com/documentation/webkit/wkwebview) for iOS. It is possible to implement web support using [`iframe`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe), but we'll leave that as an exercise for the reader.
119ec76667SBrent Vatne
129ec76667SBrent Vatne## 1. Initialize a new module
139ec76667SBrent Vatne
14162a2b31SAman MittalFirst, we'll create a new module. On this page, we will use the name `expo-web-view`/`ExpoWebView`. You can name it whatever you like, just adjust the instructions accordingly:
159ec76667SBrent Vatne
169ec76667SBrent Vatne<Terminal cmd={['$ npx create-expo-module expo-web-view']} />
179ec76667SBrent Vatne
189ec76667SBrent Vatne> **Tip**: Since you aren't going to actually ship this library, you can hit <kbd>return</kbd> for all of the prompts to accept the default values.
199ec76667SBrent Vatne
209ec76667SBrent Vatne## 2. Set up our workspace
219ec76667SBrent Vatne
229ec76667SBrent VatneNow let's clean up the default module a little bit so we have more of a clean slate and delete the code that we won't use in this guide.
239ec76667SBrent Vatne
249ec76667SBrent Vatne<Terminal
259ec76667SBrent Vatne  cmdCopy="cd expo-web-view && rm src/ExpoWebView.types.ts src/ExpoWebView.web.tsx src/ExpoWebViewModule.ts src/ExpoWebViewModule.web.ts"
269ec76667SBrent Vatne  cmd={[
279ec76667SBrent Vatne    '$ cd expo-web-view',
289ec76667SBrent Vatne    '$ rm src/ExpoWebView.types.ts src/ExpoWebViewModule.ts',
299ec76667SBrent Vatne    '$ rm src/ExpoWebView.web.tsx src/ExpoWebViewModule.web.ts',
309ec76667SBrent Vatne  ]}
319ec76667SBrent Vatne/>
329ec76667SBrent Vatne
339ec76667SBrent VatneFind the following files and replace them with the provided minimal boilerplate:
349ec76667SBrent Vatne
359ec76667SBrent Vatne```swift ios/ExpoWebViewModule.swift
369ec76667SBrent Vatneimport ExpoModulesCore
379ec76667SBrent Vatne
389ec76667SBrent Vatnepublic class ExpoWebViewModule: Module {
399ec76667SBrent Vatne  public func definition() -> ModuleDefinition {
409ec76667SBrent Vatne    Name("ExpoWebView")
419ec76667SBrent Vatne
429ec76667SBrent Vatne    View(ExpoWebView.self) {}
439ec76667SBrent Vatne  }
449ec76667SBrent Vatne}
459ec76667SBrent Vatne```
469ec76667SBrent Vatne
479ec76667SBrent Vatne```kotlin android/src/main/java/expo/modules/webview/ExpoWebViewModule.kt
489ec76667SBrent Vatnepackage expo.modules.webview
499ec76667SBrent Vatne
509ec76667SBrent Vatneimport expo.modules.kotlin.modules.Module
519ec76667SBrent Vatneimport expo.modules.kotlin.modules.ModuleDefinition
529ec76667SBrent Vatne
539ec76667SBrent Vatneclass ExpoWebViewModule : Module() {
549ec76667SBrent Vatne  override fun definition() = ModuleDefinition {
559ec76667SBrent Vatne    Name("ExpoWebView")
569ec76667SBrent Vatne
579ec76667SBrent Vatne    View(ExpoWebView::class) {}
589ec76667SBrent Vatne  }
599ec76667SBrent Vatne}
609ec76667SBrent Vatne```
619ec76667SBrent Vatne
629ec76667SBrent Vatne```typescript src/index.ts
639ec76667SBrent Vatneexport { default as WebView, Props as WebViewProps } from './ExpoWebView';
649ec76667SBrent Vatne```
659ec76667SBrent Vatne
669ec76667SBrent Vatne```typescript src/ExpoWebView.tsx
679ec76667SBrent Vatneimport { ViewProps } from 'react-native';
689ec76667SBrent Vatneimport { requireNativeViewManager } from 'expo-modules-core';
699ec76667SBrent Vatneimport * as React from 'react';
709ec76667SBrent Vatne
719ec76667SBrent Vatneexport type Props = ViewProps;
729ec76667SBrent Vatne
739ec76667SBrent Vatneconst NativeView: React.ComponentType<Props> = requireNativeViewManager('ExpoWebView');
749ec76667SBrent Vatne
759ec76667SBrent Vatneexport default function ExpoWebView(props: Props) {
769ec76667SBrent Vatne  return <NativeView {...props} />;
779ec76667SBrent Vatne}
789ec76667SBrent Vatne```
799ec76667SBrent Vatne
809ec76667SBrent Vatne```typescript example/App.tsx
819ec76667SBrent Vatneimport { WebView } from 'expo-web-view';
829ec76667SBrent Vatne
839ec76667SBrent Vatneexport default function App() {
849ec76667SBrent Vatne  return <WebView style={{ flex: 1, backgroundColor: 'purple' }} />;
859ec76667SBrent Vatne}
869ec76667SBrent Vatne```
879ec76667SBrent Vatne
889ec76667SBrent Vatne## 3. Run the example project
899ec76667SBrent Vatne
909ec76667SBrent VatneNow let's run the example project to make sure everything is working. We'll need to start the TypeScript compiler to watch for changes and rebuild the module JavaScript, and separately in another terminal window we'll compile and run the example app.
919ec76667SBrent Vatne
929ec76667SBrent Vatne<Terminal
939ec76667SBrent Vatne  cmdCopy="npm run build"
949ec76667SBrent Vatne  cmd={[
959ec76667SBrent Vatne    '# Run this in the root of the project to start the TypeScript compiler',
969ec76667SBrent Vatne    '$ npm run build',
979ec76667SBrent Vatne  ]}
989ec76667SBrent Vatne/>
999ec76667SBrent Vatne
1009ec76667SBrent Vatne<Terminal
1019ec76667SBrent Vatne  cmdCopy="cd example && npx expo run:ios"
1029ec76667SBrent Vatne  cmd={[
1039ec76667SBrent Vatne    '$ cd example',
1049ec76667SBrent Vatne    '# Run the example app on iOS',
1059ec76667SBrent Vatne    '$ npx expo run:ios',
1069ec76667SBrent Vatne    '# Run the example app on Android',
1079ec76667SBrent Vatne    '$ npx expo run:android',
1089ec76667SBrent Vatne  ]}
1099ec76667SBrent Vatne/>
1109ec76667SBrent Vatne
111c07b489cSAman MittalWe should now see a blank purple screen. That's not very exciting. However, it's a good start. Let's make it a WebView now.
1129ec76667SBrent Vatne
1139ec76667SBrent Vatne## 4. Add the system WebView as a subview
1149ec76667SBrent Vatne
1159ec76667SBrent VatneNow we are going to add the system WebView with a hardcoded URL as a subview of our ExpoWebView. Our `ExpoWebView` class extends `ExpoView`, which extends `RCTView` from React Native, which finally extends `UIView` on iOS and `View` on Android. We need to ensure that the WebView subview has the same layout as ExpoWebView, whose layout will be calculated by React Native's layout engine.
1169ec76667SBrent Vatne
1179ec76667SBrent Vatne### iOS view
1189ec76667SBrent Vatne
1199ec76667SBrent VatneOn iOS, we set `clipsToBounds` to `true` and set the `frame` of the WebView to the bounds of the ExpoWebView in `layoutSubviews` to match the layout. `init` is called when the view is created, and `layoutSubviews` is called when the layout changes.
1209ec76667SBrent Vatne
1219ec76667SBrent Vatne```swift ios/ExpoWebView.swift
1229ec76667SBrent Vatneimport ExpoModulesCore
1239ec76667SBrent Vatneimport WebKit
1249ec76667SBrent Vatne
1259ec76667SBrent Vatneclass ExpoWebView: ExpoView {
1269ec76667SBrent Vatne  let webView = WKWebView()
1279ec76667SBrent Vatne
1289ec76667SBrent Vatne  required init(appContext: AppContext? = nil) {
1299ec76667SBrent Vatne    super.init(appContext: appContext)
1309ec76667SBrent Vatne    clipsToBounds = true
1319ec76667SBrent Vatne    addSubview(webView)
1329ec76667SBrent Vatne
1339ec76667SBrent Vatne    let url =  URL(string:"https://docs.expo.dev/modules/")!
1349ec76667SBrent Vatne    let urlRequest = URLRequest(url:url)
1359ec76667SBrent Vatne    webView.load(urlRequest)
1369ec76667SBrent Vatne  }
1379ec76667SBrent Vatne
1389ec76667SBrent Vatne  override func layoutSubviews() {
1399ec76667SBrent Vatne    webView.frame = bounds
1409ec76667SBrent Vatne  }
1419ec76667SBrent Vatne}
1429ec76667SBrent Vatne```
1439ec76667SBrent Vatne
1449ec76667SBrent Vatne### Android view
1459ec76667SBrent Vatne
1469ec76667SBrent VatneOn Android we use `LayoutParams` to set the layout of the WebView to match the layout of the ExpoWebView. We can do this when we instantiate the WebView.
1479ec76667SBrent Vatne
1489ec76667SBrent Vatne```kotlin android/src/main/java/expo/modules/webview/ExpoWebView.kt
1499ec76667SBrent Vatnepackage expo.modules.webview
1509ec76667SBrent Vatne
1519ec76667SBrent Vatneimport android.content.Context
1529ec76667SBrent Vatneimport android.webkit.WebView
1539ec76667SBrent Vatneimport android.webkit.WebViewClient
1549ec76667SBrent Vatneimport expo.modules.kotlin.AppContext
1559ec76667SBrent Vatneimport expo.modules.kotlin.views.ExpoView
1569ec76667SBrent Vatne
1579ec76667SBrent Vatneclass ExpoWebView(context: Context, appContext: AppContext) : ExpoView(context, appContext) {
1589ec76667SBrent Vatne  internal val webView = WebView(context).also {
1599ec76667SBrent Vatne    it.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
1609ec76667SBrent Vatne    it.webViewClient = object : WebViewClient() {}
1619ec76667SBrent Vatne    addView(it)
1629ec76667SBrent Vatne
1639ec76667SBrent Vatne    it.loadUrl("https://docs.expo.dev/modules/")
1649ec76667SBrent Vatne  }
1659ec76667SBrent Vatne}
1669ec76667SBrent Vatne```
1679ec76667SBrent Vatne
1689ec76667SBrent Vatne### Example app
1699ec76667SBrent Vatne
1709ec76667SBrent VatneNo changes are needed, we can rebuild and run the app and you will see the [Expo Modules API overview page](/modules/).
1719ec76667SBrent Vatne
1729ec76667SBrent Vatne## 5. Add a prop to set the URL
1739ec76667SBrent Vatne
1749ec76667SBrent VatneTo set a prop on our view, we'll need to define the prop name and setter inside of `ExpoWebViewModule`. In this case we're going to reach in and access `webView` property directly for convenience, but in many real world cases you will likely want to keep this logic inside of the `ExpoWebView` class and minimize the knowledge that `ExpoWebViewModule` has about the internals of `ExpoWebView`.
1759ec76667SBrent Vatne
1769ec76667SBrent VatneWe use the [Prop definition component](/modules/module-api/#prop) to define the prop. Within the prop setter block we can access the view and the prop. Note that we specify the url is of type `URL` — the Expo modules API will take care of converting strings to the native `URL` type for us.
1779ec76667SBrent Vatne
1789ec76667SBrent Vatne### iOS module
1799ec76667SBrent Vatne
1809ec76667SBrent Vatne```swift ios/ExpoWebViewModule.swift
1819ec76667SBrent Vatneimport ExpoModulesCore
1829ec76667SBrent Vatne
1839ec76667SBrent Vatnepublic class ExpoWebViewModule: Module {
1849ec76667SBrent Vatne  public func definition() -> ModuleDefinition {
1859ec76667SBrent Vatne    Name("ExpoWebView")
1869ec76667SBrent Vatne
1879ec76667SBrent Vatne    View(ExpoWebView.self) {
1889ec76667SBrent Vatne      Prop("url") { (view, url: URL) in
1899ec76667SBrent Vatne        if view.webView.url != url {
1909ec76667SBrent Vatne          let urlRequest = URLRequest(url: url)
1919ec76667SBrent Vatne          view.webView.load(urlRequest)
1929ec76667SBrent Vatne        }
1939ec76667SBrent Vatne      }
1949ec76667SBrent Vatne    }
1959ec76667SBrent Vatne  }
1969ec76667SBrent Vatne}
1979ec76667SBrent Vatne```
1989ec76667SBrent Vatne
1999ec76667SBrent Vatne### Android module
2009ec76667SBrent Vatne
2019ec76667SBrent Vatne```kotlin android/src/main/java/expo/modules/webview/ExpoWebViewModule.kt
2029ec76667SBrent Vatnepackage expo.modules.webview
2039ec76667SBrent Vatne
2049ec76667SBrent Vatneimport expo.modules.kotlin.modules.Module
2059ec76667SBrent Vatneimport expo.modules.kotlin.modules.ModuleDefinition
2069ec76667SBrent Vatneimport java.net.URL
2079ec76667SBrent Vatne
2089ec76667SBrent Vatneclass ExpoWebViewModule : Module() {
2099ec76667SBrent Vatne  override fun definition() = ModuleDefinition {
2109ec76667SBrent Vatne    Name("ExpoWebView")
2119ec76667SBrent Vatne
2129ec76667SBrent Vatne    View(ExpoWebView::class) {
2139ec76667SBrent Vatne      Prop("url") { view: ExpoWebView, url: URL? ->
2149ec76667SBrent Vatne        view.webView.loadUrl(url.toString())
2159ec76667SBrent Vatne      }
2169ec76667SBrent Vatne    }
2179ec76667SBrent Vatne  }
2189ec76667SBrent Vatne}
2199ec76667SBrent Vatne```
2209ec76667SBrent Vatne
2219ec76667SBrent Vatne### TypeScript module
2229ec76667SBrent Vatne
2239ec76667SBrent VatneAll we need to do here is add the `url` prop to the `Props` type.
2249ec76667SBrent Vatne
2259ec76667SBrent Vatne```typescript src/ExpoWebView.tsx
2269ec76667SBrent Vatneimport { ViewProps } from 'react-native';
2279ec76667SBrent Vatneimport { requireNativeViewManager } from 'expo-modules-core';
2289ec76667SBrent Vatneimport * as React from 'react';
2299ec76667SBrent Vatne
2309ec76667SBrent Vatneexport type Props = {
2319ec76667SBrent Vatne  url?: string;
2329ec76667SBrent Vatne} & ViewProps;
2339ec76667SBrent Vatne
2349ec76667SBrent Vatneconst NativeView: React.ComponentType<Props> = requireNativeViewManager('ExpoWebView');
2359ec76667SBrent Vatne
2369ec76667SBrent Vatneexport default function ExpoWebView(props: Props) {
2379ec76667SBrent Vatne  return <NativeView {...props} />;
2389ec76667SBrent Vatne}
2399ec76667SBrent Vatne```
2409ec76667SBrent Vatne
2419ec76667SBrent Vatne### Example app
2429ec76667SBrent Vatne
2439ec76667SBrent VatneFinally, we can pass in a URL to our WebView component in the example app.
2449ec76667SBrent Vatne
2459ec76667SBrent Vatne```typescript example/App.tsx
2469ec76667SBrent Vatneimport { WebView } from 'expo-web-view';
2479ec76667SBrent Vatne
2489ec76667SBrent Vatneexport default function App() {
2499ec76667SBrent Vatne  return <WebView style={{ flex: 1 }} url="https://expo.dev" />;
2509ec76667SBrent Vatne}
2519ec76667SBrent Vatne```
2529ec76667SBrent Vatne
2539ec76667SBrent VatneWhen you rebuild and run the app, you will now see the Expo homepage.
2549ec76667SBrent Vatne
25530c3f1a5SBrent Vatne## 6. Add an event to notify when the page has loaded
2569ec76667SBrent Vatne
2579ec76667SBrent Vatne[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={...} />`. We can use the [Events definition component](/modules/module-api/#events) to define an event for our WebView. We'll call it `onLoad` as well.
2589ec76667SBrent Vatne
2599ec76667SBrent Vatne### iOS view and module
2609ec76667SBrent Vatne
2619ec76667SBrent VatneOn iOS, we need to implement `webView(_:didFinish:)` and make ExpoWebView extend `WKNavigationDelegate`. We can then call the `onLoad` from that delegate method.
2629ec76667SBrent Vatne
2639ec76667SBrent Vatne```swift ios/ExpoWebView.swift
2649ec76667SBrent Vatneimport ExpoModulesCore
2659ec76667SBrent Vatneimport WebKit
2669ec76667SBrent Vatne
2679ec76667SBrent Vatneclass ExpoWebView: ExpoView, WKNavigationDelegate {
2689ec76667SBrent Vatne  let webView = WKWebView()
2699ec76667SBrent Vatne  let onLoad = EventDispatcher()
2709ec76667SBrent Vatne
2719ec76667SBrent Vatne  required init(appContext: AppContext? = nil) {
2729ec76667SBrent Vatne    super.init(appContext: appContext)
2739ec76667SBrent Vatne    clipsToBounds = true
2749ec76667SBrent Vatne    webView.navigationDelegate = self
2759ec76667SBrent Vatne    addSubview(webView)
2769ec76667SBrent Vatne  }
2779ec76667SBrent Vatne
2789ec76667SBrent Vatne  override func layoutSubviews() {
2799ec76667SBrent Vatne    webView.frame = bounds
2809ec76667SBrent Vatne  }
2819ec76667SBrent Vatne
2829ec76667SBrent Vatne  func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
2839ec76667SBrent Vatne    if let url = webView.url {
2849ec76667SBrent Vatne      onLoad([
2859ec76667SBrent Vatne        "url": url.absoluteString
2869ec76667SBrent Vatne      ])
2879ec76667SBrent Vatne    }
2889ec76667SBrent Vatne  }
2899ec76667SBrent Vatne}
2909ec76667SBrent Vatne```
2919ec76667SBrent Vatne
2929ec76667SBrent VatneAnd we need to indicate in ExpoWebViewModule that the `View` has an `onLoad` event.
2939ec76667SBrent Vatne
2949ec76667SBrent Vatne```swift ios/ExpoWebViewModule.swift
2959ec76667SBrent Vatneimport ExpoModulesCore
2969ec76667SBrent Vatne
2979ec76667SBrent Vatnepublic class ExpoWebViewModule: Module {
2989ec76667SBrent Vatne  public func definition() -> ModuleDefinition {
2999ec76667SBrent Vatne    Name("ExpoWebView")
3009ec76667SBrent Vatne
3019ec76667SBrent Vatne    View(ExpoWebView.self) {
3029ec76667SBrent Vatne      Events("onLoad")
3039ec76667SBrent Vatne
3049ec76667SBrent Vatne      Prop("url") { (view, url: URL) in
3059ec76667SBrent Vatne        if view.webView.url != url {
3069ec76667SBrent Vatne          let urlRequest = URLRequest(url: url)
3079ec76667SBrent Vatne          view.webView.load(urlRequest)
3089ec76667SBrent Vatne        }
3099ec76667SBrent Vatne      }
3109ec76667SBrent Vatne    }
3119ec76667SBrent Vatne  }
3129ec76667SBrent Vatne}
3139ec76667SBrent Vatne```
3149ec76667SBrent Vatne
3159ec76667SBrent Vatne### Android view and module
3169ec76667SBrent Vatne
3179ec76667SBrent VatneOn Android, we need to add override the `onPageFinished` function. We can then call the `onLoad` event handler that we defined in the module.
3189ec76667SBrent Vatne
3199ec76667SBrent Vatne```kotlin android/src/main/java/expo/modules/webview/ExpoWebView.kt
3209ec76667SBrent Vatnepackage expo.modules.webview
3219ec76667SBrent Vatne
3229ec76667SBrent Vatneimport android.content.Context
3239ec76667SBrent Vatneimport android.webkit.WebView
3249ec76667SBrent Vatneimport android.webkit.WebViewClient
3259ec76667SBrent Vatneimport expo.modules.kotlin.AppContext
3269ec76667SBrent Vatneimport expo.modules.kotlin.viewevent.EventDispatcher
3279ec76667SBrent Vatneimport expo.modules.kotlin.views.ExpoView
3289ec76667SBrent Vatne
3299ec76667SBrent Vatneclass ExpoWebView(context: Context, appContext: AppContext) : ExpoView(context, appContext) {
3309ec76667SBrent Vatne  private val onLoad by EventDispatcher()
3319ec76667SBrent Vatne
3329ec76667SBrent Vatne  internal val webView = WebView(context).also {
3339ec76667SBrent Vatne    it.layoutParams = LayoutParams(
3349ec76667SBrent Vatne      LayoutParams.MATCH_PARENT,
3359ec76667SBrent Vatne      LayoutParams.MATCH_PARENT
3369ec76667SBrent Vatne    )
3379ec76667SBrent Vatne
3389ec76667SBrent Vatne    it.webViewClient = object : WebViewClient() {
3399ec76667SBrent Vatne      override fun onPageFinished(view: WebView, url: String) {
3409ec76667SBrent Vatne        onLoad(mapOf("url" to url))
3419ec76667SBrent Vatne      }
3429ec76667SBrent Vatne    }
3439ec76667SBrent Vatne
3449ec76667SBrent Vatne    addView(it)
3459ec76667SBrent Vatne  }
3469ec76667SBrent Vatne}
3479ec76667SBrent Vatne```
3489ec76667SBrent Vatne
3499ec76667SBrent VatneAnd we need to indicate in ExpoWebViewModule that the `View` has an `onLoad` event.
3509ec76667SBrent Vatne
3519ec76667SBrent Vatne```kotlin android/src/main/java/expo/modules/webview/ExpoWebViewModule.kt
3529ec76667SBrent Vatnepackage expo.modules.webview
3539ec76667SBrent Vatne
3549ec76667SBrent Vatneimport expo.modules.kotlin.modules.Module
3559ec76667SBrent Vatneimport expo.modules.kotlin.modules.ModuleDefinition
3569ec76667SBrent Vatneimport java.net.URL
3579ec76667SBrent Vatne
3589ec76667SBrent Vatneclass ExpoWebViewModule : Module() {
3599ec76667SBrent Vatne  override fun definition() = ModuleDefinition {
3609ec76667SBrent Vatne    Name("ExpoWebView")
3619ec76667SBrent Vatne
3629ec76667SBrent Vatne    View(ExpoWebView::class) {
3639ec76667SBrent Vatne      Events("onLoad")
3649ec76667SBrent Vatne
3659ec76667SBrent Vatne      Prop("url") { view: ExpoWebView, url: URL? ->
3669ec76667SBrent Vatne        view.webView.loadUrl(url.toString())
3679ec76667SBrent Vatne      }
3689ec76667SBrent Vatne    }
3699ec76667SBrent Vatne  }
3709ec76667SBrent Vatne}
3719ec76667SBrent Vatne```
3729ec76667SBrent Vatne
3739ec76667SBrent Vatne### TypeScript module
3749ec76667SBrent Vatne
3759ec76667SBrent VatneNote that event payloads are included within the `nativeEvent` property of the event, so to access the `url` from the `onLoad` event we would read `event.nativeEvent.url`.
3769ec76667SBrent Vatne
3779ec76667SBrent Vatne```typescript src/ExpoWebView.tsx
3789ec76667SBrent Vatneimport { ViewProps } from 'react-native';
3799ec76667SBrent Vatneimport { requireNativeViewManager } from 'expo-modules-core';
3809ec76667SBrent Vatneimport * as React from 'react';
3819ec76667SBrent Vatne
3829ec76667SBrent Vatneexport type OnLoadEvent = {
3839ec76667SBrent Vatne  url: string;
3849ec76667SBrent Vatne};
3859ec76667SBrent Vatne
3869ec76667SBrent Vatneexport type Props = {
3879ec76667SBrent Vatne  url?: string;
3889ec76667SBrent Vatne  onLoad?: (event: { nativeEvent: OnLoadEvent }) => void;
3899ec76667SBrent Vatne} & ViewProps;
3909ec76667SBrent Vatne
3919ec76667SBrent Vatneconst NativeView: React.ComponentType<Props> = requireNativeViewManager('ExpoWebView');
3929ec76667SBrent Vatne
3939ec76667SBrent Vatneexport default function ExpoWebView(props: Props) {
3949ec76667SBrent Vatne  return <NativeView {...props} />;
3959ec76667SBrent Vatne}
3969ec76667SBrent Vatne```
3979ec76667SBrent Vatne
3989ec76667SBrent Vatne### Example app
3999ec76667SBrent Vatne
4009ec76667SBrent VatneNow we can update the example app to show an alert when the page has loaded. Copy in the following code, then rebuild and run your app, and you should see the alert!
4019ec76667SBrent Vatne
4029ec76667SBrent Vatne```typescript example/App.tsx
4039ec76667SBrent Vatneimport { WebView } from 'expo-web-view';
4049ec76667SBrent Vatne
4059ec76667SBrent Vatneexport default function App() {
4069ec76667SBrent Vatne  return (
4079ec76667SBrent Vatne    <WebView
4089ec76667SBrent Vatne      style={{ flex: 1 }}
4099ec76667SBrent Vatne      url="https://expo.dev"
4109ec76667SBrent Vatne      onLoad={event => alert(`loaded ${event.nativeEvent.url}`)}
4119ec76667SBrent Vatne    />
4129ec76667SBrent Vatne  );
4139ec76667SBrent Vatne}
4149ec76667SBrent Vatne```
4159ec76667SBrent Vatne
41630c3f1a5SBrent Vatne## 7. Bonus: Build a web browser UI around it
4179ec76667SBrent Vatne
4189ec76667SBrent VatneNow that we have a web view, we can build a web browser UI around it. Have some fun trying to rebuild a browser UI, and maybe even add new native capabilities as needed (for example, to support a back or reload buttons). If you'd like some inspiration, there's a simple example below.
4199ec76667SBrent Vatne
4209ec76667SBrent Vatne<Collapsible summary="example/App.tsx">
4219ec76667SBrent Vatne
4229ec76667SBrent Vatne```typescript
423162a2b31SAman Mittalimport { useState } from 'react';
424162a2b31SAman Mittalimport { ActivityIndicator, Platform, Text, TextInput, View } from 'react-native';
425162a2b31SAman Mittalimport { WebView } from 'expo-web-view';
426162a2b31SAman Mittalimport { StatusBar } from 'expo-status-bar';
4279ec76667SBrent Vatne
4289ec76667SBrent Vatneexport default function App() {
429162a2b31SAman Mittal  const [inputUrl, setInputUrl] = useState('https://docs.expo.dev/modules/');
4309ec76667SBrent Vatne  const [url, setUrl] = useState(inputUrl);
4319ec76667SBrent Vatne  const [isLoading, setIsLoading] = useState(true);
4329ec76667SBrent Vatne
4339ec76667SBrent Vatne  return (
434162a2b31SAman Mittal    <View style={{ flex: 1, paddingTop: Platform.OS === 'ios' ? 80 : 30 }}>
4359ec76667SBrent Vatne      <TextInput
4369ec76667SBrent Vatne        value={inputUrl}
4379ec76667SBrent Vatne        onChangeText={setInputUrl}
4389ec76667SBrent Vatne        returnKeyType="go"
4399ec76667SBrent Vatne        autoCapitalize="none"
4409ec76667SBrent Vatne        onSubmitEditing={() => {
4419ec76667SBrent Vatne          if (inputUrl !== url) {
4429ec76667SBrent Vatne            setUrl(inputUrl);
4439ec76667SBrent Vatne            setIsLoading(true);
4449ec76667SBrent Vatne          }
4459ec76667SBrent Vatne        }}
4469ec76667SBrent Vatne        keyboardType="url"
4479ec76667SBrent Vatne        style={{
448162a2b31SAman Mittal          color: '#fff',
449162a2b31SAman Mittal          backgroundColor: '#000',
4509ec76667SBrent Vatne          borderRadius: 10,
4519ec76667SBrent Vatne          marginHorizontal: 10,
4529ec76667SBrent Vatne          paddingHorizontal: 20,
4539ec76667SBrent Vatne          height: 60,
4549ec76667SBrent Vatne        }}
4559ec76667SBrent Vatne      />
4569ec76667SBrent Vatne
4579ec76667SBrent Vatne      <WebView
458162a2b31SAman Mittal        url={url.startsWith('https://') || url.startsWith('http://') ? url : `https://${url}`}
4599ec76667SBrent Vatne        onLoad={() => setIsLoading(false)}
4609ec76667SBrent Vatne        style={{ flex: 1, marginTop: 20 }}
4619ec76667SBrent Vatne      />
4629ec76667SBrent Vatne      <LoadingView isLoading={isLoading} />
4639ec76667SBrent Vatne      <StatusBar style="auto" />
4649ec76667SBrent Vatne    </View>
4659ec76667SBrent Vatne  );
4669ec76667SBrent Vatne}
4679ec76667SBrent Vatne
4689ec76667SBrent Vatnefunction LoadingView({ isLoading }: { isLoading: boolean }) {
4699ec76667SBrent Vatne  if (!isLoading) {
4709ec76667SBrent Vatne    return null;
4719ec76667SBrent Vatne  }
4729ec76667SBrent Vatne
4739ec76667SBrent Vatne  return (
4749ec76667SBrent Vatne    <View
4759ec76667SBrent Vatne      style={{
476162a2b31SAman Mittal        position: 'absolute',
4779ec76667SBrent Vatne        bottom: 0,
4789ec76667SBrent Vatne        left: 0,
4799ec76667SBrent Vatne        right: 0,
4809ec76667SBrent Vatne        height: 80,
481162a2b31SAman Mittal        backgroundColor: 'rgba(0,0,0,0.5)',
4829ec76667SBrent Vatne        paddingBottom: 10,
483162a2b31SAman Mittal        justifyContent: 'center',
484162a2b31SAman Mittal        alignItems: 'center',
485162a2b31SAman Mittal        flexDirection: 'row',
486162a2b31SAman Mittal      }}>
487162a2b31SAman Mittal      <ActivityIndicator animating={isLoading} color="#fff" style={{ marginRight: 10 }} />
488162a2b31SAman Mittal      <Text style={{ color: '#fff' }}>Loading...</Text>
4899ec76667SBrent Vatne    </View>
4909ec76667SBrent Vatne  );
4919ec76667SBrent Vatne}
4929ec76667SBrent Vatne```
4939ec76667SBrent Vatne
4949ec76667SBrent Vatne</Collapsible>
4959ec76667SBrent Vatne
4969ec76667SBrent Vatne![A simple web browser UI built around our WebView](/static/images/modules/native-view-tutorial/web-browser.png)
4979ec76667SBrent Vatne
4989ec76667SBrent Vatne## Next steps
4999ec76667SBrent Vatne
500*afd513e4SAman MittalCongratulations, you have created your first simple yet non-trivial Expo module with a native view for Android and iOS! Learn more about the API in the [Expo Module API reference](/modules/module-api/).
5019ec76667SBrent Vatne
502*afd513e4SAman MittalIf you enjoyed this tutorial and haven't done the native module tutorial, see [creating a native module](/modules/native-module-tutorial/) next.
503