Implementing the Anyline Plugin

This section walks you through the steps in order to set up a view and work with Anyline plugins for your Android application.

Overview

The examples provided here are based on a simple meter scanning use case. A more detailed discussion can be found in the next section, as well in the API Reference.

Adding the ScanView to your Activity

Let’s create a new blank activity in your project and call it "ScanActivity".

The first step is to add the ScanView to the layout file of your Activity in order to embed it into the view. In our case, we’ll call it "scan_view".

<?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"
    tools:context=".ScanActivity">

    <io.anyline2.view.ScanView
        android:id="@+id/scan_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

In the activity class file (ScanActivity.kt), You’ll now have access to the ScanView and are almost ready to configure your scanner.

Before we jump into the code, let’s first add a config file to your project.

  • If your module does not have an assets folder yet, right-click on your module, select NewFolderAssets Folder

  • Right-click on your assets folder, select NewFile and call it "meter_config.json"

Open the file and add the following code:

{
  "cameraConfig": {
    "captureResolution": "1080p"
  },
  "flashConfig": {
    "mode": "manual",
    "alignment": "top_right"
  },
  "viewPluginConfig": {
    "pluginConfig": {
      "id": "MyMeterPlugin",
      "meterConfig": {
        "scanMode": "auto_analog_digital_meter"
      },
      "cancelOnResult": true
    },
    "cutoutConfig": {
      "alignment": "top",
      "strokeWidth": 2,
      "strokeColor": "FFFFFF",
      "cornerRadius": 4,
      "outerColor": "000000",
      "outerAlpha": 0.5,
      "feedbackStrokeColor": "0099FF",
      "width": 550,
      "maxWidthPercent": "90%",
      "maxHeightPercent": "90%",
      "ratioFromSize": {
        "width": 2.25,
        "height": 1
      }
    },
    "scanFeedbackConfig": {
      "style": "contour_rect",
      "strokeColor": "0099FF",
      "strokeWidth": 2,
      "fillColor": "220099FF",
      "cornerRadius": 2,
      "redrawTimeout": 200,
      "animationDuration": 75,
      "blinkAnimationOnResult": true,
      "beepOnResult": true,
      "vibrateOnResult": true
    }
  }
}

You’ll find what each of the parameters means in the View Configuration section.

Initialize the ScanView

By this time, you should already have initialized the Anyline SDK with the license key for your application. For more details, please see Initialize the Anyline SDK.

Now it’s time to initalize the ScanView with that configuration.

It is extremely important to wait for ScanView to finish loading before initializing it with any configuration. Therefore, we recommend that the event onScanViewLoaded be activated right after the ScanView constructor, before any other operation.

In ScanActivity, override the onCreate method as follows:

  • Kotlin

  • Java

class ScanActivity : AppCompatActivity() {
    private lateinit var binding: ActivityScanBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityScanBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.scanView.setOnScanViewLoaded { scanViewLoadResult ->
            when (scanViewLoadResult) {
                is ScanViewLoadResult.Succeeded -> {
                    try {
                        binding.scanView.init("meter_config.json")
                        //once the scan view is loaded and initialized, we can hook on the plugin events
                        binding.scanView.scanViewPlugin.resultReceived = Event { scanResult ->
                            //handle received scanResult
                            onResult(scanResult)
                        }
                        binding.scanView.start()
                    } catch (e: Exception) {
                        Log.e("ScanViewLoadResult", "ScanView could not be initialized: ${e.message}")
                    }
                }
                is ScanViewLoadResult.Failed -> {
                    Log.e("ScanViewLoadResult", "ScanView could not be loaded: ${scanViewLoadResult.getErrorMessage()}")
                }
            }
        }
    }
}
public class ScanActivity extends AppCompatActivity {
    private ActivityScanBinding binding = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = ActivityScanBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        binding.scanView.setOnScanViewLoaded(scanViewLoadResult -> {
            if (scanViewLoadResult instanceof ScanViewLoadResult.Succeeded) {
                try {
                    binding.scanView.init("meter_config.json");
                    //once the scan view is loaded and initialized, we can hook on the plugin events
                    binding.scanView.getScanViewPlugin().resultReceived = scanResult -> {
                        //handle received scanResult
                        onResult(scanResult);
                    };
                    binding.scanView.start();

                } catch (Exception e) {
                    Log.e("ScanViewLoadResult", "ScanView could not be initialized: " + e.getMessage());
                    throw new RuntimeException(e);
                }
            }
            else if (scanViewLoadResult instanceof ScanViewLoadResult.Failed) {
                ScanViewLoadResult.Failed scanViewLoadFailed = (ScanViewLoadResult.Failed) scanViewLoadResult;
                Log.e("ScanViewLoadResult", "ScanView could not be loaded: " + scanViewLoadFailed.getErrorMessage());
            }
        });
    }
}

This will instantiate & configure all necessary objects and attach them to the ScanView automatically.

If you call scanView.scanViewPlugin, you’ll get the instance of your configured ScanViewPlugin. This property gives you a ViewPluginBase.

Handling Results

  • Kotlin

  • Java

private fun onResult(scanResult: ScanResult) {
    // do something with the result image
    val bitmap = scanResult.image.bitmap

    // do something with the meter result
    val meterScanResult = scanResult.pluginResult.meterResult

    // do something with the result as a JSON object
    val json = scanResult.result
}
private void onResult(ScanResult scanResult) {
    // do something with the result image
    Bitmap bitmap = scanResult.getImage().getBitmap();

    // do something with the meter result
    MeterResult meterScanResult = scanResult.getPluginResult().getMeterResult();

    // do something with the result as a JSON object
    JSONObject json = scanResult.getResult();
}
The ScanResult instance is a memory object intrinsically linked to and dependent on the SDK. You should not retain this instance beyond the onResult() call. If you wish to retain the information (in memory or in storage), you must convert it to an ExportedScanResult object.
  • Kotlin

  • Java

private fun onResult(scanResult: ScanResult) {
    val exportedImageParameters = ExportedScanResultImageParameters().apply {
        format = ExportedScanResultImageParameters.ExportedScanResultImageFormat.PNG
        quality = 100
    }
    if (saveToDisk) {
        val exportedScanResultWithSavedImagesJson = scanResult.toExportedScanResultWithSavedImages(
            saveToFolder = this.filesDir,
            imageParameters = exportedImageParameters
        ).toJsonObject()
        Log.d("exportedScanResultJson", exportedScanResultWithSavedImagesJson.toString())
        /*
        {
            "pluginResult": {..},
            "imageParameters": {"format":"png","quality":100},
            "imageContainer": {
                "saved":{
                    "images":{
                        "cutoutImage":"cutoutImage1739346104471.png",
                        "image":"image1739346104471.png"
                    },
                    "path":"\/data\/user\/0\/com.anyline.examples\/files\/"
                }
            }
        }
        */
    } else {
        val exportedScanResultWithEncodedImagesJson = scanResult.toExportedScanResultWithBase64EncodedImages(
            imageParameters = exportedImageParameters
        ).toJsonObject()
        Log.d("exportedScanResultJson", exportedScanResultWithEncodedImagesJson.toString())
        /*
        {
            "pluginResult": {..},
            "imageParameters": {"format":"png","quality":100},
            "imageContainer": {
                "encoded":{
                    "images":{
                        "cutoutImage":"iVBORw0K...AElFTkSuQmCC",
                        "image":"iVBORw0K...RU5ErkJggg=="
                    }
                }
            }
        }
        */
    }
}
private void onResult(ScanResult scanResult) {
    ExportedScanResultImageParameters exportedImageParameters = new ExportedScanResultImageParameters();
    exportedImageParameters.setFormat(ExportedScanResultImageParameters.ExportedScanResultImageFormat.PNG);
    exportedImageParameters.setQuality(100);
    if (saveToDisk) {
        try {
            ExportedScanResult exportedScanResultWithSavedImages = scanResult.toExportedScanResultWithSavedImages(
                    this.getFilesDir(),
                    exportedImageParameters);
            JSONObject exportedScanResultWithSavedImagesJson =
                    ExportedScanResultExtensionKt.toJsonObject(exportedScanResultWithSavedImages);
            Log.d("exportedScanResultJson", exportedScanResultWithSavedImagesJson.toString());
            /*
            {
                "pluginResult": {..},
                "imageParameters": {"format":"png","quality":100},
                "imageContainer": {
                    "saved":{
                        "images":{
                            "cutoutImage":"cutoutImage1739346104471.png",
                            "image":"image1739346104471.png"
                        },
                        "path":"\/data\/user\/0\/com.anyline.examples\/files\/"
                    }
                }
            }
            */
        } catch (IOException e) {}

    } else {
        ExportedScanResult exportedScanResultWithEncodedImages = scanResult.toExportedScanResultWithBase64EncodedImages(
                exportedImageParameters);
        JSONObject exportedScanResultWithEncodedImagesJson =
                ExportedScanResultExtensionKt.toJsonObject(exportedScanResultWithEncodedImages);
        Log.d("exportedScanResultJson", exportedScanResultWithEncodedImagesJson.toString());
        /*
        {
            "pluginResult": {..},
            "imageParameters": {"format":"png","quality":100},
            "imageContainer": {
                "encoded":{
                    "images":{
                        "cutoutImage":"iVBORw0K...AElFTkSuQmCC",
                        "image":"iVBORw0K...RU5ErkJggg=="
                    }
                }
            }
        }
        */
    }
}

Start scanning

A common scenario is to start the plugin on activity lifecycle resumed state.

override fun onResume() {
    super.onResume()
    if (scanView.isInitialized) {
        //start scanning on Activity resume
        scanView.start()
    }
}
Final things

Once your Activity is paused, you want to consider cleaning up resources. This includes closing the camera and more importantly, stoping the ScanView.

override fun onPause() {
    if (scanView.isInitialized) {
        //stop scanning on Activity pause
        scanView.stop()
    }
    super.onPause()
}

And voilá - That’s all it takes - You’re ready to scan now 👏🎉.

You can find the complete source code in our GitHub examples.