Quickstart(web view)

Android(JavaScript)

Using JavaScript SDK within the Android WebView requires some extra steps.

🚧

JavaScript SDK does not provide a push notification.

Use Android SDK instead of the JavaScript SDK to support a push notification. See the instructions of Android(SDK) section to follow the best practices.

Allow new tab

To allow new tab in an Android WebView, some additional configurations are required.

📘

Channel JavaScript SDK opens a user chat to a new tab.

To use Channel JavaScript SDK in a WebView, you should allow to open a new tab. If it is not a desired behavior, you may want to change the mobileMessengerMode field in the Boot Option.

webChromeClient = object : WebChromeClient() {
    override fun onCreateWindow(
        view: WebView?,
        isDialog: Boolean,
        isUserGesture: Boolean,
        resultMsg: Message?
    ): Boolean {
        // intercept new window
        val transport = resultMsg?.obj as? WebView.WebViewTransport
        val newWebView = WebView([email protected])

        view?.addView(newWebView)
        transport?.webView = newWebView
        resultMsg?.sendToTarget()

        newWebView.webViewClient = object : WebViewClient() {
            // handle url navigating and create new activity
            override fun shouldOverrideUrlLoading(
                    view: WebView,
                    url: String
            ): Boolean {
                return when {
                    url.startsWith("tel:") -> {
                        val intent = Intent(Intent.ACTION_DIAL, Uri.parse(url))
                        startActivity(intent)
                        true
                    }
                    url.startsWith("mailto:") -> {
                        val intent = Intent(Intent.ACTION_SENDTO, Uri.parse(url))
                        startActivity(intent)
                        true
                    }
                    else -> {
                        Intent(view.context, WebViewActivity::class.java)
                            .apply { putExtra("url", url) }
                            .also { intent -> startActivity(intent) }
                        true
                    }
                }
            }
        }

        return true
    }
}

Uploading files

If you are using Android WebView, users will not be able to launch a file chooser when they click the attachment button to send files. The problem is that WebView does not implement default file choosing behavior, so we must manually define how we handle the users' actions. With our simple solution and snippets, you will only take a minimal effort on integrating JavaScript SDK with your mobile application.

📘

We highly recommend using the Android SDK for those who are looking for effortless and straightforward experiences on mobile devices.

  1. Allow WebView to access files and set WebChromeClient to get notified when the user wants to open the file chooser.
yourWebViewInstance.apply {
    settings.apply {
        // -- your other WebView options --

        // add this line to make WebView to access file system
        allowFileAccess = true
    }

    webChromeClient = object : WebChromeClient() {
        override fun onShowFileChooser(
                webView: WebView,
                filePathCallback: ValueCallback<Array<Uri>>,
                fileChooserParams: FileChooserParams
        ): Boolean {
                // -- snip --
        }
}
  1. Copy our code of WebViewAttachmentModule class that is responsible for handling file chooser callbacks. Note that you may change the snippet as it is provided to help you write code faster.

🚧

Make sure to return true from onShowFileChooser

The WebChromeClient does not allow filePathCallback to be invoked if onShowFileChooser returns false. As the module always calls the callback when an intent for a file chooser is created, make sure to call getIntentChooser only if onShowFileChooser returns true.

class WebViewAttachmentModule {

    private var filePathCallback: ValueCallback<Array<Uri>>? = null

    fun getIntentChooser(filePathCallback: ValueCallback<Array<Uri>>, acceptTypes: Array<String>): Intent {
        // init filePathCallback
        this.filePathCallback?.onReceiveValue(null)
        this.filePathCallback = filePathCallback

        return Intent(Intent.ACTION_GET_CONTENT).apply {
            addCategory(Intent.CATEGORY_OPENABLE)
            type = "*/*"
            putExtra(Intent.EXTRA_MIME_TYPES, getMimeTypesFromAcceptTypes(acceptTypes))
        }
    }

    // handle onActivityResult process in activity
    fun handleResult(requestCode: Int, resultCode: Int, data: Intent?) {
        val uri = data?.data
        if (requestCode == WEB_VIEW_FILE_UPLOAD_REQUEST_CODE && resultCode == AppCompatActivity.RESULT_OK && uri != null) {
            filePathCallback?.onReceiveValue(arrayOf(uri))
        } else {
            filePathCallback?.onReceiveValue(null)
        }
        filePathCallback = null
    }

    private fun getMimeTypesFromAcceptTypes(acceptTypes: Array<String>): Array<String> {
        return (acceptTypes
                .mapNotNull { acceptType ->
                    when {
                        // Seperate case of extensions and MIME Type
                        acceptType.startsWith(".") -> MimeTypeMap.getSingleton().getMimeTypeFromExtension(acceptType.substring(1))
                        else -> acceptType
                    }
                }
                // setting "*/*" when acceptType is not exist
                .filter { it.isNotBlank() }
                .takeIf { it.isNotEmpty() }
                ?: listOf("*/*"))
                .toTypedArray()
    }
}

Usage sample

🚧

Please change the following sample code to fit your requirements.

class WebViewActivity : AppCompatActivity() {
  
    private val attachmentModule = WebViewAttachmentModule()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_web_view)

        webview.webViewClient = WebViewClient()

        val url = intent.getStringExtra("url") ?: SOME_DEFAULT_URL

        webview.apply {
            settings.apply {
                // WebView settings
                javaScriptEnabled = true
                domStorageEnabled = true
                useWideViewPort = true
                allowFileAccess = true

                setSupportMultipleWindows(true)
                javaScriptCanOpenWindowsAutomatically = true
            }

            webChromeClient = object : WebChromeClient() {
                // handle file uploads
                override fun onShowFileChooser(
                        webView: WebView,
                        filePathCallback: ValueCallback<Array<Uri>>,
                        fileChooserParams: FileChooserParams
                ): Boolean {
                    startActivityForResult(
                            attachmentModule.getIntentChooser(
                                    filePathCallback,
                                    fileChooserParams.acceptTypes,
                            ),
                            Const.WEB_VIEW_FILE_UPLOAD_REQUEST_CODE,
                    ) // Call getIntentChooser() only if the function returns true.
                    return true
                }
              
                override fun onCreateWindow(
                    view: WebView?,
                    isDialog: Boolean,
                    isUserGesture: Boolean,
                    resultMsg: Message?
                ): Boolean {
                    // intercept new window
                    val transport = resultMsg?.obj as? WebView.WebViewTransport
                    val newWebView = WebView([email protected])

                    view?.addView(newWebView)
                    transport?.webView = newWebView
                    resultMsg?.sendToTarget()

                    newWebView.webViewClient = object : WebViewClient() {
                        // handle URL navigation
                        override fun shouldOverrideUrlLoading(
                                view: WebView,
                                url: String
                        ): Boolean {
                            return when {
                                url.startsWith("tel:") -> {
                                    val intent = Intent(Intent.ACTION_DIAL, Uri.parse(url))
                                    startActivity(intent)
                                    true
                                }
                                url.startsWith("mailto:") -> {
                                    val intent = Intent(Intent.ACTION_SENDTO, Uri.parse(url))
                                    startActivity(intent)
                                    true
                                }
                                else -> {
                                    Intent(view.context, WebViewActivity::class.java)
                                        .apply { putExtra("url", url) }
                                        .also { intent -> startActivity(intent) }
                                    true
                                }
                            }
                        }
                    }

                    return true
                }
            }
        }

        webview.loadUrl(url)
    }
  
    // handle file upload
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        attachmentModule.handleResult(requestCode, resultCode, data)

        super.onActivityResult(requestCode, resultCode, data)
    }
}

Android(SDK)

Instead of embedding JavaScript SDK to the WebView, you might decide to use Android SDK along with the WebView. A major reason for the decision is when you need to send a push notification. See how to get started with the Android SDK and enabling the push notification.

❗️

Hide redundant Channel SDK UI.

When using both JavaScript SDK and Android SDK, set hideChannelButtonOnBoot and hidePopup=true in Boot Option of JavaScript SDK to hide the Channel button and popups. Android SDK and JavaScript SDK might duplicate Channel SDK UI.

To allow Android SDK to know the current URL of the WebView, call setPage with appropriate data. Event tracking and support bot targeting will work smoothly with the information.

webChromeClient = object : WebChromeClient() {
    override fun onCreateWindow(
        view: WebView?,
        isDialog: Boolean,
        isUserGesture: Boolean,
        resultMsg: Message?
    ): Boolean {
        // intercept new window
        val transport = resultMsg?.obj as? WebView.WebViewTransport
        val newWebView = WebView([email protected])

        view?.addView(newWebView)
        transport?.webView = newWebView
        resultMsg?.sendToTarget()

        newWebView.webViewClient = object : WebViewClient() {
            // handle url navigating and create new activity
            override fun shouldOverrideUrlLoading(
                    view: WebView,
                    url: String
            ): Boolean {
                return when {
                    url.startsWith("tel:") -> {
                        val intent = Intent(Intent.ACTION_DIAL, Uri.parse(url))
                        startActivity(intent)
                        true
                    }
                    url.startsWith("mailto:") -> {
                        val intent = Intent(Intent.ACTION_SENDTO, Uri.parse(url))
                        startActivity(intent)
                        true
                    }
                    else -> {
                        Intent(view.context, WebViewActivity::class.java)
                            .apply { putExtra("url", url) }
                            .also { intent -> startActivity(intent) }
                        true
                    }
                }
            }
        }

        return true
    }
}

iOS

This section describes how to use the JavaScript SDK installed on the mobile web in an iOS WebView environment.

🚧

Push Notification doesn’t work if you are using only JavaScript.

To use the push notification, Refers ChannelIO iOS SDK

The Following Example loads a page with a URL in your YOUR_WEB_URL on any WebView ViewController.

import UIKit
import WebKit

class ViewController: UIViewController, WKNavigationDelegate, WKUIDelegate {
  
  var webView: WKWebView!
  var url: String = "YOUR_WEB_URL"
  override func loadView() {
    let config = WKWebViewConfiguration()
    webView = WKWebView(frame: .zero, configuration: config)
    webView.uiDelegate = self
    view = webView
  }
  
  override func viewDidLoad() {
    super.viewDidLoad()
    
    let myURL = URL(string:self.url)
    let myRequest = URLRequest(url: myURL!)
    webView.load(myRequest)
  }
  
  // target blank handleing
  func webView(
    _ webView: WKWebView, 
    createWebViewWith configuration: WKWebViewConfiguration, 
    for navigationAction: WKNavigationAction, 
    windowFeatures: WKWindowFeatures) -> WKWebView? {
    if navigationAction.targetFrame == nil {
      webView.load(navigationAction.request)
    }
    return nil
  }
}

Using iOS modalPresent in a WebView (e.g present(webView)) may cause app crashes. Add the example below to solve this.

See Popover menu over cards containing WebKit views on iOS 13 for more information.

  1. Add below examples on the WebViewController.
import UIKit
import WebKit

class ViewController: UIViewController, WKNavigationDelegate, WKUIDelegate, UIPopoverPresentationControllerDelegate, UIGestureRecognizerDelegate {
  
  var webView: WKWebView!
  var url: String = ""
  var lastTapPosition: CGPoint = CGPoint(x: 0, y: 0)
  
  override func loadView() {
    let config = WKWebViewConfiguration()
    webView = WKWebView(frame: .zero, configuration: config)
    webView.uiDelegate = self
    view = webView
    
    let tapGesture = UITapGestureRecognizer(target: self, action: #selector(webViewTapped(_:)))
    tapGesture.delegate = self
    webView.addGestureRecognizer(tapGesture)
  }
  
  override func viewDidLoad() {
    super.viewDidLoad()
    
    let myURL = URL(string:self.url)
    let myRequest = URLRequest(url: myURL!)
    webView.load(myRequest)
  }
  
  // target blank handleing
  func webView(
    _ webView: WKWebView, 
    createWebViewWith configuration: WKWebViewConfiguration, 
    for navigationAction: WKNavigationAction, 
    windowFeatures: WKWindowFeatures) -> WKWebView? {
    if navigationAction.targetFrame == nil {
      webView.load(navigationAction.request)
    }
    return nil
  }
  
  // gesture handling
  func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
    return true
  }
  
  @objc
  func webViewTapped(_ sender: UITapGestureRecognizer) {
    self.lastTapPosition = sender.location(in: self.view)
  }
  
  override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
    setUIDocumentMenuViewControllerSoureViewsIfNeeded(viewControllerToPresent)
    super.present(viewControllerToPresent, animated: flag, completion: completion)
  }

  func setUIDocumentMenuViewControllerSoureViewsIfNeeded(_ viewControllerToPresent: UIViewController) {
    if #available(iOS 13, *), viewControllerToPresent is UIDocumentMenuViewController && UIDevice.current.userInterfaceIdiom == .phone {
      // Prevent the app from crashing if the WKWebView decides to present a UIDocumentMenuViewController while it self is presented modally.
      viewControllerToPresent.popoverPresentationController?.sourceView = webView
      viewControllerToPresent.popoverPresentationController?.sourceRect = CGRect(origin: self.lastTapPosition, size: CGSize(width: 0, height: 0))
    }
  }
}
  1. Add the code below where you create and call the WebView ViewController above.
override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
  if let webVC = viewControllerToPresent as? ViewController {
    webVC.setUIDocumentMenuViewControllerSoureViewsIfNeeded(viewControllerToPresent)
  }
  super.present(viewControllerToPresent, animated: flag, completion: completion)
}