Skip to content

Instantly share code, notes, and snippets.

@shirozatou
Last active October 7, 2025 21:03
Show Gist options
  • Select an option

  • Save shirozatou/5a80b064498c81e767541f071f6fe33b to your computer and use it in GitHub Desktop.

Select an option

Save shirozatou/5a80b064498c81e767541f071f6fe33b to your computer and use it in GitHub Desktop.
Use ML Kit's barcode scanning API without Firebase dependencies or datatransport stuff

Use ML Kit's barcode scanning API without Firebase dependencies or datatransport stuff

Gradle

Before

dependencies {
    implementation("com.google.mlkit:barcode-scanning:17.3.0")
}

After

dependencies {
    implementation("com.google.android.gms:play-services-basement:18.4.0")
    implementation("com.google.mlkit:barcode-scanning:17.3.0") {
        isTransitive = false
    }
}

Code

import android.content.Context
import android.graphics.Bitmap
import android.graphics.Point
import android.os.Parcel
import android.os.Parcelable
import com.google.android.gms.common.internal.safeparcel.SafeParcelReader
import com.google.android.gms.dynamic.ObjectWrapper
import com.google.mlkit.vision.barcode.bundled.internal.ThickBarcodeScannerCreator


private typealias BarcodeScannerOptions = com.google.android.gms.internal.mlkit_vision_barcode_bundled.zzba
private typealias ImageMetadata = com.google.android.gms.internal.mlkit_vision_barcode_bundled.zzcc
typealias BarcodeScanner = com.google.android.gms.internal.mlkit_vision_barcode_bundled.zzbn
private typealias Barcode = com.google.android.gms.internal.mlkit_vision_barcode_bundled.zzay

fun createBarcodeScanner(
    context: Context,
    // https://developers.google.com/android/reference/com/google/mlkit/vision/barcode/common/Barcode#FORMAT_ALL_FORMATS
    format: Int, // 0 for all format
): BarcodeScanner {
    return ThickBarcodeScannerCreator().newBarcodeScanner(
        ObjectWrapper.wrap(context.applicationContext),
        BarcodeScannerOptions(format, true)
    ).also { it.zzd() }
}

// Reminder: don't invoke this after close
private fun BarcodeScanner.recognize0(image: Bitmap): List<Barcode> {
    return zzb(
        ObjectWrapper.wrap(image),
        ImageMetadata(
            /** -1 for [Bitmap], 35 for [Image] */
            -1,
            image.width,
            image.height,
            /** rotation value */
            0,
            /** timestamp, 0 is OK */
            System.currentTimeMillis()
        )
    ).filterIsInstance<Barcode>()
}

fun BarcodeScanner.close() {
    zzf()
}
class BarcodeData(
    val format: Int,
    val rawValue: String?,
    val cornerPoints: Array<Point>?,
)

fun BarcodeScanner.recognize(image: Bitmap): List<BarcodeData> {
    return recognize0(image).mapNotNull(::readBarcodeResult)
}

// Access private fields without reflection
private fun readBarcodeResult(data: Barcode): BarcodeData? {
    val parcel = Parcel.obtain()
    data.writeToParcel(parcel, Parcelable.PARCELABLE_WRITE_RETURN_VALUE)

    // read it back
    parcel.setDataPosition(0)
    var format = 0
    var rawValue: String? = null
    var cornerPoints: Array<Point>? = null
    SafeParcelReader.validateObjectHeader(parcel).let { header ->
        while (parcel.dataPosition() < header) {
            val h = SafeParcelReader.readHeader(parcel)
            when (SafeParcelReader.getFieldId(h)) {
                1 -> {
                    val f = SafeParcelReader.readInt(parcel, h)
                    // unrecognized format
                    if (f < 1) {
                        parcel.recycle()
                        return null
                    }
                    format = f
                }

                3 -> rawValue = SafeParcelReader.createString(parcel, h)
                5 -> cornerPoints = SafeParcelReader.createTypedArray(parcel, h, Point.CREATOR)
                else -> SafeParcelReader.skipUnknownField(parcel, h)
            }
        }
        SafeParcelReader.ensureAtEnd(parcel, header)
    }

    parcel.recycle()
    return if (format < 1) null else BarcodeData(
        format = format,
        rawValue = rawValue,
        cornerPoints = cornerPoints,
    )
}

Almost done

Add missing class to java src directory. ProGuard will remove it.

package com.google.mlkit.vision.common.internal;

public class ImageUtils {
    private static final ImageUtils s = new ImageUtils();

    public static ImageUtils getInstance() {
        return s;
    }

    public android.graphics.Matrix getUprightRotationMatrix(int width, int height, int rotation) {
        // assert rotation is 0
        return null;
    }
}

Add ProGuard rules from com.google.mlkit:common:18.10.0.

# Annotations are implemented as attributes, so we have to explicitly keep them.
# Catch all which encompasses attributes like RuntimeVisibleParameterAnnotations
# and RuntimeVisibleTypeAnnotations
-keepattributes RuntimeVisible*Annotation*

# JNI is an entry point that's hard to keep track of, so there's
# an annotation to mark fields and methods used by native code.

# Keep the annotations that proguard needs to process.
-keep class com.google.android.apps.common.proguard.UsedBy*

# Just because native code accesses members of a class, does not mean that the
# class itself needs to be annotated - only annotate classes that are
# referenced themselves in native code.
-keep @com.google.android.apps.common.proguard.UsedBy* class * {
  <init>();
}
-keepclassmembers class * {
    @com.google.android.apps.common.proguard.UsedBy* *;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment