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 New → Folder → Assets Folder
-
Right-click on your assets folder, select New → File 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.
|
And voilá - That’s all it takes - You’re ready to scan now 👏🎉.
You can find the complete source code in our GitHub examples.