2026 में Android पर ML Kit बारकोड स्कैनिंग: Stocky एक सेकंड से भी कम समय में पैंट्री आइटम कैसे जोड़ता है
ML Kit और CameraX के साथ ऑन-डिवाइस बारकोड स्कैनिंग की एक व्यावहारिक 2026 गाइड — फॉर्मेट ट्यूनिंग, ऑफ़लाइन प्रोडक्ट लुकअप, और एक पैंट्री ऐप के पीछे का आंशिक-उपयोग गणित।
किसी ऐप में किराने का सामान टाइप करना एक छोटा टैक्स है जो आप हर बार उसे खरीदते समय चुकाते हैं। नाम, ब्रांड, साइज़, शायद एक श्रेणी — रसोई का सामान भरने वाले किसी व्यक्ति के लिए, यह उसके पास मौजूद हर उत्पाद से गुणा किया गया तीस सेकंड का घर्षण है। इस्तेमाल के लायक पैंट्री ऐप का एकमात्र संस्करण इस गणित को हराना होगा, जिसका मतलब है कि लगभग सारा काम बारकोड को ही करना होगा।
यही वह बाधा है जिसके इर्द-गिर्द Stocky बनाया गया है: कैमरे को बारकोड की ओर करें, और एक सेकंड बाद उत्पाद पैंट्री में होता है — नाम, साइज़, श्रेणी, पोषण डेटा, सब कुछ अपने आप भर जाता है। यहाँ उस एक सेकंड के पीछे की पाइपलाइन है, और वे हिस्से जिन्हें सही करने में खुद स्कैनिंग से ज़्यादा समय लगा।
स्कैनिंग पाइपलाइन: CameraX + ML Kit Barcode Scanning
ML Kit का Barcode Scanning API, Text Recognition वाले से एक अलग मॉडल है — छोटा, तेज़, और बिल्कुल एक ही काम के लिए बनाया गया: कैमरा फ्रेम के भीतर किसी बारकोड का फॉर्मेट और उसमें एन्कोड किया गया मान ढूँढना। सेटअप एक Gradle लाइन और एक CameraX एनालाइज़र भर है:
// app/build.gradle.kts
dependencies {
implementation("com.google.mlkit:barcode-scanning:17.3.0")
implementation("androidx.camera:camera-camera2:1.4.0")
implementation("androidx.camera:camera-lifecycle:1.4.0")
}
import com.google.mlkit.vision.barcode.BarcodeScanning
import com.google.mlkit.vision.barcode.common.Barcode
import com.google.mlkit.vision.barcode.BarcodeScannerOptions
import com.google.mlkit.vision.common.InputImage
import androidx.camera.core.ImageProxy
private val options = BarcodeScannerOptions.Builder()
.setBarcodeFormats(
Barcode.FORMAT_EAN_13,
Barcode.FORMAT_EAN_8,
Barcode.FORMAT_UPC_A,
Barcode.FORMAT_UPC_E,
)
.build()
private val scanner = BarcodeScanning.getClient(options)
@androidx.camera.core.ExperimentalGetImage
fun analyzeFrame(image: ImageProxy, onFound: (String) -> Unit) {
val media = image.image ?: return image.close()
val input = InputImage.fromMediaImage(media, image.imageInfo.rotationDegrees)
scanner.process(input)
.addOnSuccessListener { barcodes ->
barcodes.firstOrNull()?.rawValue?.let(onFound)
}
.addOnCompleteListener { image.close() }
}
setBarcodeFormats कॉल दिखने से कहीं ज़्यादा मायने रखती है। डिफ़ॉल्ट पर छोड़ने पर, स्कैनर QR कोड, PDF417, Aztec, और आधा दर्जन अन्य फॉर्मेट भी ढूँढता है जो उसे टमाटर के डिब्बे पर कभी नहीं मिलेंगे — हर फ्रेम को उस खोज की कीमत चुकानी पड़ती है। रिटेल उत्पाद लगभग पूरी तरह EAN-13, EAN-8, UPC-A, और UPC-E में रहते हैं। इन चार फॉर्मेट तक सीमित रखने से हर फ्रेम पर बर्बाद होने वाला काम कम होता है, और इससे भी ज़्यादा उपयोगी बात यह है कि बैकग्राउंड के आवारा पैटर्न से होने वाली गलत-सकारात्मक पहचान भी कम होती है।
13-अंकों की संख्या से एक ऐसे उत्पाद तक जिसे दिखाया जा सके
एक बारकोड सिर्फ अंक हैं। असली उत्पाद — नाम, ब्रांड, पैकेज साइज़, पोषण संबंधी तथ्य — कहीं और से आना होता है। Stocky इसे Open Food Facts के आधार पर हल करता है, जो एक खुला, समुदाय-संचालित उत्पाद डेटाबेस है, और इसमें समुदाय द्वारा जोड़े गए तुर्की उत्पाद भी शामिल हैं जिन्हें सार्वजनिक डेटासेट अक्सर छोड़ देता है।
लुकअप खुद स्कैन किए गए कोड पर आधारित एक सीधा REST कॉल है:
suspend fun lookupProduct(barcode: String): ProductInfo? {
localCache.find(barcode)?.let { return it }
return runCatching {
api.getProduct(barcode).toProductInfo()
}.getOrNull()?.also { localCache.save(barcode, it) }
}
यहाँ दो फ़ैसले कोड से दिखने से कहीं ज़्यादा भार उठाते हैं:
- पहले कैश, पहले नेटवर्क नहीं। एक बार जब कोई बारकोड हल हो जाता है — किसी के भी द्वारा, उसी डिवाइस पर पहले की गई स्कैन सहित — तो वह Room में आ जाता है, और अगली स्कैन तुरंत और ऑफ़लाइन होती है। पैंट्री ऐप के लिए, वही दर्जन भर उत्पाद (दूध, अंडे, वही ब्रांड की ब्रेड) लगातार दोबारा स्कैन होते रहते हैं; कैश हिट रेट तेज़ी से बढ़ता है।
- न मिलना कोई डेड एंड नहीं है। बिना ब्रांड वाले या क्षेत्र-विशिष्ट उत्पाद हमेशा Open Food Facts में नहीं होंगे। जब लुकअप खाली लौटता है, तो Stocky एक साधारण त्रुटि दिखाने के बजाय सीधे एक छोटे मैनुअल-एंट्री फॉर्म में चला जाता है जो पहले से बारकोड से भरा होता है। उपयोगकर्ता सिर्फ दो फ़ील्ड भरकर काम पूरा करता है, और — सहमति के साथ — यह एंट्री अगले उस व्यक्ति के लिए समुदाय डेटासेट को मज़बूत कर सकती है जो उसी बारकोड को स्कैन करेगा।
“एक सेकंड से कम” को ईमानदार बनाए रखना
स्कैनिंग मॉडल खुद एक मिड-रेंज फोन पर 100ms से काफ़ी कम समय में बारकोड हल कर लेता है। महसूस होने वाली लेटेंसी का लगभग पूरा बजट इसके इर्द-गिर्द की हर चीज़ में जाता है: कैमरे का वार्म-अप, एनालाइज़र की फ्रेम रेट, और दोहरी पहचान से बचना।
जो डिटेल फील के लिए वाकई मायने रखती है वह यह है: डिबाउंसिंग के बिना, एक स्थिर पकड़ी हुई कैमरा हर फ्रेम पर एनालाइज़र को ट्रिगर करता है — उतने समय में जितना एक हाथ को स्थिर होने में लगता है, उसी बारकोड की बीस से ज़्यादा पहचानें हो जाती हैं। हर एक के लिए लुकअप, हैप्टिक, और UI अपडेट ट्रिगर करना तेज़ी की तरह नहीं, अटकाव की तरह महसूस होता है।
private var lastCode: String? = null
private var lastAt = 0L
fun onBarcodeFound(code: String, now: Long) {
if (code == lastCode && now - lastAt < 1_500) return
lastCode = code
lastAt = now
handleScan(code)
}
प्रति कोड 1.5 सेकंड की डिबाउंस विंडो पूरे डिटेक्शन बर्स्ट को एक ही स्कैन में समेटने के लिए काफ़ी है, जबकि उपयोगकर्ता को कैमरा हटाकर वापस लाने के बाद उसी आइटम को जानबूझकर तुरंत दोबारा स्कैन करने देती है (जैसे पैंट्री में दूसरा दही का कप जोड़ना)।
आंशिक उपयोग की समस्या
पैंट्री का कोई आइटम मौजूद-या-गायब वाला बाइनरी फ्लैग नहीं है, और यही वह हिस्सा है जिसे अकेली बारकोड स्कैनिंग हल नहीं करती। Stocky का असली डेटा मॉडल हर पैकेज के मुक़ाबले बची हुई मात्रा को ट्रैक करता है: 1 लीटर दूध का पैकेट “स्टॉक में” या “खत्म” नहीं होता, वह पहले 1000ml होता है, फिर नाश्ते के बाद 700ml, फिर 0, और चुपचाप शॉपिंग लिस्ट में चला जाता है। उसी पैकेज पर बारकोड को दोबारा स्कैन करने से उपयोगकर्ता आंशिक खपत दर्ज कर पाता है — “300ml पिया” — बजाय इसके कि किसी ऐसी चीज़ के लिए पूरा “खत्म हो गया, नया जोड़ो” चक्र मजबूर किया जाए जो फ्रिज में अभी भी आधी भरी है।
यही एक डिज़ाइन फ़ैसला है जो बाद में खत्म होने के अनुमानों को ईमानदार बनाता है: वे हर बार शून्य पर लौटने वाले और रफ़्तार के बारे में कुछ न बताने वाले किसी मोटे स्टॉक-में/खत्म-हो-गया टॉगल पर नहीं, बल्कि हर उत्पाद के असली खपत वक्र पर बने होते हैं।
जो डिवाइस पर रहता है, और जो नहीं
फ्रेम फोन से कभी बाहर नहीं जाता — ML Kit का बारकोड मॉडल पूरी तरह ऑन-डिवाइस चलता है, बिल्कुल अपने टेक्स्ट रिकग्निशन वाले साथी की तरह। नेटवर्क पर केवल बारकोड के अंक ही जाते हैं, जो उस उत्पाद को खोजने के लिए भेजे जाते हैं जिसे लाखों अन्य खरीदार पहले ही स्कैन कर चुके हैं। न तो कोई फ़ोटो कैप्चर होती है, न स्टोर होती है, और कौन स्कैन कर रहा है इस बारे में कुछ भी रिक्वेस्ट के साथ नहीं जाता। पैंट्री की सामग्री, खपत का इतिहास, और खर्च स्थानीय रहते हैं; केवल गुमनाम उत्पाद लुकअप साझा होता है।
यह OCR की तुलना में एक छोटी प्राइवेसी सतह है, और किसी फ़ीचर के लिए विज़न API चुनते समय इसे याद रखना उपयोगी है: अगर आपको जिस वैल्यू की ज़रूरत है वह एक तय, संरचित पहचानकर्ता है — कोई गद्य पन्ना नहीं, बल्कि एक बारकोड — तो एक स्कैनर मॉडल न सिर्फ बनाने में तेज़ है, बल्कि प्राइवेसी पॉलिसी में उसका बचाव करना भी उस रिकग्नाइज़र से आसान है जिसे किसी का दस्तावेज़ पढ़ना पड़ता है।
Stocky अब Google Play पर लाइव है — एक बारकोड स्कैन करें, और बाकी की किचन ट्रैकिंग वहीं से आगे बढ़ती है। ऐप पेज पर और जानें।
// संबंधित पठन
जर्नल से और भी
2026 में Jetpack DataStore: बिना एक भी सेटिंग खोए SharedPreferences से माइग्रेट करना
Android पर SharedPreferences से Jetpack DataStore पर जाने की एक व्यावहारिक गाइड — असिंक्रोनस दिक्कतें, मौजूदा वैल्यू को बचाने वाला माइग्रेशन तरीका, और इसे कैसे टेस्ट करें।
2026 में Room डेटाबेस माइग्रेशन: बिना एक भी रो खोए स्कीमा बदलाव शिप करना
Android पर Room डेटाबेस माइग्रेशन के लिए एक व्यावहारिक गाइड — AutoMigration, हाथ से लिखे Migration ऑब्जेक्ट्स, और अपने यूज़र्स के बग पाने से पहले माइग्रेशन को कैसे टेस्ट करें।
Android पर Baseline Profiles: 2026 में आपका कोल्ड-स्टार्ट टाइम असल में क्या तय करता है
Android Baseline Profiles के लिए एक व्यावहारिक गाइड — Macrobenchmark से इसे जनरेट करना, Gradle में जोड़ना, असली फायदे को मापना, और कोल्ड स्टार्ट को प्रभावित करने वाली तीन और चीज़ें।