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 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