# Building the ScanMRZ Demo App

The [MRZ Scanner User Guide](https://www.dynamsoft.com/mrz-scanner/docs/mobile/programming/android/user-guide/index.md) builds **ScanMRZBasic**, the smallest app that scans an MRZ and shows the parsed data on a single screen. This page builds **ScanMRZ**, the complete demo app on top of it.

> [!NOTE]
> The **MRZ Scanner Demo** published on Google Play is this same implementation behind a more polished landing screen. Building `ScanMRZ` gives you the full scanning and result experience; the store app adds branding around it.

Everything on this page is presentation. It uses the same SDK calls the user guide covers — `MRZScannerConfig`, `MRZScannerActivity.ResultContract`, and the getters on `MRZScanResult` — and adds a result screen around them. None of it is required in order to use the MRZ Scanner.

> [!NOTE]
> Full source on GitHub: [ScanMRZ](https://github.com/Dynamsoft/mrz-scanner-mobile/tree/main/android/samples/ScanMRZ) (Java) and [ScanMRZKt](https://github.com/Dynamsoft/mrz-scanner-mobile/tree/main/android/samples/ScanMRZKt) (Kotlin). Open **`android/samples`** in Android Studio and choose the matching run configuration.

## What ScanMRZ adds

| | ScanMRZBasic | ScanMRZ |
| --- | --- | --- |
| Screens | one activity | `MainActivity` plus a dedicated `ResultActivity` |
| Result data | seven fields, plain text | full field set, grouped into personal and document sections |
| Images | portrait only | portrait, plus a tabbed pager for the processed and original document images on both sides |
| Failed validation | field turns amber | amber, an error icon, an underline, and a tap-to-explain dialog |
| Camera denial | error string | error string plus an **Open Settings** action and recovery on resume |

## The result screen layouts

### activity_results.xml

Create **activity_results.xml** in **src/main/res/layout/**. This layout contains the scrollable result area, an error text view for exception states, a header with the portrait and key identity details, a tab-based image pager for document photos, detailed personal and document info fields, the raw MRZ text, and the action buttons:

```xml
<?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:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ResultActivity">

    <TextView
        android:id="@+id/no_result_view"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:visibility="gone"
        android:gravity="center"
        android:textSize="16sp"
        app:layout_constraintBottom_toTopOf="@+id/ll_buttons"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <ScrollView
        android:id="@+id/result_view"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:visibility="gone"
        app:layout_constraintBottom_toTopOf="@+id/ll_buttons"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:padding="16dp">

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:minHeight="100dp"
                android:orientation="horizontal">

                <LinearLayout
                    android:layout_width="0dp"
                    android:layout_height="match_parent"
                    android:layout_weight="4"
                    android:gravity="center_vertical"
                    android:orientation="vertical"
                    android:paddingVertical="8dp">

                    <TextView
                        android:id="@+id/tv_full_name"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:textSize="24sp"
                        android:textStyle="bold" />

                    <TextView
                        android:id="@+id/tv_gender_and_age"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:textSize="14sp" />

                    <TextView
                        android:id="@+id/tv_expiry"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:paddingTop="4dp"
                        android:textSize="14sp" />
                </LinearLayout>

                <View
                    android:layout_width="8dp"
                    android:layout_height="match_parent" />

                <ImageView
                    android:id="@+id/iv_portrait"
                    android:layout_width="0dp"
                    android:layout_height="match_parent"
                    android:layout_weight="1.2"
                    android:contentDescription="Portrait"
                    android:src="@drawable/ic_portrait_placeholder" />

            </LinearLayout>

            <TextView
                android:id="@+id/tv_images_header"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:paddingTop="24dp"
                android:textColor="@color/white"
                android:textStyle="bold"
                android:visibility="gone" />

            <com.google.android.material.tabs.TabLayout
                android:id="@+id/tab_images"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="24dp"
                app:tabGravity="center"
                app:tabMode="fixed" />

            <androidx.viewpager2.widget.ViewPager2
                android:id="@+id/vp_images"
                android:layout_width="match_parent"
                android:layout_height="162dp"
                android:layout_marginTop="8dp"
                android:overScrollMode="never" />

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:paddingTop="24dp"
                android:text="Personal Info"
                android:textStyle="bold" />

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:gravity="center_vertical"
                android:orientation="horizontal"
                android:paddingTop="8dp">

                <TextView
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:text="Given Name" />

                <TextView
                    android:id="@+id/tv_given_name"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:textStyle="bold" />
            </LinearLayout>

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:gravity="center_vertical"
                android:orientation="horizontal"
                android:paddingTop="8dp">

                <TextView
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:text="Surname" />

                <TextView
                    android:id="@+id/tv_surname"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:textStyle="bold" />
            </LinearLayout>

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:gravity="center_vertical"
                android:orientation="horizontal"
                android:paddingTop="8dp">

                <TextView
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:text="Date of Birth" />

                <TextView
                    android:id="@+id/tv_date_of_birth"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:textStyle="bold" />
            </LinearLayout>

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:gravity="center_vertical"
                android:orientation="horizontal"
                android:paddingTop="8dp">

                <TextView
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:text="Gender" />

                <TextView
                    android:id="@+id/tv_gender"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:textStyle="bold" />
            </LinearLayout>

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:gravity="center_vertical"
                android:orientation="horizontal"
                android:paddingTop="8dp">

                <TextView
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:text="Nationality" />

                <TextView
                    android:id="@+id/tv_nationality"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:textStyle="bold" />
            </LinearLayout>

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:paddingTop="24dp"
                android:text="Document Info"
                android:textStyle="bold" />

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:gravity="center_vertical"
                android:orientation="horizontal"
                android:paddingTop="8dp">

                <TextView
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:text="Doc. Type" />

                <TextView
                    android:id="@+id/tv_doc_type"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:textStyle="bold" />
            </LinearLayout>

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:gravity="center_vertical"
                android:orientation="horizontal"
                android:paddingTop="8dp">

                <TextView
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:text="Doc. Number" />

                <TextView
                    android:id="@+id/tv_doc_number"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:textStyle="bold" />
            </LinearLayout>

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:gravity="center_vertical"
                android:orientation="horizontal"
                android:paddingTop="8dp">

                <TextView
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:text="Expiry Date" />

                <TextView
                    android:id="@+id/tv_expiry_date"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:textStyle="bold" />
            </LinearLayout>

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:paddingTop="24dp"
                android:text="Raw MRZ Text"
                android:textStyle="bold" />

            <TextView
                android:id="@+id/tv_raw_mrz"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:fontFamily="monospace"
                android:paddingTop="8dp"
                android:paddingBottom="16dp" />

        </LinearLayout>
    </ScrollView>

    <LinearLayout
        android:id="@+id/ll_buttons"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:padding="16dp"
        android:layout_marginBottom="16dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent">

        <androidx.appcompat.widget.AppCompatButton
            android:id="@+id/btn_rescan"
            android:layout_width="0dp"
            android:layout_height="48dp"
            android:layout_weight="1"
            android:text="Re-Scan" />

        <!-- Hidden by default. Takes the Re-Scan slot when the scan failed because
             camera access is unavailable — see Step 8. -->
        <androidx.appcompat.widget.AppCompatButton
            android:id="@+id/btn_open_settings"
            android:layout_width="0dp"
            android:layout_height="48dp"
            android:layout_weight="1"
            android:visibility="gone"
            android:text="Open Settings" />

        <androidx.appcompat.widget.AppCompatButton
            android:id="@+id/btn_return_home"
            android:layout_width="0dp"
            android:layout_height="48dp"
            android:layout_weight="1"
            android:text="Return Home" />

    </LinearLayout>

</androidx.constraintlayout.widget.ConstraintLayout>
```

### ic_portrait_placeholder.xml

Create **ic_portrait_placeholder.xml** in **src/main/res/drawable/**. This vector drawable is shown as the portrait fallback when no portrait image was captured:

```xml
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="88dp"
    android:height="88dp"
    android:viewportWidth="88"
    android:viewportHeight="88">
    <path
        android:pathData="M79.382,75.625C79.141,76.043 78.794,76.39 78.375,76.632C77.957,76.873 77.483,77 77,77H11C10.517,76.999 10.044,76.872 9.626,76.631C9.208,76.389 8.862,76.042 8.621,75.624C8.38,75.206 8.253,74.732 8.253,74.249C8.253,73.767 8.38,73.293 8.621,72.875C13.857,63.824 21.924,57.334 31.34,54.257C26.682,51.485 23.064,47.26 21.04,42.232C19.016,37.204 18.699,31.651 20.137,26.425C21.574,21.199 24.688,16.59 28.999,13.305C33.31,10.02 38.58,8.241 44,8.241C49.42,8.241 54.69,10.02 59.001,13.305C63.312,16.59 66.426,21.199 67.863,26.425C69.301,31.651 68.984,37.204 66.96,42.232C64.936,47.26 61.318,51.485 56.66,54.257C66.076,57.334 74.143,63.824 79.379,72.875C79.621,73.293 79.748,73.767 79.749,74.249C79.749,74.732 79.623,75.207 79.382,75.625Z"
        android:fillColor="#000000"/>
</vector>
```

### colors.xml

Open **colors.xml** in **src/main/res/values/** and add the amber used to flag MRZ fields that fail validation. Both the error icon below and `ResultActivity` reference it:

```xml
<color name="warning_amber">#FFC107</color>
```

### ic_error_circle.xml

Create **ic_error_circle.xml** in **src/main/res/drawable/**. This vector drawable is the Material *error* glyph, displayed next to any field whose check digit fails validation:

```xml
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportWidth="24"
    android:viewportHeight="24">
    <path
        android:fillColor="@color/warning_amber"
        android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-2h2v2zM13,13h-2V7h2v6z" />
</vector>
```

> [!NOTE]
> A circle-exclamation is used rather than a warning triangle because a failed check digit is an invalid-data state, not a caution. This matches Material's convention of reserving the triangle for warnings.

## Registering ResultActivity

Open **AndroidManifest.xml** and declare `ResultActivity` inside the `<application>` block:

```xml
<activity
    android:name=".ResultActivity"
    android:exported="false"
    android:screenOrientation="portrait" />
```

`exported="false"` matters here. `MainActivity` starts this screen with an explicit `Intent`, which never requires the activity to be exported — and leaving it exported would let any other app on the device launch it with a scan result of its own choosing.

> [!NOTE]
> `MRZScannerActivity` is already declared in the library manifest with a default `screenOrientation` of `portrait`. If you need to override its orientation, redeclare it in your app manifest with `tools:replace="android:screenOrientation"`.

## ImagesFragment

`ImagesFragment` renders one or two document images side by side. The `ViewPager2` adapter in `ResultActivity` uses it to show the cropped and original scan images.

The images reach the fragment through `setArguments(Bundle)` rather than a constructor. `FragmentManager` recreates fragments by reflection, so it needs a public no-arg constructor and can only restore state it finds in the arguments `Bundle`. Because `ImageData` is not itself serializable into a `Bundle`, each image is encoded to JPEG bytes on the way in and decoded on the way out. The JPEG quality and maximum dimension are sized against the roughly 1 MB Binder transaction that carries saved instance state: 85% quality and a 1024 px cap keep each image under about 100 KB, comfortably clear of the limit even when both slots are used.

> [!IMPORTANT]
> Do not pass `ImageData` through the fragment's constructor. That form compiles, but `ResultActivity` will crash at `super.onCreate(...)` whenever the activity is recreated — after a configuration change, under **Don't keep activities**, or following process death.

<div class="sample-code-prefix"></div>
>- Java
>- Kotlin
>
>1. 
```java
package com.dynamsoft.scanmrz;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.dynamsoft.core.basic_structures.CoreException;
import com.dynamsoft.core.basic_structures.ImageData;
import java.io.ByteArrayOutputStream;
public class ImagesFragment extends Fragment {
       private static final String ARG_IMAGE_1 = "image1";
       private static final String ARG_IMAGE_2 = "image2";
       private static final int JPEG_QUALITY = 85;
       private static final int MAX_DIMENSION_PX = 1024;
       public ImagesFragment() {
          super();
       }
       @NonNull
       public static ImagesFragment newInstance(@Nullable ImageData imageData1, @Nullable ImageData imageData2) {
          ImagesFragment fragment = new ImagesFragment();
          Bundle args = new Bundle();
          byte[] bytes1 = encode(imageData1);
          byte[] bytes2 = encode(imageData2);
          if (bytes1 != null) args.putByteArray(ARG_IMAGE_1, bytes1);
          if (bytes2 != null) args.putByteArray(ARG_IMAGE_2, bytes2);
          fragment.setArguments(args);
          return fragment;
       }
       @Nullable
       private static byte[] encode(@Nullable ImageData imageData) {
          if (imageData == null) return null;
          try {
             Bitmap bmp = downscaleIfNeeded(imageData.toBitmap());
             ByteArrayOutputStream out = new ByteArrayOutputStream();
             bmp.compress(Bitmap.CompressFormat.JPEG, JPEG_QUALITY, out);
             return out.toByteArray();
          } catch (CoreException ignored) {
             return null;
          }
       }
       @NonNull
       private static Bitmap downscaleIfNeeded(@NonNull Bitmap src) {
          int w = src.getWidth();
          int h = src.getHeight();
          int max = Math.max(w, h);
          if (max <= MAX_DIMENSION_PX) return src;
          float scale = (float) MAX_DIMENSION_PX / max;
          return Bitmap.createScaledBitmap(src, Math.round(w * scale), Math.round(h * scale), true);
       }
       @Nullable
       @Override
       public View onCreateView(@NonNull LayoutInflater inflater,
                                @Nullable ViewGroup container,
                                @Nullable Bundle savedInstanceState) {
          LinearLayout root = new LinearLayout(requireContext());
          root.setLayoutParams(new LinearLayout.LayoutParams(
                  ViewGroup.LayoutParams.MATCH_PARENT,
                  ViewGroup.LayoutParams.MATCH_PARENT
          ));
          root.setOrientation(LinearLayout.HORIZONTAL);
          root.setGravity(Gravity.CENTER_VERTICAL);
          root.setBaselineAligned(false);
          root.setClipToPadding(false);
          root.setClipChildren(false);
          return root;
       }
       @Override
       public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
          Bundle args = getArguments();
          if (args == null) return;
          LinearLayout root = (LinearLayout) view;
          byte[] bytes1 = args.getByteArray(ARG_IMAGE_1);
          byte[] bytes2 = args.getByteArray(ARG_IMAGE_2);
          addImageView(root, bytes1);
          if (bytes1 != null && bytes2 != null) {
             root.addView(new View(requireContext()),
                     new LinearLayout.LayoutParams(
                             (int) (16 * getResources().getDisplayMetrics().density),
                             ViewGroup.LayoutParams.MATCH_PARENT));
          }
          addImageView(root, bytes2);
       }
       private void addImageView(@NonNull LinearLayout root, @Nullable byte[] bytes) {
          if (bytes == null) return;
          Bitmap bmp = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
          if (bmp == null) return;
          ImageView iv = new ImageView(requireContext());
          LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
                  0, ViewGroup.LayoutParams.MATCH_PARENT, 1f);
          iv.setLayoutParams(lp);
          iv.setScaleType(ImageView.ScaleType.FIT_CENTER);
          iv.setAdjustViewBounds(true);
          iv.setImageBitmap(bmp);
          root.addView(iv);
       }
}
```
2. 
```kotlin
package com.dynamsoft.scanmrz
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.os.Bundle
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout
import androidx.fragment.app.Fragment
import com.dynamsoft.core.basic_structures.CoreException
import com.dynamsoft.core.basic_structures.ImageData
import java.io.ByteArrayOutputStream
import kotlin.math.max
import kotlin.math.roundToInt
class ImagesFragment : Fragment() {
       override fun onCreateView(
          inflater: LayoutInflater,
          container: ViewGroup?,
          savedInstanceState: Bundle?
       ): View {
          val root = LinearLayout(requireContext())
          root.layoutParams = LinearLayout.LayoutParams(
             ViewGroup.LayoutParams.MATCH_PARENT,
             ViewGroup.LayoutParams.MATCH_PARENT
          )
          root.orientation = LinearLayout.HORIZONTAL
          root.gravity = Gravity.CENTER_VERTICAL
          root.isBaselineAligned = false
          root.clipToPadding = false
          root.clipChildren = false
          return root
       }
       override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
          val args = arguments ?: return
          val root = view as LinearLayout
          val bytes1 = args.getByteArray(ARG_IMAGE_1)
          val bytes2 = args.getByteArray(ARG_IMAGE_2)
          addImageView(root, bytes1)
          if (bytes1 != null && bytes2 != null) {
             root.addView(
                View(requireContext()),
                LinearLayout.LayoutParams(
                   (16 * resources.displayMetrics.density).toInt(),
                   ViewGroup.LayoutParams.MATCH_PARENT
                )
             )
          }
          addImageView(root, bytes2)
       }
       private fun addImageView(root: LinearLayout, bytes: ByteArray?) {
          if (bytes == null) return
          val bmp = BitmapFactory.decodeByteArray(bytes, 0, bytes.size) ?: return
          val iv = ImageView(requireContext())
          iv.layoutParams = LinearLayout.LayoutParams(
             0, ViewGroup.LayoutParams.MATCH_PARENT, 1f
          )
          iv.scaleType = ImageView.ScaleType.FIT_CENTER
          iv.adjustViewBounds = true
          iv.setImageBitmap(bmp)
          root.addView(iv)
       }
       companion object {
          private const val ARG_IMAGE_1 = "image1"
          private const val ARG_IMAGE_2 = "image2"
          private const val JPEG_QUALITY = 85
          private const val MAX_DIMENSION_PX = 1024
          fun newInstance(imageData1: ImageData?, imageData2: ImageData?): ImagesFragment {
             val fragment = ImagesFragment()
             val args = Bundle()
             encode(imageData1)?.let { args.putByteArray(ARG_IMAGE_1, it) }
             encode(imageData2)?.let { args.putByteArray(ARG_IMAGE_2, it) }
             fragment.arguments = args
             return fragment
          }
          private fun encode(imageData: ImageData?): ByteArray? {
             if (imageData == null) return null
             return try {
                val bmp = downscaleIfNeeded(imageData.toBitmap())
                val out = ByteArrayOutputStream()
                bmp.compress(Bitmap.CompressFormat.JPEG, JPEG_QUALITY, out)
                out.toByteArray()
             } catch (ignored: CoreException) {
                null
             }
          }
          private fun downscaleIfNeeded(src: Bitmap): Bitmap {
             val w = src.width
             val h = src.height
             val maxDimension = max(w, h)
             if (maxDimension <= MAX_DIMENSION_PX) return src
             val scale = MAX_DIMENSION_PX.toFloat() / maxDimension
             return Bitmap.createScaledBitmap(src, (w * scale).roundToInt(), (h * scale).roundToInt(), true)
          }
       }
}
```

## ResultActivity

`ResultActivity` receives the `MRZScanResult` passed from `MainActivity`, handles all three result statuses, and populates the result screen with the extracted MRZ data, portrait image, and document images.

**Per-field validation.** The `applyField` helper renders any field whose [`getFieldValidationStatus`](https://www.dynamsoft.com/mrz-scanner/docs/mobile/programming/android/api-reference/mrz-data.md#getfieldvalidationstatus) is `VS_FAILED` in amber with a trailing error icon, underlines it to signal that it is tappable, and opens a short explanation when tapped. The top summary block is deliberately left unstyled: it combines several values on one line, so flagging it on a single field's status would imply that everything on that line is suspect.

**Reading the data.** A finished scan normally carries data, but `getData()` is guarded rather than assumed — a `null` there shows the same empty-state text as an error instead of crashing. The gender field is title-cased with `Locale.ROOT`, since it is an ICAO code rather than localized text. `Doc. Type` is passed `VS_NONE` because it is derived from the MRZ code type rather than read from a field with its own check digit. The raw MRZ text is tappable for a reason that is easy to miss: a line-level failure can flag it when no individual field failed, which is what happens when the corruption lands in a field that carries no check digit of its own, such as name, nationality or sex.

**Camera-permission failures.** `showCameraPermissionAction` replaces **Re-Scan** with **Open Settings** for [`EC_CAMERA_PERMISSION_DENIED`](https://www.dynamsoft.com/mrz-scanner/docs/mobile/programming/android/api-reference/error-code.md), and `onResume` re-checks the permission so that granting it in Settings and returning starts a new scan instead of leaving a stale error on screen. **Open Settings** is not offered for `EC_CAMERA_PERMISSION_RESTRICTED`, because device policy withholds the camera and the per-app toggle is absent from Settings in that state.

The code below reads one string resource, so add it to **strings.xml** alongside the entries the layouts use:

```xml
<string name="scan_no_data">Scan returned no data</string>
```

**The image tabs.** Tabs appear only when both sets came back, since a tab bar with one tab is a control with nothing to switch between. `returnOriginalImage` is off by default, so the usual case is a single set, which gets a plain **Processed Image(s)** header styled like the sections below it. Page 0 is the processed pair whenever processed images exist, otherwise the single page holds the original pair.

<div class="sample-code-prefix"></div>
>- Java
>- Kotlin
>
>1. 
```java
package com.dynamsoft.scanmrz;
import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.style.ImageSpan;
import android.text.style.UnderlineSpan;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.fragment.app.Fragment;
import androidx.viewpager2.adapter.FragmentStateAdapter;
import androidx.viewpager2.widget.ViewPager2;
import com.dynamsoft.core.basic_structures.CoreException;
import com.dynamsoft.core.basic_structures.ImageData;
import com.dynamsoft.dcp.EnumValidationStatus;
import com.dynamsoft.mrzscannerbundle.ui.EnumDocumentSide;
import com.dynamsoft.mrzscannerbundle.ui.MRZData;
import com.dynamsoft.mrzscannerbundle.ui.MRZScanResult;
import com.google.android.material.tabs.TabLayout;
import com.google.android.material.tabs.TabLayoutMediator;
import java.util.Locale;
public class ResultActivity extends AppCompatActivity {
       public static final int REQUEST_CODE = 1024;
       public static final String EXTRA_RESULT = "RESULT";
       public static final String EXTRA_ACTION = "ACTION";
       public static final int ACTION_RESCAN = 0;
       public static final int ACTION_RETURN_HOME = 1;
       private boolean isShowingCameraPermissionError = false;
       @Override
       protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_results);
          ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
             Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
             v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
             return insets;
          });
          MRZScanResult scanResult = (MRZScanResult) getIntent().getParcelableExtra(EXTRA_RESULT);
          if (scanResult != null) {
             showMRZScanResult(scanResult);
          }
          findViewById(R.id.btn_rescan).setOnClickListener(v -> {
             setResult(RESULT_OK, getIntent().putExtra(EXTRA_ACTION, ACTION_RESCAN));
             finish();
          });
          findViewById(R.id.btn_return_home).setOnClickListener(v -> {
             setResult(RESULT_OK, getIntent().putExtra(EXTRA_ACTION, ACTION_RETURN_HOME));
             finish();
          });
       }
       @Override
       protected void onResume() {
          super.onResume();
          if (isShowingCameraPermissionError && hasCameraPermission()) {
             setResult(RESULT_OK, getIntent().putExtra(EXTRA_ACTION, ACTION_RESCAN));
             finish();
          }
       }
       private boolean hasCameraPermission() {
          return ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
                  == PackageManager.PERMISSION_GRANTED;
       }
       private void showMRZScanResult(MRZScanResult result) {
          if (result.getResultStatus() == MRZScanResult.EnumResultStatus.RS_CANCELED) {
             setResult(RESULT_OK, getIntent().putExtra(EXTRA_ACTION, ACTION_RETURN_HOME));
             finish();
             return;
          }
          if (result.getResultStatus() == MRZScanResult.EnumResultStatus.RS_EXCEPTION) {
             findViewById(R.id.result_view).setVisibility(View.GONE);
             TextView tvNoResult = findViewById(R.id.no_result_view);
             tvNoResult.setVisibility(View.VISIBLE);
             tvNoResult.setText(result.getErrorString());
             showCameraPermissionAction(result.getErrorCode());
             return;
          }
          MRZData data = result.getData();
          if (data == null) {
             findViewById(R.id.result_view).setVisibility(View.GONE);
             TextView tvNoData = findViewById(R.id.no_result_view);
             tvNoData.setVisibility(View.VISIBLE);
             tvNoData.setText(R.string.scan_no_data);
             return;
          }
          findViewById(R.id.result_view).setVisibility(View.VISIBLE);
          findViewById(R.id.no_result_view).setVisibility(View.GONE);
          String sexText = data.getSex();
          String genderText = sexText.isEmpty() ? "" : sexText.substring(0, 1).toUpperCase(Locale.ROOT)
                  + sexText.substring(1).toLowerCase(Locale.ROOT);
          ((TextView) findViewById(R.id.tv_full_name)).setText((data.getFirstName() + " " + data.getLastName()).trim());
          ((TextView) findViewById(R.id.tv_gender_and_age)).setText(
                  genderText.isEmpty() && data.getAge() == 0
                          ? ""
                          : genderText + ", " + data.getAge() + " years old");
          ((TextView) findViewById(R.id.tv_expiry)).setText(data.getDateOfExpire().isEmpty() ? "" : "Expiry: " + data.getDateOfExpire());
          ImageView ivPortrait = findViewById(R.id.iv_portrait);
          ImageData portraitImage = result.getPortraitImage();
          if (portraitImage != null) {
             try {
                ivPortrait.setImageBitmap(portraitImage.toBitmap());
             } catch (CoreException ignored) {
             }
          } else {
             ivPortrait.setImageResource(R.drawable.ic_portrait_placeholder);
          }
          showImages(result);
          applyField(findViewById(R.id.tv_given_name), data.getFirstName(), data.getFieldValidationStatus("firstName"));
          applyField(findViewById(R.id.tv_surname), data.getLastName(), data.getFieldValidationStatus("lastName"));
          applyField(findViewById(R.id.tv_date_of_birth), data.getDateOfBirth(), data.getFieldValidationStatus("dateOfBirth"));
          applyField(findViewById(R.id.tv_gender), genderText, data.getFieldValidationStatus("sex"));
          applyField(findViewById(R.id.tv_nationality), data.getNationality(), data.getFieldValidationStatus("nationality"));
          String docTypeText;
          switch (data.getDocumentType() == null ? "" : data.getDocumentType()) {
             case "MRTD_TD1_ID":       docTypeText = "ID (TD1)"; break;
             case "MRTD_TD2_ID":       docTypeText = "ID (TD2)"; break;
             case "MRTD_TD3_PASSPORT": docTypeText = "Passport (TD3)"; break;
             default:                  docTypeText = ""; break;
          }
          applyField(findViewById(R.id.tv_doc_type), docTypeText, EnumValidationStatus.VS_NONE);
          applyField(findViewById(R.id.tv_doc_number), data.getDocumentNumber(), data.getFieldValidationStatus("documentNumber"));
          applyField(findViewById(R.id.tv_expiry_date), data.getDateOfExpire(), data.getFieldValidationStatus("dateOfExpire"));
          applyField(findViewById(R.id.tv_raw_mrz), data.getMrzText(), data.getFieldValidationStatus("mrzText"));
       }
       private void applyField(TextView tv, String value, int status) {
          boolean failed = status == EnumValidationStatus.VS_FAILED;
          boolean empty = value == null || value.isEmpty();
          String text = empty ? "N/A" : value;
          if (failed) {
             Drawable icon = ContextCompat.getDrawable(this, R.drawable.ic_error_circle);
             SpannableString spannable = new SpannableString(icon == null ? text : text + "  ￼");
             if (icon != null) {
                int iconSize = Math.round(tv.getTextSize() * 1.2f);
                icon.setBounds(0, 0, iconSize, iconSize);
                spannable.setSpan(new ImageSpan(icon, ImageSpan.ALIGN_BOTTOM),
                        spannable.length() - 1, spannable.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
             }
             spannable.setSpan(new UnderlineSpan(), 0, text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
             tv.setText(spannable);
          } else {
             tv.setText(text);
          }
          tv.setTextColor(ContextCompat.getColor(this, failed ? R.color.warning_amber : R.color.white));
          if (failed) {
             tv.setOnClickListener(v -> showValidationInfoDialog());
          } else {
             tv.setOnClickListener(null);
             tv.setClickable(false);
          }
       }
       private void showValidationInfoDialog() {
          new AlertDialog.Builder(this)
                  .setTitle("Field validation warning")
                  .setMessage("This value doesn't match its check digit. The document may be invalid or altered.")
                  .setPositiveButton("OK", null)
                  .show();
       }
       private void showCameraPermissionAction(int errorCode) {
          if (errorCode != MRZScanResult.EnumErrorCode.EC_CAMERA_PERMISSION_DENIED) {
             return;
          }
          isShowingCameraPermissionError = true;
          findViewById(R.id.btn_rescan).setVisibility(View.GONE);
          View btnOpenSettings = findViewById(R.id.btn_open_settings);
          btnOpenSettings.setVisibility(View.VISIBLE);
          btnOpenSettings.setOnClickListener(v -> startActivity(
                  new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
                          Uri.fromParts("package", getPackageName(), null))));
       }
       private void showImages(MRZScanResult result) {
          ImageData mrzSideDocumentImage = result.getDocumentImage(EnumDocumentSide.DS_MRZ);
          ImageData oppositeSideDocumentImage = result.getDocumentImage(EnumDocumentSide.DS_OPPOSITE);
          ImageData mrzSideOriginalImage = result.getOriginalImage(EnumDocumentSide.DS_MRZ);
          ImageData oppositeSideOriginalImage = result.getOriginalImage(EnumDocumentSide.DS_OPPOSITE);
          TabLayout tabImages = findViewById(R.id.tab_images);
          ViewPager2 vpImages = findViewById(R.id.vp_images);
          TextView tvImagesHeader = findViewById(R.id.tv_images_header);
          boolean hasProcessed = mrzSideDocumentImage != null || oppositeSideDocumentImage != null;
          boolean hasOriginal = mrzSideOriginalImage != null || oppositeSideOriginalImage != null;
          if (!hasProcessed && !hasOriginal) {
             tvImagesHeader.setVisibility(View.GONE);
             tabImages.setVisibility(View.GONE);
             vpImages.setVisibility(View.GONE);
             return;
          }
          boolean showsTabs = hasProcessed && hasOriginal;
          tabImages.setVisibility(showsTabs ? View.VISIBLE : View.GONE);
          tvImagesHeader.setVisibility(showsTabs ? View.GONE : View.VISIBLE);
          tvImagesHeader.setText(hasProcessed ? "Processed Image(s)" : "Original Image(s)");
          vpImages.setVisibility(View.VISIBLE);
          vpImages.setAdapter(new FragmentStateAdapter(this) {
             @NonNull
             @Override
             public Fragment createFragment(int position) {
                if (position == 0 && hasProcessed) {
                   return ImagesFragment.newInstance(mrzSideDocumentImage, oppositeSideDocumentImage);
                } else {
                   return ImagesFragment.newInstance(mrzSideOriginalImage, oppositeSideOriginalImage);
                }
             }
             @Override
             public int getItemCount() {
                return hasProcessed && hasOriginal ? 2 : 1;
             }
          });
          if (showsTabs) {
             new TabLayoutMediator(tabImages, vpImages, (tab, position) ->
                     tab.setText(position == 0 ? "Processed" : "Original")).attach();
          }
       }
}
```
2. 
```kotlin
package com.dynamsoft.scanmrz
import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Bundle
import android.provider.Settings
import android.text.SpannableString
import android.text.Spanned
import android.text.style.ImageSpan
import android.text.style.UnderlineSpan
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.fragment.app.Fragment
import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.viewpager2.widget.ViewPager2
import com.dynamsoft.core.basic_structures.CoreException
import com.dynamsoft.dcp.EnumValidationStatus
import com.dynamsoft.mrzscannerbundle.ui.EnumDocumentSide
import com.dynamsoft.mrzscannerbundle.ui.MRZScanResult
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
import java.util.Locale
import kotlin.math.roundToInt
class ResultActivity : AppCompatActivity() {
       private var isShowingCameraPermissionError = false
       override fun onCreate(savedInstanceState: Bundle?) {
          super.onCreate(savedInstanceState)
          setContentView(R.layout.activity_results)
          ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
             val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
             v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
             insets
          }
          @Suppress("DEPRECATION")
          val scanResult = intent.getParcelableExtra<MRZScanResult>(EXTRA_RESULT)
          if (scanResult != null) {
             showMRZScanResult(scanResult)
          }
          findViewById<View>(R.id.btn_rescan).setOnClickListener {
             setResult(RESULT_OK, intent.putExtra(EXTRA_ACTION, ACTION_RESCAN))
             finish()
          }
          findViewById<View>(R.id.btn_return_home).setOnClickListener {
             setResult(RESULT_OK, intent.putExtra(EXTRA_ACTION, ACTION_RETURN_HOME))
             finish()
          }
       }
       override fun onResume() {
          super.onResume()
          if (isShowingCameraPermissionError && hasCameraPermission()) {
             setResult(RESULT_OK, intent.putExtra(EXTRA_ACTION, ACTION_RESCAN))
             finish()
          }
       }
       private fun hasCameraPermission(): Boolean =
          ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) ==
                  PackageManager.PERMISSION_GRANTED
       private fun showMRZScanResult(result: MRZScanResult) {
          if (result.resultStatus == MRZScanResult.EnumResultStatus.RS_CANCELED) {
             setResult(RESULT_OK, intent.putExtra(EXTRA_ACTION, ACTION_RETURN_HOME))
             finish()
             return
          }
          if (result.resultStatus == MRZScanResult.EnumResultStatus.RS_EXCEPTION) {
             findViewById<View>(R.id.result_view).visibility = View.GONE
             val tvNoResult = findViewById<TextView>(R.id.no_result_view)
             tvNoResult.visibility = View.VISIBLE
             tvNoResult.text = result.errorString
             showCameraPermissionAction(result.errorCode)
             return
          }
          val data = result.data
          if (data == null) {
             findViewById<View>(R.id.result_view).visibility = View.GONE
             val tvNoData = findViewById<TextView>(R.id.no_result_view)
             tvNoData.visibility = View.VISIBLE
             tvNoData.setText(R.string.scan_no_data)
             return
          }
          findViewById<View>(R.id.result_view).visibility = View.VISIBLE
          findViewById<View>(R.id.no_result_view).visibility = View.GONE
          val sexText = data.sex
          val genderText = if (sexText.isEmpty()) ""
          else sexText.substring(0, 1).uppercase(Locale.ROOT) + sexText.substring(1).lowercase(Locale.ROOT)
          findViewById<TextView>(R.id.tv_full_name).text = (data.firstName + " " + data.lastName).trim()
          findViewById<TextView>(R.id.tv_gender_and_age).text =
             if (genderText.isEmpty() && data.age == 0) ""
             else "$genderText, ${data.age} years old"
          findViewById<TextView>(R.id.tv_expiry).text =
             if (data.dateOfExpire.isEmpty()) "" else "Expiry: ${data.dateOfExpire}"
          val ivPortrait = findViewById<ImageView>(R.id.iv_portrait)
          val portraitImage = result.portraitImage
          if (portraitImage != null) {
             try {
                ivPortrait.setImageBitmap(portraitImage.toBitmap())
             } catch (ignored: CoreException) {
             }
          } else {
             ivPortrait.setImageResource(R.drawable.ic_portrait_placeholder)
          }
          showImages(result)
          applyField(findViewById(R.id.tv_given_name), data.firstName, data.getFieldValidationStatus("firstName"))
          applyField(findViewById(R.id.tv_surname), data.lastName, data.getFieldValidationStatus("lastName"))
          applyField(findViewById(R.id.tv_date_of_birth), data.dateOfBirth, data.getFieldValidationStatus("dateOfBirth"))
          applyField(findViewById(R.id.tv_gender), genderText, data.getFieldValidationStatus("sex"))
          applyField(findViewById(R.id.tv_nationality), data.nationality, data.getFieldValidationStatus("nationality"))
          val docTypeText = when (data.documentType ?: "") {
             "MRTD_TD1_ID" -> "ID (TD1)"
             "MRTD_TD2_ID" -> "ID (TD2)"
             "MRTD_TD3_PASSPORT" -> "Passport (TD3)"
             else -> ""
          }
          applyField(findViewById(R.id.tv_doc_type), docTypeText, EnumValidationStatus.VS_NONE)
          applyField(findViewById(R.id.tv_doc_number), data.documentNumber, data.getFieldValidationStatus("documentNumber"))
          applyField(findViewById(R.id.tv_expiry_date), data.dateOfExpire, data.getFieldValidationStatus("dateOfExpire"))
          applyField(findViewById(R.id.tv_raw_mrz), data.mrzText, data.getFieldValidationStatus("mrzText"))
       }
       private fun applyField(tv: TextView, value: String?, status: Int) {
          val failed = status == EnumValidationStatus.VS_FAILED
          val text = if (value.isNullOrEmpty()) "N/A" else value
          if (failed) {
             val icon = ContextCompat.getDrawable(this, R.drawable.ic_error_circle)
             val spannable = SpannableString(if (icon == null) text else "$text  ￼")
             if (icon != null) {
                val iconSize = (tv.textSize * 1.2f).roundToInt()
                icon.setBounds(0, 0, iconSize, iconSize)
                spannable.setSpan(
                   ImageSpan(icon, ImageSpan.ALIGN_BOTTOM),
                   spannable.length - 1, spannable.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
                )
             }
             spannable.setSpan(UnderlineSpan(), 0, text.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
             tv.text = spannable
          } else {
             tv.text = text
          }
          tv.setTextColor(
             ContextCompat.getColor(this, if (failed) R.color.warning_amber else R.color.white)
          )
          if (failed) {
             tv.setOnClickListener { showValidationInfoDialog() }
          } else {
             tv.setOnClickListener(null)
             tv.isClickable = false
          }
       }
       private fun showValidationInfoDialog() {
          AlertDialog.Builder(this)
             .setTitle("Field validation warning")
             .setMessage("This value doesn't match its check digit. The document may be invalid or altered.")
             .setPositiveButton("OK", null)
             .show()
       }
       private fun showCameraPermissionAction(errorCode: Int) {
          if (errorCode != MRZScanResult.EnumErrorCode.EC_CAMERA_PERMISSION_DENIED) {
             return
          }
          isShowingCameraPermissionError = true
          findViewById<View>(R.id.btn_rescan).visibility = View.GONE
          val btnOpenSettings = findViewById<View>(R.id.btn_open_settings)
          btnOpenSettings.visibility = View.VISIBLE
          btnOpenSettings.setOnClickListener {
             startActivity(
                Intent(
                   Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
                   Uri.fromParts("package", packageName, null)
                )
             )
          }
       }
       private fun showImages(result: MRZScanResult) {
          val mrzSideDocumentImage = result.getDocumentImage(EnumDocumentSide.DS_MRZ)
          val oppositeSideDocumentImage = result.getDocumentImage(EnumDocumentSide.DS_OPPOSITE)
          val mrzSideOriginalImage = result.getOriginalImage(EnumDocumentSide.DS_MRZ)
          val oppositeSideOriginalImage = result.getOriginalImage(EnumDocumentSide.DS_OPPOSITE)
          val tabImages = findViewById<TabLayout>(R.id.tab_images)
          val vpImages = findViewById<ViewPager2>(R.id.vp_images)
          val tvImagesHeader = findViewById<TextView>(R.id.tv_images_header)
          val hasProcessed = mrzSideDocumentImage != null || oppositeSideDocumentImage != null
          val hasOriginal = mrzSideOriginalImage != null || oppositeSideOriginalImage != null
          if (!hasProcessed && !hasOriginal) {
             tvImagesHeader.visibility = View.GONE
             tabImages.visibility = View.GONE
             vpImages.visibility = View.GONE
             return
          }
          val showsTabs = hasProcessed && hasOriginal
          tabImages.visibility = if (showsTabs) View.VISIBLE else View.GONE
          tvImagesHeader.visibility = if (showsTabs) View.GONE else View.VISIBLE
          tvImagesHeader.text = if (hasProcessed) "Processed Image(s)" else "Original Image(s)"
          vpImages.visibility = View.VISIBLE
          vpImages.adapter = object : FragmentStateAdapter(this) {
             override fun createFragment(position: Int): Fragment {
                return if (position == 0 && hasProcessed) {
                   ImagesFragment.newInstance(mrzSideDocumentImage, oppositeSideDocumentImage)
                } else {
                   ImagesFragment.newInstance(mrzSideOriginalImage, oppositeSideOriginalImage)
                }
             }
             override fun getItemCount(): Int {
                return if (hasProcessed && hasOriginal) 2 else 1
             }
          }
          if (showsTabs) {
             TabLayoutMediator(tabImages, vpImages) { tab, position ->
                tab.text = if (position == 0) "Processed" else "Original"
             }.attach()
          }
       }
       companion object {
          const val REQUEST_CODE = 1024
          const val EXTRA_RESULT = "RESULT"
          const val EXTRA_ACTION = "ACTION"
          const val ACTION_RESCAN = 0
          const val ACTION_RETURN_HOME = 1
       }
}
```

## MainActivity

The user guide's `MainActivity` renders the result on its own screen. Here it does one thing differently: it hands the result to `ResultActivity` and waits to hear what the user chose.

Two changes make that work. The launcher callback packs the `MRZScanResult` into an `Intent` and starts `ResultActivity`, and `onActivityResult` reads the action that comes back — relaunching the scanner when the user tapped **Re-Scan**. `onActivityResult` is deprecated in favour of the Activity Result APIs; the sample keeps it so the hand-off mirrors the Java version one-for-one.

The license below is a trial key, which needs a network connection. Request your own through [Request a Trial License](https://www.dynamsoft.com/customer/license/trialLicense?product=mrz&utm_source=samples&package=android).

<div class="sample-code-prefix"></div>
>- Java
>- Kotlin
>
>1. 
```java
package com.dynamsoft.scanmrz;
import android.content.Intent;
import android.os.Bundle;
import androidx.activity.EdgeToEdge;
import androidx.activity.result.ActivityResultLauncher;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import com.dynamsoft.mrzscannerbundle.ui.MRZScannerActivity;
import com.dynamsoft.mrzscannerbundle.ui.MRZScannerConfig;
public class MainActivity extends AppCompatActivity {
       private ActivityResultLauncher<MRZScannerConfig> launcher;
       private final MRZScannerConfig config = new MRZScannerConfig();
       @Override
       protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          EdgeToEdge.enable(this);
          setContentView(R.layout.activity_main);
          ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
             Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
             v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
             return insets;
          });
          config.setLicense("DLS2eyJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSJ9");
          launcher = registerForActivityResult(new MRZScannerActivity.ResultContract(), result -> {
             Intent intent = new Intent(this, ResultActivity.class);
             intent.putExtra(ResultActivity.EXTRA_RESULT, result);
             startActivityForResult(intent, ResultActivity.REQUEST_CODE);
          });
          findViewById(R.id.btn_start).setOnClickListener(v -> launcher.launch(config));
       }
       @Override
       protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
          super.onActivityResult(requestCode, resultCode, data);
          if (requestCode == ResultActivity.REQUEST_CODE && resultCode == RESULT_OK) {
             int action = data.getIntExtra(ResultActivity.EXTRA_ACTION, ResultActivity.ACTION_RETURN_HOME);
             if (action == ResultActivity.ACTION_RESCAN) {
                launcher.launch(config);
             }
          }
       }
}
```
2. 
```kotlin
package com.dynamsoft.scanmrz
import android.content.Intent
import android.os.Bundle
import android.view.View
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.ActivityResultLauncher
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.dynamsoft.mrzscannerbundle.ui.MRZScannerActivity
import com.dynamsoft.mrzscannerbundle.ui.MRZScannerConfig
class MainActivity : AppCompatActivity() {
       private lateinit var launcher: ActivityResultLauncher<MRZScannerConfig>
       private val config = MRZScannerConfig()
       override fun onCreate(savedInstanceState: Bundle?) {
          super.onCreate(savedInstanceState)
          enableEdgeToEdge()
          setContentView(R.layout.activity_main)
          ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
             val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
             v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
             insets
          }
          config.license = "DLS2eyJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSJ9"
          launcher = registerForActivityResult(MRZScannerActivity.ResultContract()) { result ->
             val intent = Intent(this, ResultActivity::class.java)
             intent.putExtra(ResultActivity.EXTRA_RESULT, result)
             startActivityForResult(intent, ResultActivity.REQUEST_CODE)
          }
          findViewById<View>(R.id.btn_start).setOnClickListener { launcher.launch(config) }
       }
       @Deprecated("Deprecated in Java")
       override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
          @Suppress("DEPRECATION")
          super.onActivityResult(requestCode, resultCode, data)
          if (requestCode == ResultActivity.REQUEST_CODE && resultCode == RESULT_OK) {
             val action = data?.getIntExtra(ResultActivity.EXTRA_ACTION, ResultActivity.ACTION_RETURN_HOME)
                ?: ResultActivity.ACTION_RETURN_HOME
             if (action == ResultActivity.ACTION_RESCAN) {
                launcher.launch(config)
             }
          }
       }
}
```

> [!NOTE]
> The `MRZScanResult` travels through the `Intent` with no extra handling. Its images are reference counted, and the instance `ResultActivity` unparcels takes its own reference. See [Results and Image Lifetime](https://www.dynamsoft.com/mrz-scanner/docs/mobile/programming/android/user-guide/index.md#results-and-image-lifetime).

## Next steps

- [MRZ Scanner User Guide](https://www.dynamsoft.com/mrz-scanner/docs/mobile/programming/android/user-guide/index.md) — build the minimal integration from scratch.
- [Demo and Samples](https://www.dynamsoft.com/mrz-scanner/docs/mobile/programming/android/samples/index.md) — all four Android samples, and the demo app.
- [Customizing the MRZ Scanner](https://www.dynamsoft.com/mrz-scanner/docs/mobile/programming/android/user-guide/customize-mrz-scanner.md) — document type, UI elements, feedback, and camera permission.
- [Android API Reference](https://www.dynamsoft.com/mrz-scanner/docs/mobile/programming/android/api-reference/index.md) — all classes and methods.
