Android
Integration in a 100% native Android app requires setting up a separate activity that contains the WebView where GetID (IDScan) will load from a custom page on your website.
This guide will walk you through setting the WebView app and make sure your app has direct connection to GetID and can receive events and data from it.
1. Create and host an html page for loading GetID (IDScan)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>GetID</title>
<!-- Load the Checkin.com library -->
<script src="https://[partner].regily.com/[key].js" async></script>
</head>
<body>
<script>
// Trigger GetID on window load
window.onCheckinLoad = (sdk) => {
sdk.settings.setLang('en')
sdk.signUp.open()
sdk.dataFlow.setOnComplete(async ({ data, completeData }) => {
// Process and send the data to your back-end here
console.log('Complete captured', { data })
// Send a request to your backend to save the data
const status = await saveDataToBackend(data)
if (status === 'OK') {
// redirect user on successful
} else {
// If something goes wrong you can display an error towards the user on your end or redirect the user
}
// Triggers checkinComplete on Android to close the WebView
window.Android.checkinComplete()
})
}
function saveDataToBackend(data) {
return new Promise(function (resolve, reject) {
// Simulated saveDataToBackend
setTimeout(resolve, 2000)
})
}
</script>
</body>
</html>
Method setOnComplete
is called when the flow ends and it can be used to handle the user data.
For more information on handling data, please see GetID (Standalone)
2. Create a new Android Activity
Create a new activity called WebView for instance. This activity is responsible for loading GetID inside a fullscreen webview, and listening to flow events:
package com.example.checkin_webview_integration;
import androidx.activity.result.ActivityResult;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.icu.text.SimpleDateFormat;
import android.os.Bundle;
import android.os.Handler;
import android.provider.Settings;
import android.webkit.JavascriptInterface;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebChromeClient;
import android.webkit.PermissionRequest;
import android.widget.Toast;
import android.webkit.ValueCallback;
import android.content.Intent;
import java.io.*;
import org.json.JSONException;
import org.json.JSONObject;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider;
import android.net.Uri;
import android.app.AlertDialog;
import android.content.DialogInterface;
import java.util.Locale;
public class WebActivity extends AppCompatActivity {
private ActivityResultLauncher<Intent> mFileChooser;
private ValueCallback<Uri[]> mediaCallback;
private static final String filename = "yyyy-MM-dd-HH-mm-ss-SSS";
private Uri imageUri;
private WebView webView;
private static final int PERMISSION_REQUEST_CODE = 1;
private static int PERMISSION_DENIED_COUNT = 0;
private static final String[] PERMISSIONS = {
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.CAMERA
};
private ActivityResultLauncher<Intent> getMediaChooserResult = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(), result -> parseMediaChooserResult(result)
);
private ActivityResultLauncher<Uri> getCameraResult = registerForActivityResult(
new ActivityResultContracts.TakePicture(), result -> {
if (result != null) { parseCameraResult(result); }
}
);
// Activity on Create
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_web);
WebView myWebView = (WebView) findViewById(R.id.webview);
myWebView.setWebChromeClient(new MyWebChromeClient(WebActivity.this, this.mFileChooser, myWebView));
if (!hasPermissions(PERMISSIONS)) {
ActivityCompat.requestPermissions(WebActivity.this, PERMISSIONS, PERMISSION_REQUEST_CODE);
}
// Enable JavaScript on the WebView
WebSettings webSettings = myWebView.getSettings();
webSettings.setJavaScriptEnabled(true);
// Set this Activity as a JavaScript interface for events handling
myWebView.addJavascriptInterface(this, "Android");
// Load GetID with Android support (Change link!)
myWebView.loadUrl("https://partner.com/checkin-getid/index.html");
}
// Custom WebChromeClient Class
private class MyWebChromeClient extends WebChromeClient {
private WebActivity webActivity;
private ActivityResultLauncher mFileChooser;
private WebView myWebView;
private static final int PERMISSION_REQUEST_CODE = 1;
final Handler handler = new Handler();
// To refresh camera preview on selfie & liveness screens
private Runnable runnable = new Runnable() {
@Override
public void run() {
myWebView.invalidate();
handler.postDelayed(this, 33); // Refresh at 30fps (1000ms / 30fps = 33.33ms per frame)
}
};
public MyWebChromeClient(WebActivity webActivity, ActivityResultLauncher mFileChooser, WebView myWebView) {
this.webActivity = webActivity;
this.mFileChooser = mFileChooser;
this.myWebView = myWebView;
}
// Handles permissions request event originating from within the custom WebChromeClient
@Override
public void onPermissionRequest(final PermissionRequest request) {
try {
webActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
final String[] requestedResources = request.getResources();
Context context = WebActivity.this;
// Rechecks if permissions are available and regrants it on the UI thread as required by Android, with a failsafe to prompt user to grant permissions with input
if (!hasPermissions(PERMISSIONS)) {
ActivityCompat.requestPermissions(WebActivity.this, PERMISSIONS, PERMISSION_REQUEST_CODE);
}
if (ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale(WebActivity.this, Manifest.permission.CAMERA)) {
AlertDialog.Builder builder = new AlertDialog.Builder(webActivity);
builder
.setTitle("Camera permission required")
.setMessage("The camera is required to advance in the process, please grant this permission.")
.setPositiveButton("OK", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
request.grant(requestedResources);
}
})
.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
request.deny();
}
});
AlertDialog dialog = builder.create();
dialog.show();
}
} else {
request.grant(requestedResources);
}
handler.post(runnable);
}
});
} catch (Exception exception) {
System.out.println(exception);
}
}
// Handles file chooser call event
@Override
public boolean onShowFileChooser(
WebView webView, ValueCallback<Uri[]> filePathCallback, WebChromeClient.FileChooserParams fileChooserParams) {
mediaCallback = filePathCallback;
Context context = WebActivity.this;
try {
if (fileChooserParams.isCaptureEnabled() == true) { // When using camera on document scanning
String name = new SimpleDateFormat(filename, Locale.US).format(System.currentTimeMillis());
imageUri = FileProvider.getUriForFile(
context,
context.getApplicationContext().getPackageName() + ".fileprovider",
new File(context.getCacheDir(), name + ".jpeg")
);
if (imageUri != null) { getCameraResult.launch(imageUri); }
} else { // When using gallery on document scanning
if (mFileChooser == null) {
mFileChooser = getMediaChooserResult;
webActivity.setmFileChooser(mFileChooser);
}
// Launch the file chooser
mFileChooser.launch(fileChooserParams.createIntent());
}
} catch (Exception exception) {
System.out.println(exception);
}
return true;
}
}
// Handles the result of permission requests
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == PERMISSION_REQUEST_CODE) {
if (!(grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED
&& grantResults[1] == PackageManager.PERMISSION_GRANTED
&& grantResults[2] == PackageManager.PERMISSION_GRANTED)) {
// Permission denied
handlePermissionsDenied();
}
// If permission is granted, no action is needed as the user will be able to go through the flow
}
}
// This prevents chromium from crashing
@Override
public void onDestroy() {
super.onDestroy();
webView=null;
}
// Check if the app has required permissions
private boolean hasPermissions(String[] permissions) {
for (String permission : permissions) {
if (ContextCompat.checkSelfPermission(WebActivity.this, permission)
!= PackageManager.PERMISSION_GRANTED) {
return false;
}
}
return true;
}
// Handler for file chooser result on document scanning
private void parseMediaChooserResult(ActivityResult result) {
if (mediaCallback != null) {
switch (result.getResultCode()) {
case RESULT_OK:
if (result.getData() != null) {
Uri uriResult = result.getData().getData();
if (uriResult != null) {
mediaCallback.onReceiveValue(new Uri[] { uriResult });
} else {
mediaCallback.onReceiveValue(new Uri[0]);
}
} else {
mediaCallback.onReceiveValue(new Uri[0]);
}
break;
default:
mediaCallback.onReceiveValue(new Uri[0]);
break;
}
}
}
// Handler for camera result on document scanning
private void parseCameraResult(boolean result) {
if (mediaCallback != null) {
if (result) {
if (imageUri != null) {
mediaCallback.onReceiveValue(new Uri[] { imageUri });
} else {
mediaCallback.onReceiveValue(new Uri[0]);
}
} else {
mediaCallback.onReceiveValue(new Uri[0]);
}
}
}
// Handler for when the user denies permissions request
private void handlePermissionsDenied() {
AlertDialog.Builder builder = new AlertDialog.Builder(WebActivity.this);
if(PERMISSION_DENIED_COUNT != 0 || permissionRationalesFalse()) {
// Show alert that redirects user to app settings to give permissions manually
builder
.setTitle("Permissions settings")
.setMessage("Access to camera and gallery are required to advance in the process, please go to the app settings to enable this permission.")
.setPositiveButton("OK", (dialog, id) -> goToAppSettings())
.setNegativeButton("Cancel", (dialog, id) -> WebActivity.this.finish());
AlertDialog dialog = builder.create();
dialog.show();
} else {
// Show alert that prompts user to give permissions again if they refuse the 1st time
builder
.setTitle("Permissions required")
.setMessage("Access to camera and gallery are required to advance in the process, please grant these permissions.")
.setPositiveButton("OK", (dialog, id) -> {
ActivityCompat.requestPermissions(WebActivity.this, PERMISSIONS, PERMISSION_REQUEST_CODE);
})
.setNegativeButton("Cancel", (dialog, id) -> {
WebActivity.this.finish();
});
AlertDialog dialog = builder.create();
dialog.show();
PERMISSION_DENIED_COUNT++;
}
}
// Navigate to the app settings
private void goToAppSettings() {
Intent intent = new Intent();
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", getPackageName(), null);
intent.setData(uri);
startActivity(intent);
}
// Checks if user has denied permissions twice. Android doesn't allow apps to ask for permissions more than 2 times
private boolean permissionRationalesFalse() {
for (String permission : PERMISSIONS) {
if (!ActivityCompat.shouldShowRequestPermissionRationale(WebActivity.this, permission)) {
return true;
}
}
return false;
}
public void setmFileChooser(ActivityResultLauncher<Intent> mFileChooser) {
this.mFileChooser = mFileChooser;
}
// Handler for Module state events. E.g. open/close
@JavascriptInterface
public void onEvent(String eventData) throws JSONException {
JSONObject data = new JSONObject(eventData);
switch (data.getString("action")) {
case "open-module":
System.out.println("open-module");
break;
case "close-module":
System.out.println("close-module");
break;
}
}
@JavascriptInterface
public void checkinComplete() throws JSONException {
System.out.println("checkinComplete");
this.finish();
}
}
3. Add a Webview to your Activity layout file
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context=".WebActivity">
<WebView
android:id="@+id/webview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
4. Create a custom file provider
Create a new class file CustomFileProvider.java
package com.example.checkin_webview_integration;
import androidx.core.content.FileProvider;
public class CustomFileProvider extends FileProvider {}
Then create provider_paths.xml
inside res/xml
directory
<?xml version="1.0" encoding="utf-8"?>
<paths>
<cache-path
name="cache_path"
path="." />
<external-path
name="external"
path="." />
<external-files-path
name="external_files"
path="." />
<external-cache-path
name="external_cache"
path="." />
<files-path
name="files"
path="." />
</paths>
5. Update your AndroidManifest
Remove ActionBar from your Webview Activity
This is one of the things you can do do make GetID look better without distraction from the ActionBar. Other things you can do is making your Activity transparent so the original app content is still visible behind GetID. Also adding configChanges
prevents the webview from reloading when user rotates their device.
Modify your new Activity in the AndroidManifest.xml
file to look like this:
<activity android:name=".WebActivity" android:exported="false" android:configChanges="orientation|screenSize" />
Give your app permission to access the internet, files and camera
This can be done by adding the following to your AndroidManifest.xml
file
<manifest ... >
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RESOURCE_VIDEO_CAPTURE"/>
<uses-permission android:name="android.permission.RESOURCE_AUDIO_CAPTURE"/>
...
</manifest>
Add custom file provider config inside application
<application ... >
<provider
android:name=".CustomFileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
...
</application>
6. Launch GetID (IDScan) with a button tap!
Put a button you want to use to open GetID in one of your Activities
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="openGetID"
android:text="GetID" />
Then add a openGetID
function to open GetID in the new activity
public void openGetID(View view) {
Intent intent = new Intent(this, WebActivity.class);
startActivity(intent);
}
Updated almost 2 years ago