You can provide web-based content---such as HTML, JavaScript, and
CSS---for your app to use that you statically compile into the app rather
than fetch over the internet.

In-app content doesn't require internet access or consume a user's bandwidth. If
the content is designed specifically for `WebView` only---that is, it
depends on communicating with a native app---then users can't accidentally
load it in a web browser.

However, there are some drawbacks to in-app content. Updating web-based content
requires shipping a new app update, and there is the possibility of mismatched
content between what's on a website and what's in the app on your device if
users have outdated app versions.

## WebViewAssetLoader

[`WebViewAssetLoader`](https://developer.android.com/reference/androidx/webkit/WebViewAssetLoader) is a
flexible and performant way to load in-app content in a
[`WebView`](https://developer.android.com/reference/android/webkit/WebView) object. This class supports the
following:

- Loading content with an HTTP(S) URL for compatibility with the [same-origin
  policy](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy).
- Loading subresources such as JavaScript, CSS, images, and iframes.

Include `WebViewAssetLoader` in your main activity file. The following is an
example of loading simple web content from the assets folder:  

### Kotlin

```kotlin
private class LocalContentWebViewClient(private val assetLoader: WebViewAssetLoader) : WebViewClientCompat() {
    @RequiresApi(21)
    override fun shouldInterceptRequest(
        view: WebView,
        request: WebResourceRequest
    ): WebResourceResponse? {
        return assetLoader.shouldInterceptRequest(request.url)
    }

    // To support API < 21.
    override fun shouldInterceptRequest(
        view: WebView,
        url: String
    ): WebResourceResponse? {
        return assetLoader.shouldInterceptRequest(Uri.parse(url))
    }
}
```

### Java

```java
private static class LocalContentWebViewClient extends WebViewClientCompat {

    private final WebViewAssetLoader mAssetLoader;

    LocalContentWebViewClient(WebViewAssetLoader assetLoader) {
        mAssetLoader = assetLoader;
    }

    @Override
    @RequiresApi(21)
    public WebResourceResponse shouldInterceptRequest(WebView view,
                                     WebResourceRequest request) {
        return mAssetLoader.shouldInterceptRequest(request.getUrl());
    }

    @Override
    @SuppressWarnings("deprecation") // To support API < 21.
    public WebResourceResponse shouldInterceptRequest(WebView view,
                                     String url) {
        return mAssetLoader.shouldInterceptRequest(Uri.parse(url));
    }
}
```

Your app must configure a `WebViewAssetLoader` instance to suit its needs. The
next section has an example.

### Create in-app assets and resources

`WebViewAssetLoader` relies on
[`PathHandler`](https://developer.android.com/reference/androidx/webkit/WebViewAssetLoader.PathHandler)
instances to load resources corresponding to a given resource path. Although you
can implement this interface to retrieve resources as needed by your app, the
Webkit library bundles
[`AssetsPathHandler`](https://developer.android.com/reference/kotlin/androidx/webkit/WebViewAssetLoader.AssetsPathHandler)
and
[`ResourcesPathHandler`](https://developer.android.com/reference/kotlin/androidx/webkit/WebViewAssetLoader.ResourcesPathHandler)
for loading Android assets and resources, respectively.

To get started, create assets and resources for your app. Generally, the
following applies:

- Text files like HTML, JavaScript, and CSS belong in assets.
- Images and other binary files belong in resources.

To add text-based web files to a project, do the following:

1. In Android Studio, right-click the **app \> src \> main** folder and then choose **New \> Directory** . ![An image showing Android Studio create-directory menus](https://developer.android.com/static/images/guide/webapps/create-assets-directory.png) **Figure 1.** Create an assets folder for your project.
2. Name the folder "assets". ![An image showing the asset folder](https://developer.android.com/static/images/guide/webapps/name-assets-directory.png) **Figure 2.** Name the assets folder.
3. Right-click the **assets** folder and then click **New \> File** . Enter `index.html` and press the <kbd>Return</kbd> or <kbd>Enter</kbd> key. ![An image of Android Studio create file menu](https://developer.android.com/static/images/guide/webapps/create-webview-file.png) **Figure 3.** Create the `index.html` file.
4. Repeat the previous step to create an empty file for `stylesheet.css`.
5. Fill in the empty files you created with the content in the next two code samples.

    ```html
    <!-- index.html content -->

    <html>
      <head>
        <!-- Tip: Use relative URLs when referring to other in-app content to give
                  your app code the flexibility to change the scheme or domain as
                  necessary. -->
        <link rel="stylesheet" href="/assets/stylesheet.css">
      </head>
      <body>
        <p>This file is loaded from in-app content.</p>
        <p><img src="/res/drawable/android_robot.png" alt="Android robot" width="100"></p>
      </body>
    </html>
    ```

    ```css
    <!-- stylesheet.css content -->

    body {
      background-color: lightblue;
    }
    ```

To add an image-based web file to your project, do the following:

1. Download the
   [`Android_symbol_green_RGB.png`](https://source.android.com/setup/images/Android_symbol_green_RGB.png)
   file to your local machine.

2. Rename the file to `android_robot.png`.

3. Manually move the file into your project's `main/res/drawable` directory on
   your hard drive.

Figure 4 shows the image you added and the text from the preceding code samples
rendered in an app.
![An image showing app rendered output](https://developer.android.com/static/images/guide/webapps/rendered-html.png) **Figure 4.** In-app HTML file and image file rendered in an app.

To complete the app, do the following:

1. Register the handlers and configure the `AssetLoader` by adding the
   following code to the `onCreate()` method:

   ### Kotlin

   ```kotlin
   val assetLoader = WebViewAssetLoader.Builder()
                          .addPathHandler("/assets/", AssetsPathHandler(this))
                          .addPathHandler("/res/", ResourcesPathHandler(this))
                          .build()
   webView.webViewClient = LocalContentWebViewClient(assetLoader)
   ```

   ### Java

   ```java
   final WebViewAssetLoader assetLoader = new WebViewAssetLoader.Builder()
            .addPathHandler("/assets/", new WebViewAssetLoader.AssetsPathHandler(this))
            .addPathHandler("/res/", new WebViewAssetLoader.ResourcesPathHandler(this))
            .build();
   mWebView.setWebViewClient(new LocalContentWebViewClient(assetLoader));
   ```
2. Load the content by adding the following code to the `onCreate()` method:

   ### Kotlin

   ```kotlin
   webView.loadUrl("https://appassets.androidplatform.net/assets/index.html")
   ```

   ### Java

   ```java
   mWebView.loadUrl("https://appassets.androidplatform.net/assets/index.html");
   ```

### Mix in-app content with resources from your website

Your app might need to load a mix of in-app content and content from the
internet, such as an in-app HTML page styled by your website's CSS.
`WebViewAssetLoader` supports this use case. If none of the registered
`PathHandler` instances can find a resource for the given path, `WebView` falls
back to loading content from the internet. If you mix in-app content with
resources from your website, reserve directory paths, such as `/assets/` or
`/resources/`, for in-app resources. Avoid storing any resources from your
website in those locations.  

### Kotlin

```kotlin
val assetLoader = WebViewAssetLoader.Builder()
                        .setDomain("example.com") // Replace this with your website's domain.
                        .addPathHandler("/assets/", AssetsPathHandler(this))
                        .build()

webView.webViewClient = LocalContentWebViewClient(assetLoader)
val inAppHtmlUrl = "https://example.com/assets/index.html"
webView.loadUrl(inAppHtmlUrl)
val websiteUrl = "https://example.com/website/data.json"

// JavaScript code to fetch() content from the same origin.
val jsCode = "fetch('$websiteUrl')" +
        ".then(resp => resp.json())" +
        ".then(data => console.log(data));"

webView.evaluateJavascript(jsCode, null)
```

### Java

```java
final WebViewAssetLoader assetLoader = new WebViewAssetLoader.Builder()
           .setDomain("example.com") // Replace this with your website's domain.
           .addPathHandler("/assets/", new AssetsPathHandler(this))
           .build();

mWebView.setWebViewClient(new LocalContentWebViewClient(assetLoader));
String inAppHtmlUrl = "https://example.com/assets/index.html";
mWebView.loadUrl(inAppHtmlUrl);
String websiteUrl = "https://example.com/website/data.json";

// JavaScript code to fetch() content from the same origin.
String jsCode = "fetch('" + websiteUrl + "')" +
      ".then(resp => resp.json())" +
      ".then(data => console.log(data));";

mWebView.evaluateJavascript(jsCode, null);
```

See the [`WebView` demo on
GitHub](https://github.com/android/views-widgets-samples/tree/main/WebView)
for an example of an in-app HTML page fetching web-hosted JSON data.

## loadDataWithBaseURL

When your app only needs to load an HTML page and doesn't need to intercept
subresources, consider using
[`loadDataWithBaseURL()`](https://developer.android.com/reference/android/webkit/WebView#loadDataWithBaseURL(java.lang.String,%20java.lang.String,%20java.lang.String,%20java.lang.String,%20java.lang.String)),
which doesn't require app assets. You can use it as shown in the following code
sample:  

### Kotlin

```kotlin
val html = "<html><body><p>Hello world</p></body></html>"
val baseUrl = "https://example.com/"

webView.loadDataWithBaseURL(baseUrl, html, "text/html", null, baseUrl)
```

### Java

```java
String html = "<html><body><p>Hello world</p></body></html>";
String baseUrl = "https://example.com/";

mWebView.loadDataWithBaseURL(baseUrl, html, "text/html", null, baseUrl);
```

Choose argument values carefully. Consider the following:

- `baseUrl`: this is the URL your HTML content is loaded as. This must be an HTTP(S) URL.
- `data`: this is the HTML content you want to display, as a string.
- `mimeType`: this must usually be set to `text/html`.
- `encoding`: this is unused when `baseUrl` is an HTTP(S) URL, so it can be set to `null`.
- `historyUrl`: this is set to the same value as `baseUrl`.

We strongly recommend using an HTTP(S) URL as the `baseUrl`, as this helps
ensure your app complies with the same-origin policy.

If you can't find a suitable `baseUrl` for your content and prefer to use
[`loadData()`](https://developer.android.com/reference/android/webkit/WebView#loadData(java.lang.String,%20java.lang.String,%20java.lang.String)),
you **must encode the content** with
[percent-encoding](https://developer.mozilla.org/en-US/docs/Glossary/percent-encoding)
or
[Base64
encoding](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs#encoding_data_into_base64_format).
We strongly recommend choosing Base64 encoding and using Android APIs to encode
this programmatically, as shown in the following code sample:  

### Kotlin

```kotlin
val encodedHtml: String = Base64.encodeToString(html.toByteArray(), Base64.NO_PADDING)

webView.loadData(encodedHtml, mimeType, "base64")
```

### Java

```java
String encodedHtml = Base64.encodeToString(html.getBytes(), Base64.NO_PADDING);

mWebView.loadData(encodedHtml, mimeType, "base64");
```
| **Caution:** By default, `loadData()` expects the HTML data to be percent-encoded. Percent-encoding by hand is error prone, and there are no Android APIs to do this programmatically. We strongly recommend switching to `loadDataWithBaseURL()` to avoid this requirement or using Base64 APIs to encode the content, as shown in the preceding code sample.

## Things to avoid

There are several other ways to load in-app content, but we strongly recommend
against them:

- `file://` URLs and `data:` URLs are considered to be *opaque origins* , meaning they can't take advantage of powerful web APIs such as [`fetch()`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) or [`XMLHttpRequest`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest). `loadData()` internally uses `data:` URLs, so we encourage using `WebViewAssetLoader` or `loadDataWithBaseURL()` instead.
- Although [`WebSettings.setAllowFileAccessFromFileURLs()`](https://developer.android.com/reference/android/webkit/WebSettings#setAllowFileAccessFromFileURLs(boolean)) and [`WebSettings.setAllowUniversalAccessFromFileURLs()`](https://developer.android.com/reference/android/webkit/WebSettings#setAllowUniversalAccessFromFileURLs(boolean)) can work around the issues with `file://` URLs, we recommend against setting these to `true` because doing so leaves your app vulnerable to file-based exploits. We recommend explicitly setting these to `false` on all API levels for the strongest security.
- For the same reasons, we recommend against `file://android_assets/` and `file://android_res/` URLs. The `AssetsHandler` and `ResourcesHandler` classes are meant to be drop-in replacements.
- Avoid using [`MIXED_CONTENT_ALWAYS_ALLOW`](https://developer.android.com/reference/android/webkit/WebSettings#MIXED_CONTENT_ALWAYS_ALLOW). This setting generally isn't necessary and weakens the security of your app. We recommend loading your in-app content over the same scheme---HTTP or HTTPS---as your website's resources and using [`MIXED_CONTENT_COMPATIBILITY_MODE`](https://developer.android.com/reference/android/webkit/WebSettings#MIXED_CONTENT_COMPATIBILITY_MODE) or [`MIXED_CONTENT_NEVER_ALLOW`](https://developer.android.com/reference/android/webkit/WebSettings#MIXED_CONTENT_NEVER_ALLOW), as appropriate.