Skip to content

Integrating LIQA into Android app

Below is the guide for integrating LIQA into an Android app using Kotlin language.

WebView with LIQA integrated

Here is a ready-to-use implementation of WebView with integrated LIQA functionality.

Production recommendation

For the simplicity of the guide, WebView loads the HTML page from a string containing the page‘s HTML content.

In a real-world application, you may consider loading the page from a CDN managed by your organization. Check the Hosting the HTML Page section for more details.

IMPORTANT

WebView should be opened over HTTPS. Without HTTPS, the camera access will not be requested, and the camera cannot be used.

When hosting locally, you can open the WebView using https://localhost/ as the baseUrl

kotlin
loadDataWithBaseURL("https://localhost/", HTML, "text/html", "UTF-8", null)
loadDataWithBaseURL("https://localhost/", HTML, "text/html", "UTF-8", null)
kotlin
package ai.haut.liqa

import android.net.Uri
import android.webkit.*
import androidx.activity.ComponentActivity
import androidx.activity.result.contract.ActivityResultContracts
import org.json.JSONObject

const val HTML = """ PUT THE HTML PAGE CONTENT HERE """

fun LiqaWebView(context: ComponentActivity, onLiqaEvent: (name: String, payload: JSONObject?) -> Unit): WebView {
  return WebView(context).apply {
    webViewClient = WebViewClient()
    webChromeClient = object : WebChromeClient() {
      // Grant camera permissions to WebView
      override fun onPermissionRequest(request: PermissionRequest) {
        request.grant(request.resources)
      }

      // Handle Upload device photo
      override fun onShowFileChooser(
        webView: WebView?,
        filePathCallback: ValueCallback<Array<Uri>>?,
        fileChooserParams: FileChooserParams?
      ): Boolean {
        onFilePicked = filePathCallback
        filePicker.launch(fileChooserParams?.createIntent())
        return true
      }
      private var onFilePicked: ValueCallback<Array<Uri>>? = null
      private val filePicker = context.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
        val data = result.data?.data
        if (data != null)
          onFilePicked?.onReceiveValue(arrayOf(data))
      }
    }
    settings.apply {
      javaScriptEnabled = true
      mediaPlaybackRequiresUserGesture = false
    }

    // Communication interface between WebView‘s JavaScript and the Native App
    addJavascriptInterface(object {
      @JavascriptInterface
      fun postMessage(message: String) {
        val json = JSONObject(message)
        val name = json.getString("name")
        val payload = if (json.has("payload")) json.getJSONObject("payload") else null

        onLiqaEvent(name, payload)
      }
    }, "NativeApp")

    // `https://` is required to get WebView camera working
    loadDataWithBaseURL("https://YOUR.DOMAIN.COM", HTML, "text/html", "UTF-8", null)
  }
}
package ai.haut.liqa

import android.net.Uri
import android.webkit.*
import androidx.activity.ComponentActivity
import androidx.activity.result.contract.ActivityResultContracts
import org.json.JSONObject

const val HTML = """ PUT THE HTML PAGE CONTENT HERE """

fun LiqaWebView(context: ComponentActivity, onLiqaEvent: (name: String, payload: JSONObject?) -> Unit): WebView {
  return WebView(context).apply {
    webViewClient = WebViewClient()
    webChromeClient = object : WebChromeClient() {
      // Grant camera permissions to WebView
      override fun onPermissionRequest(request: PermissionRequest) {
        request.grant(request.resources)
      }

      // Handle Upload device photo
      override fun onShowFileChooser(
        webView: WebView?,
        filePathCallback: ValueCallback<Array<Uri>>?,
        fileChooserParams: FileChooserParams?
      ): Boolean {
        onFilePicked = filePathCallback
        filePicker.launch(fileChooserParams?.createIntent())
        return true
      }
      private var onFilePicked: ValueCallback<Array<Uri>>? = null
      private val filePicker = context.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
        val data = result.data?.data
        if (data != null)
          onFilePicked?.onReceiveValue(arrayOf(data))
      }
    }
    settings.apply {
      javaScriptEnabled = true
      mediaPlaybackRequiresUserGesture = false
    }

    // Communication interface between WebView‘s JavaScript and the Native App
    addJavascriptInterface(object {
      @JavascriptInterface
      fun postMessage(message: String) {
        val json = JSONObject(message)
        val name = json.getString("name")
        val payload = if (json.has("payload")) json.getJSONObject("payload") else null

        onLiqaEvent(name, payload)
      }
    }, "NativeApp")

    // `https://` is required to get WebView camera working
    loadDataWithBaseURL("https://YOUR.DOMAIN.COM", HTML, "text/html", "UTF-8", null)
  }
}

Before integrating the snippet into your app, please, ensure that:

Application integration example

Here is an app‘s entry point, showcasing a sample integration of LiqaWebView:

kotlin
package ai.haut.liqa

import android.Manifest
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.result.contract.ActivityResultContracts
import org.json.JSONObject
import android.widget.Toast

class MainActivity : ComponentActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    val webView = LiqaWebView(this) { name, payload ->
      handleLiqaEvent(name, payload)
    }

    // Request camera permission before loading WebView
    registerForActivityResult(ActivityResultContracts.RequestPermission()) {
      setContentView(webView)
    }.launch(Manifest.permission.CAMERA)
  }

  // The App‘s business logic for handling LIQA events
  private fun handleLiqaEvent(name: String, payload: JSONObject?) {
    var text = "LIQA: $name"

    payload?.let {
      var extars = "$payload"
      // Do not overwhelm the log
      if (extars.length >= 1024) extars = extars.substring(0, 1024) + "..."
      text += "\n$extars"
    }

    log(text)
  }

  private fun log(text: String) {
    Toast.makeText(this, text, Toast.LENGTH_SHORT).show()
  }
}
package ai.haut.liqa

import android.Manifest
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.result.contract.ActivityResultContracts
import org.json.JSONObject
import android.widget.Toast

class MainActivity : ComponentActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    val webView = LiqaWebView(this) { name, payload ->
      handleLiqaEvent(name, payload)
    }

    // Request camera permission before loading WebView
    registerForActivityResult(ActivityResultContracts.RequestPermission()) {
      setContentView(webView)
    }.launch(Manifest.permission.CAMERA)
  }

  // The App‘s business logic for handling LIQA events
  private fun handleLiqaEvent(name: String, payload: JSONObject?) {
    var text = "LIQA: $name"

    payload?.let {
      var extars = "$payload"
      // Do not overwhelm the log
      if (extars.length >= 1024) extars = extars.substring(0, 1024) + "..."
      text += "\n$extars"
    }

    log(text)
  }

  private fun log(text: String) {
    Toast.makeText(this, text, Toast.LENGTH_SHORT).show()
  }
}

Feel free to adapt the example for your‘s application needs.

Application permissions configuration

Additionally, there is an example of the minimal Android manifest file necessary for LiqaWebView to function correctly:

xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

  <uses-permission android:name="android.permission.INTERNET" />
  <uses-permission android:name="android.permission.CAMERA" />

  <uses-feature
      android:name="android.hardware.camera"
      android:required="false" />

  <application android:label="LIQA WebView integration">
    <activity
        android:name=".MainActivity"
        android:exported="true">
      <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
    </activity>
  </application>
</manifest>
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

  <uses-permission android:name="android.permission.INTERNET" />
  <uses-permission android:name="android.permission.CAMERA" />

  <uses-feature
      android:name="android.hardware.camera"
      android:required="false" />

  <application android:label="LIQA WebView integration">
    <activity
        android:name=".MainActivity"
        android:exported="true">
      <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
    </activity>
  </application>
</manifest>

Before running your app with LiqaWebView integrated, please, ensure that:

Need Assistance?

If you have any questions regarding the integration or need further assistance, please contact Haut.AI Support Team with this request via the Support Desk.