Skip to content
Last updated

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.

WebView embed

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, and grant WebView access to them in onPermissionRequest on your WebChromeClient.

Manifest permissions

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

<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). To use it from a native app, load a small HTML wrapper containing the script embed instead of loading the form URL directly:

@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