<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Computer Vision on MangoDriod</title><link>https://md.eknath.dev/tags/computer-vision/</link><description>Recent content in Computer Vision on MangoDriod</description><generator>Hugo -- 0.141.0</generator><language>en-us</language><lastBuildDate>Tue, 10 Feb 2026 21:30:00 +0530</lastBuildDate><atom:link href="https://md.eknath.dev/tags/computer-vision/index.xml" rel="self" type="application/rss+xml"/><item><title>Android ML Kit Document Scanner: Stop Using Camera Capture for Documents</title><link>https://md.eknath.dev/posts/android-ml-kit-document-scanner/</link><pubDate>Tue, 10 Feb 2026 21:30:00 +0530</pubDate><guid>https://md.eknath.dev/posts/android-ml-kit-document-scanner/</guid><description>&lt;h2 id="tldr---why-ml-kit-document-scanner-changes-everything">TL;DR - Why ML Kit Document Scanner Changes Everything&lt;/h2>
&lt;p>Stop building custom camera UIs for document capture. &lt;strong>ML Kit Document Scanner&lt;/strong> gives you a professional, AI-powered document scanning experience with just a few lines of code:&lt;/p>
&lt;p>✅ &lt;strong>Automatic edge detection&lt;/strong> - AI finds document boundaries instantly
✅ &lt;strong>Perspective correction&lt;/strong> - Automatically straightens skewed documents
✅ &lt;strong>Shadow removal&lt;/strong> - Intelligent lighting correction
✅ &lt;strong>Multi-page support&lt;/strong> - Scan multiple pages in one session
✅ &lt;strong>Quality enhancement&lt;/strong> - Auto-adjusts contrast and brightness
✅ &lt;strong>Minimal code&lt;/strong> - 10 lines vs 500+ for custom implementation
✅ &lt;strong>Small library size&lt;/strong> - ~3MB vs building from scratch&lt;/p></description><content:encoded><![CDATA[<h2 id="tldr---why-ml-kit-document-scanner-changes-everything">TL;DR - Why ML Kit Document Scanner Changes Everything</h2>
<p>Stop building custom camera UIs for document capture. <strong>ML Kit Document Scanner</strong> gives you a professional, AI-powered document scanning experience with just a few lines of code:</p>
<p>✅ <strong>Automatic edge detection</strong> - AI finds document boundaries instantly
✅ <strong>Perspective correction</strong> - Automatically straightens skewed documents
✅ <strong>Shadow removal</strong> - Intelligent lighting correction
✅ <strong>Multi-page support</strong> - Scan multiple pages in one session
✅ <strong>Quality enhancement</strong> - Auto-adjusts contrast and brightness
✅ <strong>Minimal code</strong> - 10 lines vs 500+ for custom implementation
✅ <strong>Small library size</strong> - ~3MB vs building from scratch</p>
<p><strong>The result?</strong> Professional document scanning that rivals dedicated scanner apps, with 95% less code and effort.</p>
<hr>
<h2 id="why-developers-still-use-basic-camera-capture">Why Developers Still Use Basic Camera Capture</h2>
<p>Despite ML Kit Document Scanner being available since 2022, most apps still use basic camera capture for documents. Here&rsquo;s why:</p>
<ol>
<li><strong>Lack of awareness</strong> - Many developers don&rsquo;t know it exists</li>
<li><strong>&ldquo;Camera is good enough&rdquo;</strong> - Until users complain about quality</li>
<li><strong>Custom UI preference</strong> - Wanting full control (unnecessary)</li>
<li><strong>Assumed complexity</strong> - Thinking it requires ML expertise</li>
<li><strong>Library size concerns</strong> - Actually very reasonable (~3MB)</li>
</ol>
<p><strong>The reality:</strong> Basic camera capture for documents creates <strong>terrible UX</strong> compared to proper document scanning.</p>
<hr>
<h2 id="the-problem-with-basic-camera-capture">The Problem with Basic Camera Capture</h2>
<h3 id="what-happens-with-regular-camera">What Happens with Regular Camera:</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-kotlin" data-lang="kotlin"><span style="display:flex;"><span><span style="color:#75715e">// Typical camera implementation
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">val</span> cameraIntent = Intent(<span style="color:#a6e22e">MediaStore</span>.ACTION_IMAGE_CAPTURE)
</span></span><span style="display:flex;"><span>launcher.launch(cameraIntent)
</span></span><span style="display:flex;"><span><span style="color:#75715e">// User gets: blurry, skewed, shadowy image
</span></span></span></code></pre></div><p><strong>User experience issues:</strong></p>
<ul>
<li>📸 No guidance on document boundaries</li>
<li>🔲 Manual cropping required (tedious)</li>
<li>🌓 Poor lighting = unusable scans</li>
<li>📐 Perspective distortion (holding phone at angle)</li>
<li>📄 One page at a time (inefficient for multi-page docs)</li>
<li>🎨 No enhancement (washed out, low contrast)</li>
</ul>
<p><strong>Result:</strong> Users waste time cropping, retaking photos, and dealing with poor quality scans.</p>
<hr>
<h2 id="what-ml-kit-document-scanner-provides">What ML Kit Document Scanner Provides</h2>
<h3 id="the-complete-package">The Complete Package:</h3>
<ol>
<li>
<p><strong>Real-time Edge Detection</strong></p>
<ul>
<li>AI instantly finds document corners</li>
<li>Visual overlay shows detected boundaries</li>
<li>Works even with complex backgrounds</li>
</ul>
</li>
<li>
<p><strong>Auto Perspective Correction</strong></p>
<ul>
<li>Straightens tilted/skewed documents</li>
<li>Removes keystoning (trapezoid effect)</li>
<li>Perfect rectangular output every time</li>
</ul>
</li>
<li>
<p><strong>Smart Enhancement</strong></p>
<ul>
<li>Removes shadows and glare</li>
<li>Adjusts contrast automatically</li>
<li>Optimizes for text readability</li>
<li>Handles various lighting conditions</li>
</ul>
</li>
<li>
<p><strong>Multi-page Scanning</strong></p>
<ul>
<li>Scan entire documents in one flow</li>
<li>Add/remove pages easily</li>
<li>Page reordering built-in</li>
</ul>
</li>
<li>
<p><strong>Multiple Export Formats</strong></p>
<ul>
<li>High-quality images (JPEG/PNG)</li>
<li>PDF generation built-in</li>
<li>Configurable resolution</li>
</ul>
</li>
</ol>
<hr>
<h2 id="implementation">Implementation</h2>
<h3 id="step-1-add-dependencies"><strong>Step 1: Add Dependencies</strong></h3>
<p>Add to your app&rsquo;s <code>build.gradle</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-gradle" data-lang="gradle"><span style="display:flex;"><span>dependencies <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// ML Kit Document Scanner
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    implementation <span style="color:#e6db74">&#39;com.google.android.gms:play-services-mlkit-document-scanner:16.0.0-beta1&#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">}</span>
</span></span></code></pre></div><p><strong>Library size:</strong> ~3MB (tiny compared to the functionality you get!)</p>
<hr>
<h3 id="step-2-configure-scanner-options"><strong>Step 2: Configure Scanner Options</strong></h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-kotlin" data-lang="kotlin"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> com.google.mlkit.vision.documentscanner.GmsDocumentScanner
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> com.google.mlkit.vision.documentscanner.GmsDocumentScannerOptions
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> com.google.mlkit.vision.documentscanner.GmsDocumentScannerOptions.RESULT_FORMAT_JPEG
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> com.google.mlkit.vision.documentscanner.GmsDocumentScannerOptions.RESULT_FORMAT_PDF
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> com.google.mlkit.vision.documentscanner.GmsDocumentScannerOptions.SCANNER_MODE_FULL
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Create scanner with options
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">val</span> options = <span style="color:#a6e22e">GmsDocumentScannerOptions</span>.Builder()
</span></span><span style="display:flex;"><span>    .setGalleryImportAllowed(<span style="color:#66d9ef">true</span>)              <span style="color:#75715e">// Allow importing from gallery
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    .setPageLimit(<span style="color:#ae81ff">10</span>)                            <span style="color:#75715e">// Max 10 pages per scan
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    .setResultFormats(RESULT_FORMAT_JPEG, RESULT_FORMAT_PDF)  <span style="color:#75715e">// Get both formats
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    .setScannerMode(SCANNER_MODE_FULL)           <span style="color:#75715e">// Full scanning experience
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    .build()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">val</span> scanner = <span style="color:#a6e22e">GmsDocumentScanning</span>.getClient(options)
</span></span></code></pre></div><p><strong>Scanner Modes:</strong></p>
<ul>
<li><code>SCANNER_MODE_FULL</code> - Complete UI with all features (recommended)</li>
<li><code>SCANNER_MODE_BASE</code> - Minimal UI, faster scanning</li>
</ul>
<hr>
<h3 id="step-3-launch-scanner-and-handle-results"><strong>Step 3: Launch Scanner and Handle Results</strong></h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-kotlin" data-lang="kotlin"><span style="display:flex;"><span><span style="color:#75715e">// Activity Result Launcher
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">private</span> <span style="color:#66d9ef">val</span> scannerLauncher = registerForActivityResult(
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">ActivityResultContracts</span>.StartIntentSenderForResult()
</span></span><span style="display:flex;"><span>) { result <span style="color:#f92672">-&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> (result.resultCode <span style="color:#f92672">==</span> RESULT_OK) {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">val</span> scanningResult = <span style="color:#a6e22e">GmsDocumentScanningResult</span>.fromActivityResultIntent(result.<span style="color:#66d9ef">data</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        scanningResult<span style="color:#f92672">?.</span>let { scanResult <span style="color:#f92672">-&gt;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#75715e">// Get scanned pages
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>            scanResult.pages<span style="color:#f92672">?.</span>let { pages <span style="color:#f92672">-&gt;</span>
</span></span><span style="display:flex;"><span>                pages.forEach { page <span style="color:#f92672">-&gt;</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#75715e">// Access page image URI
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>                    <span style="color:#66d9ef">val</span> imageUri = page.imageUri
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                    <span style="color:#75715e">// Use the scanned image
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>                    loadScannedImage(imageUri)
</span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#75715e">// Get PDF if generated
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>            scanResult.pdf<span style="color:#f92672">?.</span>let { pdf <span style="color:#f92672">-&gt;</span>
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">val</span> pdfUri = pdf.uri
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">val</span> pageCount = pdf.pageCount
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                <span style="color:#75715e">// Save or share PDF
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>                savePdfDocument(pdfUri, pageCount)
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Launch scanner
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">fun</span> <span style="color:#a6e22e">startDocumentScan</span>() {
</span></span><span style="display:flex;"><span>    scanner.getStartScanIntent(<span style="color:#66d9ef">this</span>)
</span></span><span style="display:flex;"><span>        .addOnSuccessListener { intentSender <span style="color:#f92672">-&gt;</span>
</span></span><span style="display:flex;"><span>            scannerLauncher.launch(
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">IntentSenderRequest</span>.Builder(intentSender).build()
</span></span><span style="display:flex;"><span>            )
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        .addOnFailureListener { exception <span style="color:#f92672">-&gt;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#75715e">// Handle error
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>            <span style="color:#a6e22e">Log</span>.e(<span style="color:#e6db74">&#34;Scanner&#34;</span>, <span style="color:#e6db74">&#34;Failed to start scanner&#34;</span>, exception)
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><hr>
<h3 id="step-4-complete-jetpack-compose-integration"><strong>Step 4: Complete Jetpack Compose Integration</strong></h3>
<p>Here&rsquo;s a production-ready composable implementation:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-kotlin" data-lang="kotlin"><span style="display:flex;"><span><span style="color:#a6e22e">@Composable</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">fun</span> <span style="color:#a6e22e">DocumentScannerButton</span>(
</span></span><span style="display:flex;"><span>    onDocumentsScanned: (List&lt;Uri&gt;) <span style="color:#f92672">-&gt;</span> Unit,
</span></span><span style="display:flex;"><span>    onPdfGenerated: (Uri, Int) <span style="color:#f92672">-&gt;</span> Unit,
</span></span><span style="display:flex;"><span>    modifier: Modifier = Modifier,
</span></span><span style="display:flex;"><span>    enabled: Boolean = <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">val</span> context = <span style="color:#a6e22e">LocalContext</span>.current
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">val</span> activity = context <span style="color:#66d9ef">as</span>? ComponentActivity
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Scanner options
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#66d9ef">val</span> scanner = remember {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">val</span> options = <span style="color:#a6e22e">GmsDocumentScannerOptions</span>.Builder()
</span></span><span style="display:flex;"><span>            .setGalleryImportAllowed(<span style="color:#66d9ef">true</span>)
</span></span><span style="display:flex;"><span>            .setPageLimit(<span style="color:#ae81ff">10</span>)
</span></span><span style="display:flex;"><span>            .setResultFormats(RESULT_FORMAT_JPEG, RESULT_FORMAT_PDF)
</span></span><span style="display:flex;"><span>            .setScannerMode(SCANNER_MODE_FULL)
</span></span><span style="display:flex;"><span>            .build()
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">GmsDocumentScanning</span>.getClient(options)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Result launcher
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#66d9ef">val</span> scannerLauncher = rememberLauncherForActivityResult(
</span></span><span style="display:flex;"><span>        contract = <span style="color:#a6e22e">ActivityResultContracts</span>.StartIntentSenderForResult()
</span></span><span style="display:flex;"><span>    ) { result <span style="color:#f92672">-&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> (result.resultCode <span style="color:#f92672">==</span> <span style="color:#a6e22e">ComponentActivity</span>.RESULT_OK) {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">val</span> scanResult = <span style="color:#a6e22e">GmsDocumentScanningResult</span>.fromActivityResultIntent(result.<span style="color:#66d9ef">data</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            scanResult<span style="color:#f92672">?.</span>let {
</span></span><span style="display:flex;"><span>                <span style="color:#75715e">// Handle scanned images
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>                <span style="color:#66d9ef">it</span>.pages<span style="color:#f92672">?.</span>let { pages <span style="color:#f92672">-&gt;</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#66d9ef">val</span> imageUris = pages.mapNotNull { page <span style="color:#f92672">-&gt;</span> page.imageUri }
</span></span><span style="display:flex;"><span>                    <span style="color:#66d9ef">if</span> (imageUris.isNotEmpty()) {
</span></span><span style="display:flex;"><span>                        onDocumentsScanned(imageUris)
</span></span><span style="display:flex;"><span>                    }
</span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                <span style="color:#75715e">// Handle PDF
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>                <span style="color:#66d9ef">it</span>.pdf<span style="color:#f92672">?.</span>let { pdf <span style="color:#f92672">-&gt;</span>
</span></span><span style="display:flex;"><span>                    onPdfGenerated(pdf.uri, pdf.pageCount)
</span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Launch scanner
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#66d9ef">fun</span> <span style="color:#a6e22e">launchScanner</span>() {
</span></span><span style="display:flex;"><span>        activity<span style="color:#f92672">?.</span>let { act <span style="color:#f92672">-&gt;</span>
</span></span><span style="display:flex;"><span>            scanner.getStartScanIntent(act)
</span></span><span style="display:flex;"><span>                .addOnSuccessListener { intentSender <span style="color:#f92672">-&gt;</span>
</span></span><span style="display:flex;"><span>                    scannerLauncher.launch(
</span></span><span style="display:flex;"><span>                        <span style="color:#a6e22e">IntentSenderRequest</span>.Builder(intentSender).build()
</span></span><span style="display:flex;"><span>                    )
</span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span>                .addOnFailureListener { exception <span style="color:#f92672">-&gt;</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#a6e22e">Log</span>.e(<span style="color:#e6db74">&#34;DocumentScanner&#34;</span>, <span style="color:#e6db74">&#34;Failed to start&#34;</span>, exception)
</span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    Button(
</span></span><span style="display:flex;"><span>        onClick = { launchScanner() },
</span></span><span style="display:flex;"><span>        enabled = enabled,
</span></span><span style="display:flex;"><span>        modifier = modifier
</span></span><span style="display:flex;"><span>    ) {
</span></span><span style="display:flex;"><span>        Icon(
</span></span><span style="display:flex;"><span>            imageVector = <span style="color:#a6e22e">Icons</span>.<span style="color:#a6e22e">Default</span>.DocumentScanner,
</span></span><span style="display:flex;"><span>            contentDescription = <span style="color:#66d9ef">null</span>,
</span></span><span style="display:flex;"><span>            modifier = <span style="color:#a6e22e">Modifier</span>.size(<span style="color:#ae81ff">20.</span>dp)
</span></span><span style="display:flex;"><span>        )
</span></span><span style="display:flex;"><span>        Spacer(modifier = <span style="color:#a6e22e">Modifier</span>.width(<span style="color:#ae81ff">8.</span>dp))
</span></span><span style="display:flex;"><span>        Text(<span style="color:#e6db74">&#34;Scan Document&#34;</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p><strong>Usage:</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-kotlin" data-lang="kotlin"><span style="display:flex;"><span><span style="color:#a6e22e">@Composable</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">fun</span> <span style="color:#a6e22e">DocumentUploadScreen</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">var</span> scannedImages <span style="color:#66d9ef">by</span> remember { mutableStateOf&lt;List&lt;Uri&gt;&gt;(emptyList()) }
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">var</span> pdfUri <span style="color:#66d9ef">by</span> remember { mutableStateOf&lt;Uri?&gt;(<span style="color:#66d9ef">null</span>) }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    Column(
</span></span><span style="display:flex;"><span>        modifier = Modifier
</span></span><span style="display:flex;"><span>            .fillMaxSize()
</span></span><span style="display:flex;"><span>            .padding(<span style="color:#ae81ff">16.</span>dp)
</span></span><span style="display:flex;"><span>    ) {
</span></span><span style="display:flex;"><span>        DocumentScannerButton(
</span></span><span style="display:flex;"><span>            onDocumentsScanned = { images <span style="color:#f92672">-&gt;</span>
</span></span><span style="display:flex;"><span>                scannedImages = images
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">Toast</span>.makeText(
</span></span><span style="display:flex;"><span>                    context,
</span></span><span style="display:flex;"><span>                    <span style="color:#e6db74">&#34;Scanned </span><span style="color:#e6db74">${images.size}</span><span style="color:#e6db74"> pages&#34;</span>,
</span></span><span style="display:flex;"><span>                    <span style="color:#a6e22e">Toast</span>.LENGTH_SHORT
</span></span><span style="display:flex;"><span>                ).show()
</span></span><span style="display:flex;"><span>            },
</span></span><span style="display:flex;"><span>            onPdfGenerated = { uri, pageCount <span style="color:#f92672">-&gt;</span>
</span></span><span style="display:flex;"><span>                pdfUri = uri
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">Toast</span>.makeText(
</span></span><span style="display:flex;"><span>                    context,
</span></span><span style="display:flex;"><span>                    <span style="color:#e6db74">&#34;PDF created with </span><span style="color:#e6db74">$pageCount</span><span style="color:#e6db74"> pages&#34;</span>,
</span></span><span style="display:flex;"><span>                    <span style="color:#a6e22e">Toast</span>.LENGTH_SHORT
</span></span><span style="display:flex;"><span>                ).show()
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>        )
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// Display scanned images
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>        LazyColumn {
</span></span><span style="display:flex;"><span>            items(scannedImages) { imageUri <span style="color:#f92672">-&gt;</span>
</span></span><span style="display:flex;"><span>                AsyncImage(
</span></span><span style="display:flex;"><span>                    model = imageUri,
</span></span><span style="display:flex;"><span>                    contentDescription = <span style="color:#e6db74">&#34;Scanned page&#34;</span>,
</span></span><span style="display:flex;"><span>                    modifier = Modifier
</span></span><span style="display:flex;"><span>                        .fillMaxWidth()
</span></span><span style="display:flex;"><span>                        .height(<span style="color:#ae81ff">200.</span>dp)
</span></span><span style="display:flex;"><span>                        .padding(vertical = <span style="color:#ae81ff">8.</span>dp)
</span></span><span style="display:flex;"><span>                )
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><hr>
<h2 id="real-world-use-cases">Real-World Use Cases</h2>
<h3 id="1-iddocument-verification"><strong>1. ID/Document Verification</strong></h3>
<p>Perfect for KYC (Know Your Customer) flows:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-kotlin" data-lang="kotlin"><span style="display:flex;"><span><span style="color:#a6e22e">@Composable</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">fun</span> <span style="color:#a6e22e">KYCDocumentUpload</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">var</span> idCardUri <span style="color:#66d9ef">by</span> remember { mutableStateOf&lt;Uri?&gt;(<span style="color:#66d9ef">null</span>) }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    Column {
</span></span><span style="display:flex;"><span>        Text(<span style="color:#e6db74">&#34;Upload ID Card&#34;</span>, style = <span style="color:#a6e22e">MaterialTheme</span>.typography.titleLarge)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        DocumentScannerButton(
</span></span><span style="display:flex;"><span>            onDocumentsScanned = { images <span style="color:#f92672">-&gt;</span>
</span></span><span style="display:flex;"><span>                idCardUri = images.firstOrNull()
</span></span><span style="display:flex;"><span>                <span style="color:#75715e">// Automatically extract text with ML Kit Text Recognition
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>                verifyIDDocument(idCardUri)
</span></span><span style="display:flex;"><span>            },
</span></span><span style="display:flex;"><span>            onPdfGenerated = { _, _ <span style="color:#f92672">-&gt;</span> }
</span></span><span style="display:flex;"><span>        )
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        idCardUri<span style="color:#f92672">?.</span>let { uri <span style="color:#f92672">-&gt;</span>
</span></span><span style="display:flex;"><span>            AsyncImage(
</span></span><span style="display:flex;"><span>                model = uri,
</span></span><span style="display:flex;"><span>                contentDescription = <span style="color:#e6db74">&#34;ID Card&#34;</span>,
</span></span><span style="display:flex;"><span>                modifier = <span style="color:#a6e22e">Modifier</span>.fillMaxWidth()
</span></span><span style="display:flex;"><span>            )
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h3 id="2-receiptinvoice-scanning"><strong>2. Receipt/Invoice Scanning</strong></h3>
<p>For expense tracking or accounting apps:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-kotlin" data-lang="kotlin"><span style="display:flex;"><span><span style="color:#a6e22e">@Composable</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">fun</span> <span style="color:#a6e22e">ExpenseReceiptScanner</span>(
</span></span><span style="display:flex;"><span>    onReceiptScanned: (Uri, String) <span style="color:#f92672">-&gt;</span> Unit
</span></span><span style="display:flex;"><span>) {
</span></span><span style="display:flex;"><span>    DocumentScannerButton(
</span></span><span style="display:flex;"><span>        onDocumentsScanned = { images <span style="color:#f92672">-&gt;</span>
</span></span><span style="display:flex;"><span>            images.firstOrNull()<span style="color:#f92672">?.</span>let { receiptUri <span style="color:#f92672">-&gt;</span>
</span></span><span style="display:flex;"><span>                <span style="color:#75715e">// Extract text from receipt
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>                <span style="color:#66d9ef">val</span> amount = extractReceiptAmount(receiptUri)
</span></span><span style="display:flex;"><span>                onReceiptScanned(receiptUri, amount)
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>        },
</span></span><span style="display:flex;"><span>        onPdfGenerated = { _, _ <span style="color:#f92672">-&gt;</span> }
</span></span><span style="display:flex;"><span>    )
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h3 id="3-multi-page-document-archival"><strong>3. Multi-page Document Archival</strong></h3>
<p>For scanning contracts, forms, or books:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-kotlin" data-lang="kotlin"><span style="display:flex;"><span><span style="color:#a6e22e">@Composable</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">fun</span> <span style="color:#a6e22e">DocumentArchiver</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">var</span> documentTitle <span style="color:#66d9ef">by</span> remember { mutableStateOf(<span style="color:#e6db74">&#34;&#34;</span>) }
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">var</span> savedPdfUri <span style="color:#66d9ef">by</span> remember { mutableStateOf&lt;Uri?&gt;(<span style="color:#66d9ef">null</span>) }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    Column {
</span></span><span style="display:flex;"><span>        OutlinedTextField(
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">value</span> = documentTitle,
</span></span><span style="display:flex;"><span>            onValueChange = { documentTitle = <span style="color:#66d9ef">it</span> },
</span></span><span style="display:flex;"><span>            label = { Text(<span style="color:#e6db74">&#34;Document Name&#34;</span>) }
</span></span><span style="display:flex;"><span>        )
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        DocumentScannerButton(
</span></span><span style="display:flex;"><span>            onDocumentsScanned = { images <span style="color:#f92672">-&gt;</span>
</span></span><span style="display:flex;"><span>                <span style="color:#75715e">// Individual pages available if needed
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>            },
</span></span><span style="display:flex;"><span>            onPdfGenerated = { pdfUri, pageCount <span style="color:#f92672">-&gt;</span>
</span></span><span style="display:flex;"><span>                <span style="color:#75715e">// Save PDF with title
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>                savedPdfUri = pdfUri
</span></span><span style="display:flex;"><span>                saveToDocuments(documentTitle, pdfUri)
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>        )
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h3 id="4-note-taking-apps"><strong>4. Note-Taking Apps</strong></h3>
<p>Scan handwritten notes or whiteboard content:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-kotlin" data-lang="kotlin"><span style="display:flex;"><span><span style="color:#a6e22e">@Composable</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">fun</span> <span style="color:#a6e22e">ScanAndConvertNotes</span>() {
</span></span><span style="display:flex;"><span>    DocumentScannerButton(
</span></span><span style="display:flex;"><span>        onDocumentsScanned = { images <span style="color:#f92672">-&gt;</span>
</span></span><span style="display:flex;"><span>            images.forEach { imageUri <span style="color:#f92672">-&gt;</span>
</span></span><span style="display:flex;"><span>                <span style="color:#75715e">// Use ML Kit Text Recognition
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>                extractHandwrittenText(imageUri) { text <span style="color:#f92672">-&gt;</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#75715e">// Convert to editable text
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>                    saveAsNote(text)
</span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>        },
</span></span><span style="display:flex;"><span>        onPdfGenerated = { _, _ <span style="color:#f92672">-&gt;</span> }
</span></span><span style="display:flex;"><span>    )
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><hr>
<h2 id="advanced-configuration-options">Advanced Configuration Options</h2>
<h3 id="custom-scanner-settings"><strong>Custom Scanner Settings</strong></h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-kotlin" data-lang="kotlin"><span style="display:flex;"><span><span style="color:#75715e">// Minimal scanner (faster, less features)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">val</span> minimalOptions = <span style="color:#a6e22e">GmsDocumentScannerOptions</span>.Builder()
</span></span><span style="display:flex;"><span>    .setScannerMode(SCANNER_MODE_BASE)
</span></span><span style="display:flex;"><span>    .setPageLimit(<span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>    .setResultFormats(RESULT_FORMAT_JPEG)
</span></span><span style="display:flex;"><span>    .setGalleryImportAllowed(<span style="color:#66d9ef">false</span>)
</span></span><span style="display:flex;"><span>    .build()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Professional scanner (all features)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">val</span> professionalOptions = <span style="color:#a6e22e">GmsDocumentScannerOptions</span>.Builder()
</span></span><span style="display:flex;"><span>    .setScannerMode(SCANNER_MODE_FULL)
</span></span><span style="display:flex;"><span>    .setPageLimit(<span style="color:#ae81ff">50</span>)                           <span style="color:#75715e">// Up to 50 pages
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    .setResultFormats(RESULT_FORMAT_JPEG, RESULT_FORMAT_PDF)
</span></span><span style="display:flex;"><span>    .setGalleryImportAllowed(<span style="color:#66d9ef">true</span>)
</span></span><span style="display:flex;"><span>    .build()
</span></span></code></pre></div><h3 id="handling-different-result-formats"><strong>Handling Different Result Formats</strong></h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-kotlin" data-lang="kotlin"><span style="display:flex;"><span>scanningResult<span style="color:#f92672">?.</span>let { result <span style="color:#f92672">-&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Option 1: Process individual images
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    result.pages<span style="color:#f92672">?.</span>forEach { page <span style="color:#f92672">-&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">val</span> imageUri = page.imageUri
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// Each page as separate image
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>        processImage(imageUri)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Option 2: Get consolidated PDF
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    result.pdf<span style="color:#f92672">?.</span>let { pdf <span style="color:#f92672">-&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">val</span> pdfUri = pdf.uri
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">val</span> pageCount = pdf.pageCount
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// Single PDF with all pages
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>        sharePDF(pdfUri)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><hr>
<h2 id="comparison-before-vs-after">Comparison: Before vs After</h2>
<h3 id="custom-camera-implementation-old-way"><strong>Custom Camera Implementation (Old Way)</strong></h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-kotlin" data-lang="kotlin"><span style="display:flex;"><span><span style="color:#75715e">// 500+ lines of code for:
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">CustomCameraActivity</span> : AppCompatActivity() {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">private</span> <span style="color:#66d9ef">lateinit</span> <span style="color:#66d9ef">var</span> cameraProvider: ProcessCameraProvider
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">private</span> <span style="color:#66d9ef">var</span> imageCapture: ImageCapture? = <span style="color:#66d9ef">null</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Camera setup
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#75715e">// Permission handling
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#75715e">// Custom UI overlay
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#75715e">// Manual cropping UI
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#75715e">// Image enhancement logic
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#75715e">// Edge detection algorithm
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#75715e">// Perspective correction math
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#75715e">// Multi-page management
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#75715e">// PDF generation
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#75715e">// Error handling
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#75715e">// ... 450+ more lines
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>}
</span></span></code></pre></div><p><strong>Problems:</strong></p>
<ul>
<li>500+ lines of complex code</li>
<li>Camera permission management</li>
<li>Device compatibility issues</li>
<li>Manual cropping UI needed</li>
<li>No auto enhancement</li>
<li>Mediocre results</li>
<li>Maintenance burden</li>
</ul>
<h3 id="ml-kit-document-scanner-new-way"><strong>ML Kit Document Scanner (New Way)</strong></h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-kotlin" data-lang="kotlin"><span style="display:flex;"><span><span style="color:#75715e">// 10 lines of code:
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">val</span> scanner = <span style="color:#a6e22e">GmsDocumentScanning</span>.getClient(options)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>scanner.getStartScanIntent(activity)
</span></span><span style="display:flex;"><span>    .addOnSuccessListener { intentSender <span style="color:#f92672">-&gt;</span>
</span></span><span style="display:flex;"><span>        launcher.launch(<span style="color:#a6e22e">IntentSenderRequest</span>.Builder(intentSender).build())
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Done! Professional scanning with AI.
</span></span></span></code></pre></div><p><strong>Benefits:</strong></p>
<ul>
<li>10 lines of code</li>
<li>No permissions needed</li>
<li>Works on all devices</li>
<li>Auto cropping included</li>
<li>AI enhancement</li>
<li>Professional results</li>
<li>Zero maintenance</li>
</ul>
<hr>
<h2 id="performance--best-practices">Performance &amp; Best Practices</h2>
<h3 id="library-size-impact"><strong>Library Size Impact</strong></h3>
<pre tabindex="0"><code>ML Kit Document Scanner:  ~3MB
Custom camera + CV libs:  ~15-25MB
</code></pre><p><strong>Worth it?</strong> Absolutely. You get professional features for 1/5th the size.</p>
<h3 id="memory-management"><strong>Memory Management</strong></h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-kotlin" data-lang="kotlin"><span style="display:flex;"><span><span style="color:#75715e">// Don&#39;t load all images at once
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>scannedImages.forEach { uri <span style="color:#f92672">-&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Process one at a time
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    processImage(uri)
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Or use paging for large batches
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Use Coil/Glide for efficient image loading
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>AsyncImage(
</span></span><span style="display:flex;"><span>    model = imageUri,
</span></span><span style="display:flex;"><span>    contentDescription = <span style="color:#66d9ef">null</span>,
</span></span><span style="display:flex;"><span>    contentScale = <span style="color:#a6e22e">ContentScale</span>.Fit
</span></span><span style="display:flex;"><span>)
</span></span></code></pre></div><h3 id="error-handling"><strong>Error Handling</strong></h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-kotlin" data-lang="kotlin"><span style="display:flex;"><span>scanner.getStartScanIntent(activity)
</span></span><span style="display:flex;"><span>    .addOnSuccessListener { intentSender <span style="color:#f92672">-&gt;</span>
</span></span><span style="display:flex;"><span>        launcher.launch(<span style="color:#a6e22e">IntentSenderRequest</span>.Builder(intentSender).build())
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    .addOnFailureListener { exception <span style="color:#f92672">-&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">when</span> (exception) {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">is</span> MlKitException <span style="color:#f92672">-&gt;</span> {
</span></span><span style="display:flex;"><span>                <span style="color:#75715e">// ML Kit specific error
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>                showError(<span style="color:#e6db74">&#34;Scanner unavailable: </span><span style="color:#e6db74">${exception.message}</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">else</span> <span style="color:#f92672">-&gt;</span> {
</span></span><span style="display:flex;"><span>                <span style="color:#75715e">// Generic error
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>                showError(<span style="color:#e6db74">&#34;Failed to start scanner&#34;</span>)
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span></code></pre></div><h3 id="testing-on-different-devices"><strong>Testing on Different Devices</strong></h3>
<p>ML Kit Document Scanner works on:</p>
<ul>
<li>✅ Android 5.0+ (API 21+)</li>
<li>✅ Devices with Google Play Services</li>
<li>✅ All form factors (phones, tablets)</li>
<li>✅ Various camera qualities</li>
</ul>
<p><strong>Note:</strong> Requires Google Play Services. Check availability:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-kotlin" data-lang="kotlin"><span style="display:flex;"><span><span style="color:#66d9ef">fun</span> <span style="color:#a6e22e">isDocumentScannerAvailable</span>(context: Context): Boolean {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">try</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">val</span> status = <span style="color:#a6e22e">GoogleApiAvailability</span>.getInstance()
</span></span><span style="display:flex;"><span>            .isGooglePlayServicesAvailable(context)
</span></span><span style="display:flex;"><span>        status <span style="color:#f92672">==</span> <span style="color:#a6e22e">ConnectionResult</span>.SUCCESS
</span></span><span style="display:flex;"><span>    } <span style="color:#66d9ef">catch</span> (e: Exception) {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">false</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><hr>
<h2 id="when-to-use-ml-kit-vs-custom-camera">When to Use ML Kit vs Custom Camera</h2>
<h3 id="use-ml-kit-document-scanner-when"><strong>Use ML Kit Document Scanner When:</strong></h3>
<p>✅ Scanning documents, receipts, IDs, contracts
✅ Need professional quality scans
✅ Want multi-page support
✅ Need PDF generation
✅ Auto enhancement required
✅ Limited development time/budget</p>
<h3 id="use-custom-camera-when"><strong>Use Custom Camera When:</strong></h3>
<p>⚠️ Capturing photos (not documents)
⚠️ Need real-time filters/effects
⚠️ Building a camera app
⚠️ Very specific custom workflow
⚠️ Can&rsquo;t use Google Play Services</p>
<p><strong>Bottom line:</strong> For 95% of document capture use cases, ML Kit is superior.</p>
<hr>
<h2 id="common-pitfalls-to-avoid">Common Pitfalls to Avoid</h2>
<h3 id="1-not-checking-for-play-services"><strong>1. Not Checking for Play Services</strong></h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-kotlin" data-lang="kotlin"><span style="display:flex;"><span><span style="color:#75715e">// ❌ BAD - Assumes availability
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>scanner.getStartScanIntent(activity)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// ✅ GOOD - Check first
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">if</span> (isDocumentScannerAvailable(context)) {
</span></span><span style="display:flex;"><span>    scanner.getStartScanIntent(activity)
</span></span><span style="display:flex;"><span>} <span style="color:#66d9ef">else</span> {
</span></span><span style="display:flex;"><span>    showFallbackOption()
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h3 id="2-ignoring-uri-permissions"><strong>2. Ignoring URI Permissions</strong></h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-kotlin" data-lang="kotlin"><span style="display:flex;"><span><span style="color:#75715e">// ✅ Grant persistent URI permissions
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>contentResolver.takePersistableUriPermission(
</span></span><span style="display:flex;"><span>    uri,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Intent</span>.FLAG_GRANT_READ_URI_PERMISSION
</span></span><span style="display:flex;"><span>)
</span></span></code></pre></div><h3 id="3-not-handling-page-limits"><strong>3. Not Handling Page Limits</strong></h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-kotlin" data-lang="kotlin"><span style="display:flex;"><span><span style="color:#75715e">// ✅ Set appropriate page limits
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">val</span> options = <span style="color:#a6e22e">GmsDocumentScannerOptions</span>.Builder()
</span></span><span style="display:flex;"><span>    .setPageLimit(
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> (multiPage) <span style="color:#ae81ff">50</span> <span style="color:#66d9ef">else</span> <span style="color:#ae81ff">1</span>  <span style="color:#75715e">// Adjust based on use case
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    )
</span></span><span style="display:flex;"><span>    .build()
</span></span></code></pre></div><hr>
<h2 id="integration-with-other-ml-kit-features">Integration with Other ML Kit Features</h2>
<h3 id="combine-with-text-recognition"><strong>Combine with Text Recognition</strong></h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-kotlin" data-lang="kotlin"><span style="display:flex;"><span><span style="color:#75715e">// Scan document, then extract text
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>onDocumentsScanned = { images <span style="color:#f92672">-&gt;</span>
</span></span><span style="display:flex;"><span>    images.forEach { imageUri <span style="color:#f92672">-&gt;</span>
</span></span><span style="display:flex;"><span>        recognizeText(imageUri) { extractedText <span style="color:#f92672">-&gt;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#75715e">// Use extracted text
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>            saveDocumentWithText(imageUri, extractedText)
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">fun</span> <span style="color:#a6e22e">recognizeText</span>(uri: Uri, onTextExtracted: (String) <span style="color:#f92672">-&gt;</span> Unit) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">val</span> image = <span style="color:#a6e22e">InputImage</span>.fromFilePath(context, uri)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">val</span> recognizer = <span style="color:#a6e22e">TextRecognition</span>.getClient(<span style="color:#a6e22e">TextRecognizerOptions</span>.DEFAULT_OPTIONS)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    recognizer.process(image)
</span></span><span style="display:flex;"><span>        .addOnSuccessListener { visionText <span style="color:#f92672">-&gt;</span>
</span></span><span style="display:flex;"><span>            onTextExtracted(visionText.text)
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h3 id="barcode-scanning-from-documents"><strong>Barcode Scanning from Documents</strong></h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-kotlin" data-lang="kotlin"><span style="display:flex;"><span><span style="color:#75715e">// Scan document with barcode/QR code
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>onDocumentsScanned = { images <span style="color:#f92672">-&gt;</span>
</span></span><span style="display:flex;"><span>    images.forEach { imageUri <span style="color:#f92672">-&gt;</span>
</span></span><span style="display:flex;"><span>        scanBarcode(imageUri) { barcodeValue <span style="color:#f92672">-&gt;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#75715e">// Handle barcode data
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><hr>
<h2 id="quick-implementation-checklist">Quick Implementation Checklist</h2>
<p>Before shipping document scanning to production:</p>
<ul>
<li><input disabled="" type="checkbox"> ✅ Added ML Kit dependency (~3MB)</li>
<li><input disabled="" type="checkbox"> 📱 Tested on devices with/without Play Services</li>
<li><input disabled="" type="checkbox"> 🔧 Configured appropriate scanner mode</li>
<li><input disabled="" type="checkbox"> 📄 Set reasonable page limits</li>
<li><input disabled="" type="checkbox"> 🎨 Handled both image and PDF results</li>
<li><input disabled="" type="checkbox"> ⚠️ Implemented error handling</li>
<li><input disabled="" type="checkbox"> 💾 Managed URI permissions properly</li>
<li><input disabled="" type="checkbox"> 🧪 Tested with various document types</li>
<li><input disabled="" type="checkbox"> 📏 Verified image quality/resolution</li>
<li><input disabled="" type="checkbox"> 🔄 Added loading states for scanning</li>
</ul>
<hr>
<h2 id="real-world-impact">Real-World Impact</h2>
<h3 id="before-ml-kit"><strong>Before ML Kit:</strong></h3>
<ul>
<li>⏱️ Users spent 2-3 minutes per document (capture, crop, adjust)</li>
<li>😤 30-40% required retakes due to poor quality</li>
<li>📉 High abandonment rates on document upload flows</li>
<li>🐛 Constant bug reports about scanning issues</li>
</ul>
<h3 id="after-ml-kit"><strong>After ML Kit:</strong></h3>
<ul>
<li>⚡ 20-30 seconds per document (all automatic)</li>
<li>✨ &lt;5% retake rate (AI handles most issues)</li>
<li>📈 50-70% improvement in completion rates</li>
<li>😊 Positive feedback about scanning experience</li>
</ul>
<hr>
<h2 id="-related-resources">🔗 Related Resources</h2>
<ul>
<li><a href="https://developers.google.com/ml-kit/vision/doc-scanner">ML Kit Document Scanner Documentation</a></li>
<li><a href="https://developers.google.com/ml-kit/vision/text-recognition">ML Kit Text Recognition</a></li>
<li><a href="https://developers.google.com/android/guides/setup">Google Play Services Setup</a></li>
</ul>
<hr>
<h2 id="-final-thoughts">💡 Final Thoughts</h2>
<p>Stop wasting time building custom document capture solutions. <strong>ML Kit Document Scanner gives you professional, AI-powered scanning with minimal code.</strong></p>
<p><strong>The math is simple:</strong></p>
<ul>
<li>🕐 Custom implementation: 2-3 weeks + ongoing maintenance</li>
<li>⚡ ML Kit integration: 2-3 hours + zero maintenance</li>
<li>🎯 Result quality: ML Kit wins every time</li>
</ul>
<p><strong>Key takeaways:</strong></p>
<ol>
<li><strong>Stop using basic camera capture</strong> for documents</li>
<li><strong>ML Kit is tiny</strong> (~3MB) for massive functionality</li>
<li><strong>10 lines of code</strong> beats 500+ lines</li>
<li><strong>Professional results</strong> without CV expertise</li>
<li><strong>Users notice the difference</strong> - completion rates improve significantly</li>
</ol>
<p>Your users deserve better than blurry, crooked photos. Give them professional document scanning with ML Kit.</p>
<hr>
<p><strong>That&rsquo;s it!</strong> You now have the knowledge to implement professional document scanning in your Android app. 🎉</p>
<p>Feel free to reach out via my social handles with questions or to share your implementation! 😊</p>
<p><strong>Happy scanning!</strong> 📄✨</p>
]]></content:encoded></item></channel></rss>