# Embedding forms in Kotlin

Native Android apps don't have a DOM to embed a `<script>` or `<iframe>` into, so a form is embedded with a `WebView` that loads the form's URL, or a small HTML wrapper that carries the [script embed](/embeds/script-embed).

## WebView embed

```kotlin
class CakeOrderFormActivity : AppCompatActivity() {
    private lateinit var webView: WebView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        webView = WebView(this)
        setContentView(webView)

        webView.settings.javaScriptEnabled = true
        webView.settings.domStorageEnabled = true
        webView.webChromeClient = WebChromeClient() // required for camera / file-upload fields
        webView.webViewClient = WebViewClient()

        webView.loadUrl("https://eu.forms.app/form/69d4bd130b443bda40c8f65a")
    }
}
```

- `javaScriptEnabled` and `domStorageEnabled` are both required; forms.app forms use JavaScript and local storage.
- `WebChromeClient` is needed for file-upload and camera-based fields to open a native picker inside the `WebView`.
- Request the runtime permissions your form's field types need (`CAMERA`, `RECORD_AUDIO`, `ACCESS_FINE_LOCATION`), matching the `allow` attribute used on the web [iframe embed](/embeds/iframe-embed), and grant `WebView` access to them in `onPermissionRequest` on your `WebChromeClient`.


### Manifest permissions

Only declare the ones your form's field types actually need:

```xml
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
```

## Passing data into the form

The form's `src` URL doesn't accept prefill query parameters directly; prefilling goes through the `answers` option of the script embed (see [Passing data to your form](/embeds/passing-form-data)). To use it from a native app, load a small HTML wrapper containing the script embed instead of loading the form URL directly:

```kotlin
@Composable
fun CakeOrderFormScreen(referralCode: String) {
    val safeReferral = remember(referralCode) {
        JSONObject.quote(referralCode) // escape for safe interpolation into the HTML string below
    }

    AndroidView(factory = { context ->
        WebView(context).apply {
            settings.javaScriptEnabled = true
            settings.domStorageEnabled = true
            webChromeClient = WebChromeClient()

            val html = """
                <html>
                  <body style="margin:0;">
                    <div data-formsapp-src="https://eu.forms.app/form/69d4bd130b443bda40c8f65a"></div>
                    <script src="https://cdn.formsapp.io/embed.js" async defer onload="
                      new formsapp('69d4bd130b443bda40c8f65a', 'standard',
                        {'width':'100vw','height':'formHeight','answers':{'63ebad419442ad0448b9e9b6':$safeReferral}},
                        'https://eu.forms.app');
                    "></script>
                  </body>
                </html>
            """.trimIndent()

            loadDataWithBaseURL("https://eu.forms.app", html, "text/html", "UTF-8", null)
        }
    })
}
```

Escape interpolated values
`referralCode` is being written directly into an HTML string that a `WebView` will execute as a page. Always escape or JSON-encode any value that comes from outside your app (a deep link, a previous screen, and so on) before interpolating it, to avoid injecting unintended HTML or script into the page.

`loadDataWithBaseURL`'s `baseUrl` argument is set to `https://eu.forms.app`, your account's data-region domain, so the loaded page resolves the embed script correctly. Match it to the domain shown on your form's Share page.

## What's next

- [Embed options](/embeds/embed-options) for every layout's settings.
- [Passing data to your form](/embeds/passing-form-data) for the `answers` option in full.