<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ Android - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ Android - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Wed, 06 May 2026 16:59:59 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/android/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How AOSP 16 Bluetooth Scanner Works: The Ultimate Guide ]]>
                </title>
                <description>
                    <![CDATA[ Ah, Bluetooth. The technology we all love to hate. It's like that one friend who's always just about to connect, but then... doesn't. For years, Android developers have been locked in a dramatic, often tragic, romance with Bluetooth. We've wrestled w... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-aosp-16-bluetooth-scanner-works-the-ultimate-guide/</link>
                <guid isPermaLink="false">6983ae630a7fef9ac2d90313</guid>
                
                    <category>
                        <![CDATA[ ble ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Bluetooth Low Energy ]]>
                    </category>
                
                    <category>
                        <![CDATA[ bluetooth ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Android ]]>
                    </category>
                
                    <category>
                        <![CDATA[ android app development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ scanner ]]>
                    </category>
                
                    <category>
                        <![CDATA[ handbook ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Nikheel Vishwas Savant ]]>
                </dc:creator>
                <pubDate>Wed, 04 Feb 2026 20:38:59 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1770234863523/44a1690e-ab8a-4f6b-a12b-2c2636947d8c.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Ah, Bluetooth. The technology we all love to hate. It's like that one friend who's always just about to connect, but then... doesn't.</p>
<p>For years, Android developers have been locked in a dramatic, often tragic, romance with Bluetooth. We've wrestled with its quirks, begged it to just work, and shed silent tears over its mysterious connection drops.</p>
<p>But what if I told you that things are about to get better? What if I told you that with Android 16, the Bluetooth gods have finally smiled upon us? It's not a dream, my friends. It's the AOSP 16 Bluetooth Scanner, and it's here to bring a new hope to our weary developer souls.</p>
<p>In this handbook, we're going on a journey. A journey into the heart of AOSP 16's new Bluetooth features. We'll laugh, we'll cry (hopefully from joy this time), and we'll learn how to wield these new powers for good. We'll explore the magic of passive scanning, the drama of bond loss reasons, and the sheer convenience of getting service UUIDs without all the usual fuss.</p>
<p>By the end of this epic saga, you'll be able to:</p>
<ul>
<li><p>Build a Bluetooth scanner that's so efficient, it's practically psychic.</p>
</li>
<li><p>Debug connection issues like a seasoned detective.</p>
</li>
<li><p>Impress your friends and colleagues with your newfound Bluetooth mastery.</p>
</li>
</ul>
<h3 id="heading-prerequisites"><strong>Prerequisites:</strong></h3>
<p>Before we dive in, it's a good idea to have a basic understanding of Android development and Kotlin. If you've ever tried to make two devices talk to each other and ended up wanting to throw your computer out the window, you're more than qualified.</p>
<p>So grab your favorite beverage, put on your coding cape, and let's get ready for the Bluetooth awakening!</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><p><a class="post-section-overview" href="#heading-a-brief-history-of-bluetooth-in-android">A Brief History of Bluetooth in Android</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-whats-new-in-aosp-16-the-three-musketeers">What's New in AOSP 16: The Three Musketeers</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-deep-dive-1-passive-scanning">Deep Dive #1: Passive Scanning</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-understanding-the-bluetoothlescanner">Understanding the BluetoothLeScanner</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-hands-on-building-your-first-passive-scanner">Hands-On: Building Your First Passive Scanner</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-deep-dive-2-bluetooth-bond-loss-reasons">Deep Dive #2: Bluetooth Bond Loss Reasons</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-deep-dive-3-service-uuids-from-advertisements">Deep Dive #3: Service UUIDs from Advertisements</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-advanced-topics-leveling-up-your-scanning-game">Advanced Topics: Leveling Up Your Scanning Game</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-real-world-use-cases-where-the-bluetooth-hits-the-road">Real-World Use Cases: Where the Bluetooth Hits the Road</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-api-version-checking-how-to-not-crash-your-app">API Version Checking: How to Not Crash Your App</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-testing-and-debugging-the-fun-part-said-no-one-ever">Testing and Debugging: The Fun Part (Said No One Ever)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-performance-and-best-practices-how-to-be-a-good-bluetooth-citizen">Performance and Best Practices: How to Be a Good Bluetooth Citizen</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion-the-future-is-passive-and-thats-okay">Conclusion: The Future is Passive (and That's Okay)</a></p>
</li>
</ol>
<h2 id="heading-a-brief-history-of-bluetooth-or-how-we-learned-to-stop-worrying-and-love-the-radio-waves">A Brief History of Bluetooth (Or: How We Learned to Stop Worrying and Love the Radio Waves)</h2>
<h3 id="heading-the-dark-ages-classic-bluetooth">The Dark Ages: Classic Bluetooth</h3>
<p>In the beginning, there was Classic Bluetooth. It was the digital equivalent of a loud, boisterous party guest. It could carry a lot of data (like your favorite tunes to a speaker), but it sure was a battery hog. It was great for streaming audio, but for small, infrequent data transfers? It was like using a fire hose to water a houseplant. Overkill, and frankly, a little messy.</p>
<p>Developers in this era spent their days wrestling with BluetoothAdapter, BluetoothDevice, and the dreaded BluetoothSocket. It was a time of great uncertainty, where a simple connection could take seconds, or... well, let's just say you could go make a cup of coffee. And the battery drain? Your users would watch their phone's power level plummet faster than a lead balloon.</p>
<h3 id="heading-the-renaissance-enter-bluetooth-low-energy-ble">The Renaissance: Enter Bluetooth Low Energy (BLE)</h3>
<p>Then, with Android 4.3, a new hero emerged: Bluetooth Low Energy, or BLE. This wasn't your dad’s Bluetooth. BLE was sleek, efficient, and mysterious. It was designed for short bursts of data, sipping power like a fine wine instead of chugging it.</p>
<p>BLE was the cool kid on the block. It introduced us to a whole new world of possibilities: heart-rate monitors, smart watches, and a million and one IoT devices that could run for months on a single coin-cell battery. It was a game-changer.</p>
<p>But with great power came... great complexity. We had to learn a whole new language of GATT, GAP, services, and characteristics. It was like going from writing simple scripts to composing a full-blown opera. The potential was huge, but the learning curve was steep.</p>
<h3 id="heading-the-problem-child-scanning">The Problem Child: Scanning</h3>
<p>And then there was scanning. The act of finding these new, power-sipping devices. In the early days of BLE, scanning was still a bit of a wild west. It was an active, noisy process. Your phone would shout into the void, "IS ANYONE OUT THERE?", and then listen for replies. This worked, but it was still a significant power drain, especially if your app needed to scan for long periods.</p>
<p>It was the classic developer dilemma: you need to find devices, but you don't want to be the reason your user's phone is dead by lunchtime. For years, we walked this tightrope, balancing the need for discovery with the desperate plea for battery life.</p>
<p>This is the world that AOSP 16 was born into. A world crying out for a better way to scan. A world ready for a hero. And that hero, my friends, is passive scanning. But more on that in a bit...</p>
<h2 id="heading-whats-new-in-aosp-16-spoiler-its-actually-cool">What's New in AOSP 16? (Spoiler: It's Actually Cool)</h2>
<p>Alright, let's get to the good stuff. What shiny new toys did the Android team give us in AOSP 16? It turns out, quite a few! But before we unwrap the presents, let's talk about the new delivery schedule, because even that is a little different now.</p>
<h3 id="heading-a-tale-of-two-releases">A Tale of Two Releases</h3>
<p>In a shocking plot twist, Android decided to grace us with two major API releases in 2025. First, we got the main event, Android 16 (codenamed "Baklava," because who doesn't love a good pastry?), which landed in Q2. This is your traditional, big-bang release with all the behavior changes you've come to know and love (or fear).</p>
<p>But then, in Q4, we get a surprise second act: a minor release, which is where our new Bluetooth goodies made their grand entrance. This release is all about new features and APIs, without the scary, app-breaking changes. It's like getting a free dessert after you've already paid the bill.</p>
<h3 id="heading-the-three-musketeers-of-bluetooth">The Three Musketeers of Bluetooth</h3>
<p>So, what did this Q4 release bring to the Bluetooth party? I'm glad you asked. It brought three new heroes, ready to save us from our Bluetooth woes. I call them... The Three Musketeers.</p>
<table><tbody><tr><td><p><strong>Feature</strong></p></td><td><p><strong>The Gist</strong></p></td><td><p><strong>Why You Should Care</strong></p></td></tr><tr><td><p><strong>Passive Scanning</strong></p></td><td><p>The ability to listen for Bluetooth devices without shouting at them.</p></td><td><p>Your app can now be a silent, battery-saving ninja.</p></td></tr><tr><td><p><strong>Bond Loss Reasons</strong></p></td><td><p>Finally, some closure on why your Bluetooth connections break up.</p></td><td><p>You can stop playing the guessing game and actually debug connection issues.</p></td></tr><tr><td><p><strong>Service UUID from Ads</strong></p></td><td><p>Grab a device's vital stats directly from its advertisement.</p></td><td><p>It's like speed dating for Bluetooth devices. Faster, more efficient connections.</p></td></tr></tbody></table>

<p>These aren't just minor tweaks, folks. These are quality-of-life improvements that will fundamentally change how we build and debug Bluetooth-enabled apps. It's as if the Android team actually listened to our collective cries for help. (I know, I'm shocked too.)</p>
<p>In the next few sections, we're going to get up close and personal with each of these new features. We'll dive into the code, explore the use cases, and learn how to harness their power. So, get ready to meet our first musketeer: the strong, silent type known as Passive Scanning.</p>
<h2 id="heading-deep-dive-1-passive-scanning">Deep Dive #1: Passive Scanning</h2>
<p>Imagine you're in a library. You're looking for a friend, but you don't know where they are. You have two options:</p>
<ul>
<li><p><strong>Active Scanning:</strong> You stand in the middle of the library and shout, "HEY, STEVE! ARE YOU HERE?" This is effective, but it's also loud, disruptive, and will get you kicked out by the librarian (who, in this analogy, is your user's battery).</p>
</li>
<li><p><strong>Passive Scanning:</strong> You quietly walk around the library, listening for your friend's distinctive, wheezing laugh. You don't say a word. You just listen. This is stealthy, efficient, and won't drain your social (or actual) battery.</p>
</li>
</ul>
<p>For years, Android's Bluetooth scanning has been the guy shouting in the library. But with AOSP 16, we can finally be the quiet listener. This is the magic of passive scanning.</p>
<h3 id="heading-active-vs-passive-the-technical-showdown">Active vs. Passive: The Technical Showdown</h3>
<p>In the world of BLE, devices send out little packets of information called "advertisements." It's their way of saying, "Hey, I'm here, and this is what I do!"</p>
<ul>
<li><p><strong>Active Scanning:</strong> When your phone performs an active scan, it hears an advertisement and then sends back a SCAN_REQ (Scan Request). It's basically saying, "Tell me more!" The peripheral device then replies with a SCAN_RSP (Scan Response), which contains extra information.</p>
</li>
<li><p><strong>Passive Scanning:</strong> With passive scanning, your phone hears the advertisement... and that's it. It doesn't send anything back. It just takes note of the initial advertisement and moves on. It's a one-way conversation.</p>
</li>
</ul>
<h3 id="heading-why-go-passive-the-power-of-silence">Why Go Passive? The Power of Silence</h3>
<p>So, why is this such a big deal? Two words: power consumption. Every time your phone's radio has to transmit something (like a SCAN_REQ), it uses energy. If your app is scanning for devices all the time, those little transmissions add up, and your user's battery pays the price.</p>
<p>By switching to passive scanning, you're telling the radio to just listen. No talking, just listening. This dramatically reduces the power needed for scanning, making it a perfect solution for apps that need to monitor for nearby devices over long periods.</p>
<h3 id="heading-the-code-how-to-become-a-bluetooth-ninja">The Code: How to Become a Bluetooth Ninja</h3>
<p>So, how do we implement this newfound stealth mode? It's surprisingly simple. It all comes down to the ScanSettings you use when you start your scan.</p>
<p>Previously, you might have done something like this:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> settings = ScanSettings.Builder()
    .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
    .build()
</code></pre>
<p>Now, with AOSP 16, we have a new option. To enable passive scanning, you simply set the scan type:</p>
<pre><code class="lang-kotlin"><span class="hljs-comment">// This is the magic line!</span>
.setScanMode(ScanSettings.SCAN_TYPE_PASSIVE)
</code></pre>
<p>Wait, that can't be right. The documentation says SCAN_TYPE_PASSIVE is a scan type, not a scan mode. And you're right! My apologies, I got a little too excited. The correct way to do this is by setting the scan mode to passive. Let's try that again.</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> settings = ScanSettings.Builder()
    <span class="hljs-comment">// The actual magic line!</span>
    .setScanMode(ScanSettings.SCAN_MODE_OPPORTUNISTIC) <span class="hljs-comment">// This is the closest to passive</span>
    .build()
</code></pre>
<p>Hold on, that's not quite right either. It seems I've gotten my wires crossed. Let's consult the official scrolls... Ah, here it is! The ScanSettings.Builder has a new method in Android 16 QPR2. It's not setScanMode, it's a whole new setting.</p>
<p>Let's get this right once and for all. Here is the correct way to enable passive scanning:</p>
<pre><code class="lang-kotlin"><span class="hljs-comment">// Available in Android 16 QPR2 and later</span>
<span class="hljs-keyword">val</span> settings = ScanSettings.Builder()
    <span class="hljs-comment">// This is the REAL magic line, I promise!</span>
    .setScanType(ScanSettings.SCAN_TYPE_PASSIVE) 
    .build()
</code></pre>
<p>And there you have it. With that one line, you've transformed your app from a loud, battery-guzzling tourist to a silent, efficient Bluetooth ninja. Your users' batteries will thank you.</p>
<p>Of course, there's a trade-off. Since you're not sending a SCAN_REQ, you won't get the extra data from the SCAN_RSP. But for many use cases, the initial advertisement is all you need. And the power savings are more than worth it.</p>
<p>Now that we've mastered the art of silent scanning, let's move on to the next piece of the puzzle: understanding the BluetoothLeScanner itself.</p>
<h2 id="heading-understanding-bluetoothlescanner-the-star-of-our-show">Understanding BluetoothLeScanner (The Star of Our Show)</h2>
<p>Before we can truly master the art of Bluetooth scanning, we must first understand our primary weapon: the BluetoothLeScanner. Think of it as the PKE Meter from Ghostbusters. It's the tool we use to detect the invisible energy (in our case, BLE advertisements) floating all around us. But how does this ghost-hunting gadget actually work?</p>
<h3 id="heading-the-architecture-a-peek-behind-the-curtain">The Architecture: A Peek Behind the Curtain</h3>
<p>At a high level, the process is pretty straightforward. Your app, living comfortably in its own little world, decides it wants to find some BLE devices. It grabs an instance of the BluetoothLeScanner and says, "Hey, go look for stuff."</p>
<p>Under the hood, a lot is happening. The BluetoothLeScanner talks to the Android Bluetooth stack (codenamed "Fluoride," which sounds like something your dentist would be very proud of). The stack then communicates with the device's Bluetooth controller, the actual hardware that does the sending and receiving of radio waves. It's a classic case of "it's more complicated than it looks."</p>
<h3 id="heading-the-alphabet-soup-gatt-gap-and-friends">The Alphabet Soup: GATT, GAP, and Friends</h3>
<p>When you venture into the world of BLE, you'll quickly run into a whole bunch of acronyms. Don't panic! They're not as scary as they look. The two most important ones to understand are GAP and GATT.</p>
<ul>
<li><p><strong>GAP (Generic Access Profile):</strong> This is all about how devices discover and connect to each other. Think of GAP as the bouncer at a nightclub. It decides who gets to talk to whom. It manages advertising (the device shouting "I'm here!") and scanning (your app listening for those shouts). Our BluetoothLeScanner is a key player in the GAP-verse.</p>
</li>
<li><p><strong>GATT (Generic Attribute Profile):</strong> Once two devices are connected, GATT takes over. It defines how they exchange data. Think of GATT as the actual conversation happening inside the nightclub. It's all about Services, Characteristics, and Descriptors. A device might have a "Heart Rate Service," which contains a "Heart Rate Measurement Characteristic." Your app reads from or writes to these characteristics to get the data it needs.</p>
</li>
</ul>
<p>For the purpose of scanning, we're mostly living in the world of GAP. We're the ones standing outside the club, listening for interesting advertisements.</p>
<h3 id="heading-the-scanning-lifecycle-a-dramatic-play-in-three-acts">The Scanning Lifecycle: A Dramatic Play in Three Acts</h3>
<p>The life of a Bluetooth scan is a simple, yet elegant, drama.</p>
<ul>
<li><p><strong>Act I:</strong> The Preparation. Your app decides it's time to scan. It gets the BluetoothLeScanner, creates a set of ScanFilters (to only find specific devices) and ScanSettings (to define how to scan, like our new passive mode), and defines a ScanCallback.</p>
</li>
<li><p><strong>Act II:</strong> The Scan. Your app calls startScan(). The Bluetooth radio springs to life, listening for advertisements that match your filters. When it finds one, it reports back to your app via the onScanResult() method in your ScanCallback.</p>
</li>
<li><p><strong>Act III:</strong> The End. When your app has had enough (or, more importantly, when you've found what you're looking for), it calls stopScan(). The radio powers down, and all is quiet once more. It's crucial to always stop your scan when you're done. A rogue scan is the number one cause of "my battery dies in an hour" complaints from users.</p>
</li>
</ul>
<p>And that's the BluetoothLeScanner in a nutshell. It's our gateway to the world of BLE discovery. It's powerful, it's complex, but as we're learning, it's getting smarter and more efficient with every new Android release. Now that we know our tool, let's get our hands dirty and build our first passive scanner!</p>
<h2 id="heading-hands-on-building-your-first-passive-scanner">Hands-On: Building Your First Passive Scanner</h2>
<p>Theory is great, but let's be honest, we're developers. We learn by doing (or by copying pasting from Stack Overflow). It's time to roll up our sleeves, fire up Android Studio, and build something. We're going to create a simple app that uses our newfound passive scanning powers to find nearby BLE devices.</p>
<h3 id="heading-step-1-the-permission-inquisition">Step 1: The Permission Inquisition</h3>
<p>Before we write a single line of Kotlin, we must appease the Android permission gods. This is a sacred and often frustrating ritual. For Bluetooth scanning, the rules have changed a bit over the years.</p>
<p>First, open your <code>AndroidManifest.xml</code> and add the following:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">uses-permission</span> <span class="hljs-attr">android:name</span>=<span class="hljs-string">"android.permission.BLUETOOTH"</span> /&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">uses-permission</span> <span class="hljs-attr">android:name</span>=<span class="hljs-string">"android.permission.BLUETOOTH_ADMIN"</span> /&gt;</span>

<span class="hljs-comment">&lt;!-- For Android 12 (API 31) and above --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">uses-permission</span> <span class="hljs-attr">android:name</span>=<span class="hljs-string">"android.permission.BLUETOOTH_SCAN"</span> /&gt;</span>

<span class="hljs-comment">&lt;!-- For older versions, you needed location permissions --&gt;</span>
<span class="hljs-comment">&lt;!-- You might still need this if you support older devices --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">uses-permission</span> <span class="hljs-attr">android:name</span>=<span class="hljs-string">"android.permission.ACCESS_FINE_LOCATION"</span> /&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">uses-permission</span> <span class="hljs-attr">android:name</span>=<span class="hljs-string">"android.permission.ACCESS_COARSE_LOCATION"</span> /&gt;</span>
</code></pre>
<p>Looking at the permissions we've declared above, you can see the evolution of Android's Bluetooth permission model playing out in real-time.</p>
<p>The first two permissions, <code>BLUETOOTH</code> and <code>BLUETOOTH_ADMIN</code>, are the old guard. They've been around since the early days of Android and provide basic Bluetooth functionality and the ability to discover devices. Then we have <code>BLUETOOTH_SCAN</code>, which was introduced in Android 12 (API 31) and represents a major shift in how Google thinks about privacy.</p>
<p>Yes, you're seeing that right. In the good old days (before Android 12), Google decided that finding a Bluetooth device was basically the same as knowing your user's exact location. It kind of made sense: after all, if you can see which Bluetooth beacons are nearby, you can triangulate your position. But it was also a bit creepy to ask for location just to find a pair of headphones. This led to the awkward situation where users would see a simple Bluetooth scanner app asking for their precise location and understandably get suspicious.</p>
<p>Thankfully, with Android 12, they introduced the <code>BLUETOOTH_SCAN</code> permission, which is much more sensible. This permission finally allows apps to scan for Bluetooth devices without needing to ask for location access, which makes a lot more sense from a user perspective. You'll still need to request this permission at runtime, but at least you don't have to explain to your users why your simple gadget-finder app wants to know where they live.</p>
<p>However, notice those last two permissions for location access. Those are the remnants of the old system. If you're building an app that needs to support older devices running Android 11 or below, you'll need to keep these location permissions in your manifest for backwards compatibility. On modern devices, the <code>BLUETOOTH_SCAN</code> permission alone will do the job.</p>
<h3 id="heading-step-2-the-code-awakens">Step 2: The Code Awakens</h3>
<p>Alright, let's get to the fun part. Here's a breakdown of how to implement the passive scanner in your Activity or Fragment.</p>
<h4 id="heading-get-the-scanner">Get the Scanner</h4>
<p>First, we need to get an instance of the BluetoothLeScanner.</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> bluetoothAdapter: BluetoothAdapter? <span class="hljs-keyword">by</span> lazy {
    <span class="hljs-keyword">val</span> bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) <span class="hljs-keyword">as</span> BluetoothManager
    bluetoothManager.adapter
}

<span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> bleScanner: BluetoothLeScanner? <span class="hljs-keyword">by</span> lazy {
    bluetoothAdapter?.bluetoothLeScanner
}
</code></pre>
<p>Let's break down what's happening in the code above. We're using Kotlin's <code>lazy</code> delegation, which is a fancy way of saying "don't create this object until I actually need it." This is a good practice because getting the Bluetooth adapter involves system calls, and there's no point in doing that work if we never actually use it.</p>
<p>First, we grab the <code>BluetoothManager</code> from the system services. Think of the <code>BluetoothManager</code> as the gatekeeper to all things Bluetooth on your device. From this manager, we get the <code>BluetoothAdapter</code>, which represents your device's physical Bluetooth hardware. Notice that we're declaring it as nullable (<code>BluetoothAdapter?</code>) because, believe it or not, not every Android device has Bluetooth. Some tablets or obscure devices might not have the hardware, so we need to be prepared for that possibility.</p>
<p>Once we have the adapter, we can ask it for the <code>BluetoothLeScanner</code>. This is the actual object we'll use to perform our scans. Again, we're using the safe call operator (<code>?.</code>) because if the adapter is null (no Bluetooth hardware), we definitely can't get a scanner from it. This defensive programming might seem paranoid, but it's what separates apps that crash mysteriously from apps that gracefully handle edge cases.</p>
<h4 id="heading-define-the-callback">Define the Callback</h4>
<p>This is where the magic happens. The ScanCallback is an object that will listen for scan results. We need to override two methods: onScanResult and onScanFailed.</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> scanCallback = <span class="hljs-keyword">object</span> : ScanCallback() {
    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onScanResult</span><span class="hljs-params">(callbackType: <span class="hljs-type">Int</span>, result: <span class="hljs-type">ScanResult</span>)</span></span> {
        <span class="hljs-comment">// We found a device! </span>
        <span class="hljs-comment">// The 'result' object contains the device, RSSI, and advertisement data.</span>
        Log.d(<span class="hljs-string">"BleScanner"</span>, <span class="hljs-string">"Found device: <span class="hljs-subst">${result.device.address}</span>, RSSI: <span class="hljs-subst">${result.rssi}</span>"</span>)
    }

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onScanFailed</span><span class="hljs-params">(errorCode: <span class="hljs-type">Int</span>)</span></span> {
        <span class="hljs-comment">// This is the universe's way of telling you to take a break.</span>
        <span class="hljs-comment">// Or that something went horribly wrong.</span>
        Log.e(<span class="hljs-string">"BleScanner"</span>, <span class="hljs-string">"Scan failed with error code: <span class="hljs-variable">$errorCode</span>"</span>)
    }
}
</code></pre>
<p>The <code>ScanCallback</code> we've defined above is your app's ears in the Bluetooth world. When the scanner finds a device, it doesn't just store the information somewhere, it actively calls back to your app through this callback object. This is classic event-driven programming, and it's how Android keeps your app responsive without blocking the main thread.</p>
<p>The <code>onScanResult</code> method is called every time the scanner discovers a device that matches your filters (or any device if you're not using filters). The <code>result</code> parameter is a treasure trove of information. It contains the <code>BluetoothDevice</code> object (which has the device's MAC address and name), the RSSI value (Received Signal Strength Indicator – basically how close the device is, with higher numbers meaning closer), and the raw advertisement data that the device is broadcasting.</p>
<p>In our simple example above, we're just logging the MAC address and RSSI, but in a real app, you'd probably want to update your UI, add the device to a list, or trigger a connection.</p>
<p>The <code>callbackType</code> parameter tells you <em>why</em> this callback was triggered. It could be <code>CALLBACK_TYPE_ALL_MATCHES</code> (the default, meaning "here's every device we found"), <code>CALLBACK_TYPE_FIRST_MATCH</code> (the first time we saw this device), or <code>CALLBACK_TYPE_MATCH_LOST</code> (we haven't seen this device in a while, so it probably left). We'll dive deeper into these types in the advanced section.</p>
<p>Then there's <code>onScanFailed</code>, the method we all hope never gets called but that we absolutely need to handle. This is invoked when something goes catastrophically wrong with the scan. Maybe the Bluetooth adapter got turned off mid-scan, maybe your app doesn't have the right permissions, or maybe the Bluetooth controller just had a bad day. The <code>errorCode</code> will give you a hint about what went wrong, and you should always log this and handle it gracefully – perhaps by showing a message to the user or attempting to restart the scan after a delay.</p>
<h4 id="heading-configure-the-scan">Configure the Scan</h4>
<p>Now, we create our ScanSettings. This is where we tell Android that we want to be a passive, battery-saving ninja.</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> scanSettings = ScanSettings.Builder()
    .setScanMode(ScanSettings.SCAN_MODE_LOW_POWER) <span class="hljs-comment">// Let's be nice to the battery</span>
    .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
    .setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE)
    .setNumOfMatches(ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT) <span class="hljs-comment">// Report each ad once</span>
    .setReportDelay(<span class="hljs-number">0L</span>) <span class="hljs-comment">// Report immediately</span>
    <span class="hljs-comment">// And here's the star of the show!</span>
    .setScanType(ScanSettings.SCAN_TYPE_PASSIVE)
    .build()
</code></pre>
<p>The <code>ScanSettings</code> object we're building above is like a detailed instruction manual for the Bluetooth scanner. Each method call fine-tunes exactly how the scan should behave, and getting these settings right is the difference between a battery-friendly app and one that gets uninstalled within hours.</p>
<p>Let's walk through each setting. First, <code>setScanMode(SCAN_MODE_LOW_POWER)</code> tells the scanner to use a low-power scanning mode, which means it will scan in intervals rather than continuously. This is perfect for most use cases where you don't need instant results and want to preserve battery life. The scanner will wake up, scan for a bit, sleep, and repeat. It's the Bluetooth equivalent of taking power naps.</p>
<p>Next, <code>setCallbackType(CALLBACK_TYPE_ALL_MATCHES)</code> means we want to be notified every time the scanner finds a matching device. This is the default behavior and is what you'll use most of the time. As we mentioned earlier, you can also use <code>CALLBACK_TYPE_FIRST_MATCH</code> or <code>CALLBACK_TYPE_MATCH_LOST</code> for more sophisticated presence detection.</p>
<p>The <code>setMatchMode(MATCH_MODE_AGGRESSIVE)</code> setting controls how aggressively the hardware should try to match devices against your filters. <code>MATCH_MODE_AGGRESSIVE</code> means "report matches quickly, even if you're not 100% certain," while <code>MATCH_MODE_STICKY</code> means "wait until you're really sure before reporting." Aggressive mode gives you faster results but might occasionally give you false positives.</p>
<p>Then we have <code>setNumOfMatches(MATCH_NUM_ONE_ADVERTISEMENT)</code>, which tells the scanner to report a device after seeing just one advertisement from it. The alternative is <code>MATCH_NUM_FEW_ADVERTISEMENT</code>, which waits for multiple advertisements before reporting. Using one advertisement gives you faster discovery, while waiting for a few reduces false positives from devices that are just passing by.</p>
<p>The <code>setReportDelay(0L)</code> setting is crucial. A delay of <code>0</code> means "report results immediately." If you set this to, say, <code>5000</code> milliseconds, the scanner would batch up results and deliver them every 5 seconds. Batching is great for background scanning (as we discussed in the advanced section), but for foreground scanning where the user is actively waiting, immediate reporting is what you want.</p>
<p>And finally, the star of our show: <code>setScanType(SCAN_TYPE_PASSIVE)</code>. This is the new API from Android 16 QPR2 that transforms our scanner into a silent listener. Instead of actively sending scan requests to every device it hears, it just listens to the advertisements floating through the air. This single setting can dramatically reduce your app's battery consumption during scanning. It's the feature we've been waiting for, and it's glorious.</p>
<h4 id="heading-start-and-stop-the-scan">Start and Stop the Scan</h4>
<p>Finally, we need functions to start and stop our scan. Remember: always stop your scan! A forgotten scan is a battery-killing monster.</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">startBleScan</span><span class="hljs-params">()</span></span> {
    <span class="hljs-comment">// Don't forget to request permissions first!</span>
    <span class="hljs-keyword">if</span> (bleScanner != <span class="hljs-literal">null</span>) {
        <span class="hljs-comment">// You can add ScanFilters here to search for specific devices</span>
        <span class="hljs-keyword">val</span> scanFilters: List&lt;ScanFilter&gt; = listOf() 
        bleScanner.startScan(scanFilters, scanSettings, scanCallback)
        Log.d(<span class="hljs-string">"BleScanner"</span>, <span class="hljs-string">"Scan started."</span>)
    } <span class="hljs-keyword">else</span> {
        Log.e(<span class="hljs-string">"BleScanner"</span>, <span class="hljs-string">"Bluetooth is not available."</span>)
    }
}

<span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">stopBleScan</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">if</span> (bleScanner != <span class="hljs-literal">null</span>) {
        bleScanner.stopScan(scanCallback)
        Log.d(<span class="hljs-string">"BleScanner"</span>, <span class="hljs-string">"Scan stopped."</span>)
    }
}
</code></pre>
<p>These two functions above are the on/off switches for your Bluetooth scanner, and they're deceptively simple for how important they are. Let's break down what's happening in each one.</p>
<p>In <code>startBleScan()</code>, we first check if the <code>bleScanner</code> is not null. This is our safety net: if the device doesn't have Bluetooth hardware or if Bluetooth is disabled, the scanner will be null, and we don't want to crash by trying to call methods on a null object. If the scanner exists, we call <code>startScan()</code> with three parameters: a list of <code>ScanFilter</code> objects, our carefully crafted <code>ScanSettings</code>, and the <code>ScanCallback</code> we defined earlier.</p>
<p>The <code>scanFilters</code> list is currently empty in our example, which means "find all BLE devices." In a real-world app, you'd typically add filters here to narrow down your search.</p>
<p>For instance, if you're building an app that only works with heart rate monitors, you'd create a filter that only matches devices advertising the Heart Rate Service UUID. This is crucial for both performance and battery life: why wake up your app for every random Bluetooth toothbrush when you only care about fitness trackers?</p>
<p>The <code>startScan()</code> method kicks off the scanning process. From this point on, the Bluetooth radio is actively (or in our case, passively) listening for advertisements, and your <code>scanCallback</code> will start receiving results. This is an asynchronous operation, meaning your code doesn't block here waiting for results – rather, it continues executing, and the results come in through the callback whenever they're available.</p>
<p>Now let's talk about <code>stopBleScan()</code>, which might be the most important function you write. When you call <code>stopScan()</code> with your callback, you're telling the Bluetooth radio, "Okay, we're done here, you can go back to sleep." This immediately stops the scanning process and releases the resources.</p>
<p>The critical thing to understand is that if you don't call this, the scan will continue running indefinitely, draining your user's battery like a vampire at an all-you-can-eat blood bank. This is why we emphasize it so much: a forgotten <code>stopScan()</code> call is one of the most common causes of battery drain complaints in Bluetooth apps.</p>
<p>Notice that we're passing the same <code>scanCallback</code> object to <code>stopScan()</code> that we used in <code>startScan()</code>. This is how Android knows which scan to stop – you might theoretically have multiple scans running with different callbacks (though that's rarely a good idea). Always make sure you're stopping the same scan you started by using the same callback reference.</p>
<h3 id="heading-putting-it-all-together">Putting It All Together</h3>
<p>Here's a complete example you can drop into an Activity. Just remember to handle the runtime permissions!</p>
<pre><code class="lang-kotlin"><span class="hljs-comment">// In your Activity class</span>

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MainActivity</span> : <span class="hljs-type">AppCompatActivity</span></span>() {

    <span class="hljs-comment">// ... (lazy properties for adapter and scanner from above)</span>

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onCreate</span><span class="hljs-params">(savedInstanceState: <span class="hljs-type">Bundle</span>?)</span></span> {
        <span class="hljs-keyword">super</span>.onCreate(savedInstanceState)
        <span class="hljs-comment">// ... your UI setup ...</span>

        <span class="hljs-comment">// Example: Start scan on button click</span>
        <span class="hljs-keyword">val</span> startButton = findViewById&lt;Button&gt;(R.id.startButton)
        startButton.setOnClickListener {
            <span class="hljs-comment">// You MUST request permissions before calling this!</span>
            startBleScan()
        }

        <span class="hljs-comment">// Example: Stop scan on another button click</span>
        <span class="hljs-keyword">val</span> stopButton = findViewById&lt;Button&gt;(R.id.stopButton)
        stopButton.setOnClickListener {
            stopBleScan()
        }
    }

    <span class="hljs-comment">// ... (scanCallback, startBleScan, stopBleScan functions from above)</span>

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onPause</span><span class="hljs-params">()</span></span> {
        <span class="hljs-keyword">super</span>.onPause()
        <span class="hljs-comment">// Always stop scanning when the activity is not visible.</span>
        stopBleScan()
    }
}
</code></pre>
<p>The complete example above shows how all the pieces fit together in a real Activity. This is a minimal but functional Bluetooth scanner that you can actually run. Let's highlight a few important patterns we're using here.</p>
<p>First, notice how we're tying the scan lifecycle to user actions through button clicks. This is a common pattern: the user explicitly starts and stops the scan, giving them control over when the app is using Bluetooth. This is both good UX and good for battery life, as the scan only runs when the user wants it to.</p>
<p>But here's the really important part: the <code>onPause()</code> override. This is a critical safety net. When your Activity goes into the background (maybe the user pressed the home button, or they switched to another app), <code>onPause()</code> is called, and we immediately stop the scan. This is essential because if the user can't see your app, they don't need scan results, and there's no reason to drain their battery. This pattern ensures that even if the user forgets to press the "Stop" button, the scan won't run forever in the background.</p>
<p>You might be wondering, "What about <code>onResume()</code>? Shouldn't we restart the scan when the user comes back?" That's a design decision. In some apps, you might want to automatically restart scanning in <code>onResume()</code>. In others, you might want the user to explicitly press "Start" again. It depends on your use case. For a device-finding app where the user is actively searching, auto-resuming makes sense. For a monitoring app that runs in the background, you might want more explicit control.</p>
<p>One crucial thing we haven't shown in this example is runtime permission handling. Remember those permissions we declared in the manifest? On Android 6.0 and above, you can't just declare them, you have to actually request them from the user at runtime. Before calling <code>startBleScan()</code>, you should check if you have the necessary permissions and, if not, request them using <code>ActivityCompat.requestPermissions()</code>. If you try to start a scan without the proper permissions, it will fail silently (or loudly, depending on the Android version), and you'll be left scratching your head wondering why nothing is working.</p>
<p>And there you have it! You've just built your first AOSP 16 passive Bluetooth scanner. It's lean, it's mean, and it's incredibly power-efficient. The scanner listens silently for BLE advertisements, reports them through your callback, and stops gracefully when it's not needed.</p>
<p>Now, let's move on to our next topic: what to do when things go wrong. It's time to talk about breakups... Bluetooth bond breakups, that is.</p>
<h2 id="heading-deep-dive-2-bluetooth-bond-loss-reasons">Deep Dive #2: Bluetooth Bond Loss Reasons</h2>
<p>Ah, the Bluetooth bond. It's a beautiful, sacred thing. It's the digital equivalent of exchanging friendship bracelets. When you bond your phone with your headphones, you're creating a long-term, trusted relationship. They share secret keys, they remember each other, and they promise to connect automatically, saving you the hassle of pairing every single time. It's a beautiful romance.</p>
<p>Until it's not.</p>
<p>Suddenly, one day, they just... forget each other. The connection is gone. The trust is broken. And your app is left in the middle, trying to play therapist, with no idea what went wrong. You've been ghosted. And until now, Android has been no help. You'd get a notification that the bond state is now BOND_NONE, but that's it. No explanation. No closure. Just the cold, hard silence of a failed connection.</p>
<h3 id="heading-finally-some-closure">Finally, Some Closure!</h3>
<p>But our friends on the Android team have clearly been through some tough breakups, because in AOSP 16, they've given us the gift of closure. Introducing BluetoothDevice.EXTRA_BOND_LOSS_REASON. It's a new extra that comes with the ACTION_BOND_STATE_CHANGED broadcast, and it's here to tell you why the bond was lost. It's like getting a breakup text that actually explains what happened!</p>
<p>Now, when a bond is broken, you can get a specific reason code. Think of them as the classic breakup excuses, but for Bluetooth:</p>
<table><tbody><tr><td><p><strong>Reason Code (Illustrative)</strong></p></td><td><p><strong>What it Actually Means</strong></p></td></tr><tr><td><p>BOND_LOSS_REASON_BREDR_AUTH_FAILURE</p></td><td><p>Indicates that the reason for the bond loss is BREDR authentication failure.</p></td></tr><tr><td><p>BOND_LOSS_REASON_BREDR_INCOMING_PAIRING</p></td><td><p>Indicates that the reason for the bond loss is BREDR pairing failure.</p></td></tr><tr><td><p>BOND_LOSS_REASON_LE_ENCRYPT_FAILURE</p></td><td><p>Indicates that the reason for the bond loss is LE encryption failure.</p></td></tr><tr><td><p>BOND_LOSS_REASON_LE_INCOMING_PAIRING</p></td><td><p>Indicates that the reason for the bond loss is LE pairing failure.</p></td></tr></tbody></table>

<h3 id="heading-the-code-playing-detective">The Code: Playing Detective</h3>
<p>So, how do we get this juicy gossip? We need to set up a BroadcastReceiver to listen for bond state changes.</p>
<pre><code class="lang-kotlin"><span class="hljs-comment">// Create a BroadcastReceiver to listen for bond state changes</span>
<span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> bondStateReceiver = <span class="hljs-keyword">object</span> : BroadcastReceiver() {
    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onReceive</span><span class="hljs-params">(context: <span class="hljs-type">Context</span>, intent: <span class="hljs-type">Intent</span>)</span></span> {
        <span class="hljs-keyword">if</span> (intent.action == BluetoothDevice.ACTION_BOND_STATE_CHANGED) {
            <span class="hljs-keyword">val</span> device: BluetoothDevice? = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)
            <span class="hljs-keyword">val</span> bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.ERROR)
            <span class="hljs-keyword">val</span> previousBondState = intent.getIntExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, BluetoothDevice.ERROR)

            <span class="hljs-comment">// Check if we went from bonded to not bonded</span>
            <span class="hljs-keyword">if</span> (bondState == BluetoothDevice.BOND_NONE &amp;&amp; previousBondState == BluetoothDevice.BOND_BONDED) {
                Log.d(<span class="hljs-string">"BondBreakup"</span>, <span class="hljs-string">"We got dumped by <span class="hljs-subst">${device?.address}</span>!"</span>)

                <span class="hljs-comment">// Now, let's find out why...</span>
                <span class="hljs-keyword">val</span> reason = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_LOSS_REASON, -<span class="hljs-number">1</span>)

                <span class="hljs-keyword">when</span> (reason) {
                    <span class="hljs-comment">// Note: The actual constant values are in the Android SDK</span>
                    BluetoothDevice.BOND_LOSS_REASON_REMOTE_DEVICE_REMOVED -&gt; {
                        Log.d(<span class="hljs-string">"BondBreakup"</span>, <span class="hljs-string">"Reason: The remote device removed the bond."</span>)
                        <span class="hljs-comment">// You could show a message to the user: "Your headphones seem to have forgotten you. Please try pairing again."</span>
                    }
                    <span class="hljs-comment">// ... handle other reasons ...</span>
                    <span class="hljs-keyword">else</span> -&gt; {
                        Log.d(<span class="hljs-string">"BondBreakup"</span>, <span class="hljs-string">"Reason: It's complicated (Unknown reason code: <span class="hljs-variable">$reason</span>)"</span>)
                    }
                }
            }
        }
    }
}

<span class="hljs-comment">// In your Activity or Service, register the receiver</span>
<span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onResume</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">super</span>.onResume()
    <span class="hljs-keyword">val</span> filter = IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED)
    registerReceiver(bondStateReceiver, filter)
}

<span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onPause</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">super</span>.onPause()
    <span class="hljs-comment">// Don't forget to unregister!</span>
    unregisterReceiver(bondStateReceiver)
}
</code></pre>
<p>The code above implements a detective system for Bluetooth bond breakups, and it's more sophisticated than it might first appear. Let's walk through how this broadcast receiver pattern works and why it's so powerful.</p>
<p>First, we're creating a <code>BroadcastReceiver</code>, which is Android's way of letting your app listen for system-wide events. Think of it as subscribing to a notification service, whenever something interesting happens in the Android system (like a bond state change), the system broadcasts an "intent" to all registered listeners. Our receiver is one of those listeners.</p>
<p>In the <code>onReceive()</code> method, we first check if the incoming intent's action is <code>ACTION_BOND_STATE_CHANGED</code>. This is crucial because broadcast receivers can potentially receive many different types of intents, and we only care about bond state changes. Once we've confirmed this is the right type of event, we extract the relevant information from the intent using <code>getParcelableExtra()</code> and <code>getIntExtra()</code>.</p>
<p>The <code>device</code> object tells us which Bluetooth device this event is about. After all, you might be bonded to multiple devices (your headphones, your smartwatch, your car), and we need to know which one just broke up with us. The <code>bondState</code> tells us the current state (are we bonded, bonding, or not bonded?), and <code>previousBondState</code> tells us what the state was before this change occurred.</p>
<p>The key logic happens in our conditional check: <code>if (bondState == BluetoothDevice.BOND_NONE &amp;&amp; previousBondState == BluetoothDevice.BOND_BONDED)</code>. This is checking for the specific transition from "bonded" to "not bonded," which is the digital equivalent of a breakup. We're not interested in the bonding process itself (going from none to bonding to bonded) – we only care about when an existing bond is lost.</p>
<p>Once we've detected a breakup, we extract the new <code>EXTRA_BOND_LOSS_REASON</code> from the intent. This is the star feature from AOSP 16 that finally gives us closure. The reason code tells us exactly why the bond was lost – was it the remote device that ended things? Did the user manually forget the device? Did authentication fail? Each reason code corresponds to a different scenario, and you can handle each one appropriately.</p>
<p>In the example above, we're using a when expression to handle different reason codes. For BOND_LOSS_REASON_BREDR_INCOMING_PAIRING, we know the other device initiated the breakup, so we can show a helpful message like "Your headphones seem to have forgotten you. Please try pairing again." For other reasons, you'd add more branches to handle them specifically.</p>
<p>Now, notice the lifecycle management at the bottom. We register our receiver in <code>onResume()</code> and unregister it in <code>onPause()</code>. This is critical: if you forget to unregister a broadcast receiver, it will continue to receive broadcasts even after your Activity is destroyed, which can cause memory leaks and crashes. The pattern of registering in <code>onResume()</code> and unregistering in <code>onPause()</code> ensures that we only listen for bond changes when our Activity is visible and active.</p>
<p>This is a huge step forward for debugging and for user experience. Instead of just telling the user "Connection failed," you can now give them actionable advice based on the specific reason the bond was lost. It's like being a helpful, informed relationship counselor instead of a confused bystander who can only shrug and say "I don't know what happened."</p>
<p>Now that we've dealt with the emotional baggage of breakups, let's move on to something a little more lighthearted: speed dating for Bluetooth devices.</p>
<h2 id="heading-deep-dive-3-service-uuids-from-advertisements">Deep Dive #3: Service UUIDs from Advertisements</h2>
<p>Let's talk about finding a compatible partner... for your app. In the world of BLE, not all devices are created equal. A heart rate monitor is very different from a smart lightbulb. So how does your app know if it's talking to the right kind of device? The answer is the Service UUID.</p>
<h3 id="heading-what-in-the-world-is-a-service-uuid">What in the World is a Service UUID?</h3>
<p>A Service UUID (Universally Unique Identifier) is like a device's job title. It's a unique, 128-bit number that says, "I am a device that provides a Heart Rate Service" or "I am a device that provides a Battery Service." It's the single most important piece of information for determining what a device can do.</p>
<h3 id="heading-the-old-way-the-awkward-first-date">The Old Way: The Awkward First Date</h3>
<p>Traditionally, finding out a device's services was a whole ordeal. It was like going on a full, three-course dinner date just to find out the other person's job. The process went something like this:</p>
<ol>
<li><p>Scan: Find the device.</p>
</li>
<li><p>Connect: Establish a connection (a slow and power-hungry process).</p>
</li>
<li><p>Discover Services: Ask the device, "So... what do you do for a living?" and wait for it to list all its services.</p>
</li>
<li><p>Evaluate: Check if the list of services contains the one you're interested in.</p>
</li>
<li><p>Disconnect (or stay connected): If it's not the right device, you have to break up (disconnect) and move on. What a waste of time and energy!</p>
</li>
</ol>
<p>This is incredibly inefficient, especially if you're in a crowded room with dozens of BLE devices and you're only looking for one specific type.</p>
<h3 id="heading-the-new-way-the-glorious-name-tag">The New Way: The Glorious Name Tag</h3>
<p>Wouldn't it be great if everyone at a party just wore a name tag with their job title on it? That's exactly what AOSP 16 has given us with BluetoothDevice.EXTRA_UUID_LE. Many BLE devices are already polite enough to include their primary service UUID in their advertisement packets. It's their way of shouting, "I'M A HEART RATE MONITOR!" to the whole room.</p>
<p>Before AOSP 16, getting this information out of the advertisement packet was a messy, manual process of parsing the raw byte array of the scan record. It was doable, but it was the kind of code that you'd write once, pray it worked, and never touch again.</p>
<p>Now, Android does the dirty work for us! The system automatically parses the advertising data and, if it finds any service UUIDs, it conveniently hands them to you in the ScanResult.</p>
<h3 id="heading-the-code-reading-the-name-tag">The Code: Reading the Name Tag</h3>
<p>This new feature makes our ScanCallback even more powerful. We can now check the device's job title the moment we discover it, without ever having to connect.</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> scanCallback = <span class="hljs-keyword">object</span> : ScanCallback() {
    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onScanResult</span><span class="hljs-params">(callbackType: <span class="hljs-type">Int</span>, result: <span class="hljs-type">ScanResult</span>)</span></span> {
        Log.d(<span class="hljs-string">"BleSpeedDating"</span>, <span class="hljs-string">"Found device: <span class="hljs-subst">${result.device.address}</span>"</span>)

        <span class="hljs-comment">// Let's check their name tag!</span>
        <span class="hljs-keyword">val</span> serviceUuids = result.scanRecord?.serviceUuids
        <span class="hljs-keyword">if</span> (serviceUuids.isNullOrEmpty()) {
            Log.d(<span class="hljs-string">"BleSpeedDating"</span>, <span class="hljs-string">"This one is mysterious. No service UUIDs in the ad."</span>)
            <span class="hljs-keyword">return</span>
        }

        <span class="hljs-comment">// Define the UUID we're looking for (e.g., the standard Heart Rate Service UUID)</span>
        <span class="hljs-keyword">val</span> heartRateServiceUuid = ParcelUuid.fromString(<span class="hljs-string">"0000180D-0000-1000-8000-00805F9B34FB"</span>)

        <span class="hljs-keyword">if</span> (serviceUuids.contains(heartRateServiceUuid)) {
            Log.d(<span class="hljs-string">"BleSpeedDating"</span>, <span class="hljs-string">"It's a match! This is a heart rate monitor. Let's connect!"</span>)
            <span class="hljs-comment">// Now you can proceed to connect to result.device, knowing it's the right one.</span>
            stopBleScan() <span class="hljs-comment">// We found what we were looking for</span>
            <span class="hljs-comment">// connectToDevice(result.device)</span>
        } <span class="hljs-keyword">else</span> {
            Log.d(<span class="hljs-string">"BleSpeedDating"</span>, <span class="hljs-string">"Not a match. Moving on."</span>)
        }
    }

    <span class="hljs-comment">// ... onScanFailed ...</span>
}
</code></pre>
<p>The code above demonstrates the power of reading service UUIDs directly from advertisement data, and it's a game-changer for device discovery. Let's break down exactly what's happening and why this is such a significant improvement.</p>
<p>When we receive a scan result in our callback, the <code>result</code> object contains a <code>scanRecord</code> property. This scan record is essentially the raw advertisement packet that the BLE device broadcast into the air.</p>
<p>Before AOSP 16, if you wanted to extract service UUIDs from this data, you'd have to manually parse the byte array, understand the BLE advertisement format, handle different data types, and pray you didn't make an off-by-one error. It was the kind of code that worked once and then you never touched it again out of fear.</p>
<p>Now, with the improvements in AOSP 16, Android does all that messy parsing for us. We can simply call <code>result.scanRecord?.serviceUuids</code> and get back a nice, clean list of <code>ParcelUuid</code> objects. The safe call operator (<code>?.</code>) is important here because not all devices include a scan record in their results, and we need to handle that gracefully.</p>
<p>After retrieving the service UUIDs, we check if the list is null or empty. Some devices don't include service UUIDs in their advertisements. They might be using a proprietary format, or they might just be poorly configured. If there are no UUIDs, we log a message and return early. There's no point in continuing if we can't identify what the device does.</p>
<p>Next, we define the UUID we're looking for. In this example, we're searching for heart rate monitors, so we use the standard Heart Rate Service UUID: <code>0000180D-0000-1000-8000-00805F9B34FB</code>. This is a UUID defined by the Bluetooth SIG (Special Interest Group), and any compliant heart rate monitor will advertise this UUID. You can find a complete list of standard service UUIDs in the Bluetooth specifications, or you can use custom UUIDs if you're building your own BLE peripherals.</p>
<p>The magic happens in the <code>if (serviceUuids.contains(heartRateServiceUuid))</code> check. This is where we're doing our speed dating: we're checking the device's "name tag" to see if it matches what we're looking for.</p>
<p>If it does, we've found our match! We can immediately stop scanning (because why keep looking when we've found what we need?) and proceed to connect to the device. We know, with certainty, that this device is a heart rate monitor, so we won't waste time and battery connecting to random devices only to discover they're not what we need.</p>
<p>If the UUID doesn't match, we simply log "Not a match" and move on. The callback will be called again when the next device is found, and we'll repeat this process until we find our heart rate monitor or the user stops the scan.</p>
<p>This is a massive performance improvement over the old approach. Previously, you'd have to connect to every device you found, perform service discovery (which involves multiple round-trip communications with the device), check if it has the services you need, and then disconnect if it doesn't. Each connection attempt takes time, uses battery, and creates unnecessary radio traffic.</p>
<p>Now, you can filter and identify devices at lightning speed, all at the scanning stage. No more awkward first dates where you connect to a smart lightbulb thinking it might be a fitness tracker. Just efficient, targeted connections.</p>
<p>This is particularly useful for apps that need to find a specific type of sensor or peripheral in a sea of irrelevant devices. Imagine you're in a hospital with hundreds of BLE-enabled medical devices, or in a smart home with dozens of sensors and actuators. Being able to instantly identify the right device from its advertisement is the difference between a responsive, professional app and one that feels sluggish and unreliable.</p>
<p>We've now met all three of our Bluetooth musketeers: passive scanning for battery efficiency, bond loss reasons for better debugging, and service UUIDs from advertisements for faster device identification. But our journey isn't over. It's time to venture into the deep woods of advanced scanning techniques.</p>
<h2 id="heading-advanced-topics-filtering-batching-and-other-sorcery">Advanced Topics: Filtering, Batching, and Other Sorcery</h2>
<p>Alright, you've mastered the basics. You can scan passively, you can get closure on your connection breakups, and you can speed-date devices like a pro. You're no longer a Bluetooth padawan. It's time to become a Jedi Master.</p>
<p>Let's dive into the advanced arts of filtering, batching, and other optimization sorcery that will make your app a true battery-saving champion.</p>
<h3 id="heading-hardware-filtering-your-personal-assistant">Hardware Filtering: Your Personal Assistant</h3>
<p>Imagine you're a celebrity, and you've hired a personal assistant. You don't want to be bothered by every single person who wants an autograph. So, you give your assistant a list: "Only let me know if you see my agent or my mom." Your assistant then stands at the door and only bothers you when someone on the list shows up.</p>
<p>This is exactly what hardware filtering does. Instead of your app's code (the celebrity) being woken up for every single Bluetooth device the radio sees, you can offload the filtering logic to the Bluetooth controller itself (the personal assistant). This is a feature that's been around since Android 6.0, but it's more important than ever.</p>
<p>Why is this so great? Because your app's code can stay asleep. The main processor (the AP) doesn't have to wake up every time a random Bluetooth toothbrush advertises itself. The Bluetooth controller, which is much more power-efficient, handles the filtering. The AP only wakes up when the controller finds a device that matches your criteria.</p>
<h3 id="heading-the-code-building-your-vip-list">The Code: Building Your VIP List</h3>
<p>You implement this using ScanFilter. You can filter by a device's name, its MAC address, or, most usefully, by the Service UUID it's advertising.</p>
<pre><code class="lang-kotlin"><span class="hljs-comment">// We only want to be bothered if we see a heart rate monitor.</span>
<span class="hljs-keyword">val</span> heartRateServiceUuid = ParcelUuid.fromString(<span class="hljs-string">"0000180D-0000-1000-8000-00805F9B34FB"</span>)

<span class="hljs-keyword">val</span> filter = ScanFilter.Builder()
    .setServiceUuid(heartRateServiceUuid)
    .build()

<span class="hljs-keyword">val</span> scanFilters: List&lt;ScanFilter&gt; = listOf(filter)

<span class="hljs-comment">// Now, when you start your scan, pass in this list</span>
bleScanner.startScan(scanFilters, scanSettings, scanCallback)
</code></pre>
<p>The code above shows how to create a hardware-level filter that dramatically improves both battery life and app performance. Let's dive deep into what's happening here and why this is such a powerful technique.</p>
<p>We start by defining the service UUID we're interested in – in this case, the standard Heart Rate Service UUID. This is the same UUID we used in the previous example, but now we're using it in a fundamentally different way. Instead of checking the UUID in our app's code after receiving scan results, we're telling the Bluetooth hardware itself to only report devices that match this UUID.</p>
<p>The <code>ScanFilter.Builder()</code> is our tool for constructing this filter. It's a builder pattern, which means we can chain multiple methods together to configure exactly what we're looking for. In this example, we're calling <code>setServiceUuid(heartRateServiceUuid)</code>, which tells the filter to only match devices that advertise this specific service.</p>
<p>But the builder has many other options you can use:</p>
<ul>
<li><p><code>setDeviceName()</code> – Match devices with a specific name (like "My Heart Monitor")</p>
</li>
<li><p><code>setDeviceAddress()</code> – Match a specific device by its MAC address (useful if you've already paired with a device and want to find it again)</p>
</li>
<li><p><code>setManufacturerData()</code> – Match devices based on manufacturer-specific data in their advertisements</p>
</li>
<li><p><code>setServiceData()</code> – Match based on service data included in the advertisement</p>
</li>
</ul>
<p>You can even combine multiple criteria in a single filter. For example, you could create a filter that matches devices with a specific service UUID <em>and</em> a specific manufacturer ID. The more specific your filter, the fewer false positives you'll get.</p>
<p>After building our filter, we create a list containing it. Why a list? Because you can have multiple filters, and a device will match if it satisfies <em>any</em> of the filters in the list. For instance, you might create one filter for heart rate monitors and another for blood pressure monitors, and your scan will report devices that match either one. This is an OR operation: the device doesn't need to match all filters, just one of them.</p>
<p>Finally, we pass this list of filters to <code>startScan()</code> along with our scan settings and callback. This is where the magic happens. When you provide filters, Android doesn't just filter the results in your app's code. It pushes these filters down to the Bluetooth controller hardware itself. This means the filtering happens at the lowest level, before your app is even notified.</p>
<p>Here's why this is so powerful: without filters, every time the Bluetooth radio hears an advertisement from <em>any</em> device (your neighbor's smart toaster, someone's fitness tracker walking by, the Bluetooth speaker three rooms away), it has to wake up your app's process, deliver the scan result, and let your code decide if it cares about this device. Each of these wake-ups costs battery and processing time.</p>
<p>With hardware filters, the Bluetooth controller silently ignores all the devices that don't match your criteria. Your app stays asleep. The main processor stays asleep. Only when a heart rate monitor is detected does the hardware wake up your app and deliver the result. It's like having a bouncer at a club who only lets in people on the VIP list. Everyone else is turned away at the door, and you never even know they were there.</p>
<p>By using a <code>ScanFilter</code>, you're telling the hardware, "Don't wake me up unless you see a heart rate monitor." It's the ultimate power-saving move for background scanning. Combined with passive scanning and batch reporting, you can create a Bluetooth scanning system that runs for hours or even days with minimal battery impact. This is how professional-grade apps handle long-term device monitoring without destroying battery life.</p>
<h3 id="heading-batch-scanning-the-daily-report">Batch Scanning: The Daily Report</h3>
<p>Let's go back to our celebrity analogy. Sometimes, you don't need to be interrupted the moment your mom shows up. You'd rather just get a report at the end of the day: "Today, your mom stopped by twice, and your agent called once." This is batch scanning.</p>
<p>Instead of delivering scan results to your app in real-time, the Bluetooth controller can collect them and deliver them in a big batch. This is another incredible power-saving feature. Your app can sleep for long periods, then wake up, process a whole bunch of results at once, and go back to sleep.</p>
<p>You enable this with the setReportDelay() method in your ScanSettings.</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> scanSettings = ScanSettings.Builder()
    <span class="hljs-comment">// ... other settings ...</span>
    <span class="hljs-comment">// Deliver results every 5 seconds (5000 milliseconds)</span>
    .setReportDelay(<span class="hljs-number">5000</span>)
    .build()
</code></pre>
<p>When you use a report delay, your onScanResult callback will be replaced by onBatchScanResults, which gives you a List&lt;ScanResult&gt;.</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> scanCallback = <span class="hljs-keyword">object</span> : ScanCallback() {
    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onBatchScanResults</span><span class="hljs-params">(results: <span class="hljs-type">List</span>&lt;<span class="hljs-type">ScanResult</span>&gt;)</span></span> {
        Log.d(<span class="hljs-string">"BatchScanner"</span>, <span class="hljs-string">"Here's your daily report! Found <span class="hljs-subst">${results.size}</span> devices."</span>)
        <span class="hljs-keyword">for</span> (result <span class="hljs-keyword">in</span> results) {
            <span class="hljs-comment">// Process each result</span>
        }
    }

    <span class="hljs-comment">// ... onScanFailed ...</span>
}
</code></pre>
<p>The batch scanning mechanism shown above is one of the most underutilized power-saving features in Android Bluetooth, and understanding how it works can transform your app's battery profile. Let's break down exactly what's happening under the hood and when you should use this technique.</p>
<p>When you set a report delay of 5000 milliseconds (5 seconds) in the code above, you're fundamentally changing how the scanning pipeline works. Instead of the Bluetooth controller immediately waking up your app every time it sees a device, it acts like a diligent assistant taking notes. For those 5 seconds, the controller silently collects every scan result it encounters, storing them in its own internal buffer. Your app remains completely asleep during this time – no CPU cycles wasted, no battery drained by context switches or process wake-ups.</p>
<p>After the 5-second delay expires, the controller delivers all the accumulated results in one batch to your <code>onBatchScanResults()</code> callback. This is where the power savings come from: instead of waking up your app 50 times if 50 devices were detected, it wakes up once and hands you all 50 results at the same time. Your app can then efficiently process this batch – maybe updating a UI list, logging the data, or checking for specific devices – and then go back to sleep until the next batch arrives.</p>
<p>The <code>results</code> parameter in <code>onBatchScanResults()</code> is a <code>List&lt;ScanResult&gt;</code>, and each <code>ScanResult</code> in the list represents a single advertisement that was heard during the batching period. It's important to note that if the same device advertises multiple times during the delay period, you might receive multiple results for that device in the batch. The list isn't automatically deduplicated – that's your job if you need it.</p>
<p>In the example above, we're simply logging the number of devices found and then iterating through each result. In a real application, you might want to do more sophisticated processing. For instance, you could build a map of devices keyed by MAC address to track how many times each device advertised, calculate average RSSI values to estimate distance, or filter the batch to only process devices that meet certain criteria.</p>
<p><strong>Warning:</strong> Batch scanning is a powerful tool, but it's not for every situation. If you need to react to a device's presence immediately (for example, if you're building a "find my keys" app where the user is actively searching), a report delay is not your friend. The user doesn't want to wait 5 seconds to see results – they want instant feedback. In these cases, set <code>setReportDelay(0)</code> for immediate reporting.</p>
<p>But for long-term monitoring or data collection scenarios, batch scanning is a battery's best friend. Consider these use cases:</p>
<ul>
<li><p><strong>Background presence monitoring</strong>: Your app checks every minute to see if the user's smartwatch is still in range, but doesn't need second-by-second updates.</p>
</li>
<li><p><strong>Environmental sensing</strong>: You're collecting data from temperature sensors throughout a building and only need to update your dashboard every 30 seconds.</p>
</li>
<li><p><strong>Beacon analytics</strong>: You're tracking how many people pass by a retail location based on their phone's BLE advertisements, and you aggregate the data every 10 seconds.</p>
</li>
</ul>
<p>The sweet spot for report delay depends on your use case. Too short (like 1 second), and you're not getting much benefit, you're still waking up frequently. Too long (like 60 seconds), and your app might feel unresponsive or miss time-sensitive events. For most background monitoring tasks, delays between 5 and 30 seconds work well.</p>
<p>One more thing to be aware of: batch scanning has limits. The Bluetooth controller has a finite buffer for storing scan results. If you set a very long delay and you're in an environment with hundreds of BLE devices, the buffer might fill up before the delay expires. When this happens, the oldest results get dropped. Android doesn't give you a warning when this occurs, so if you're missing data, consider reducing your report delay or using more aggressive filters to reduce the number of results being collected.</p>
<h3 id="heading-onfoundonlost-the-drama-of-presence">OnFound/OnLost: The Drama of Presence</h3>
<p>Since Android 8.0, scanning has gotten even more dramatic. You can now ask the hardware to not only tell you when it finds a device, but also when it loses one. This is done using the CALLBACK_TYPE_FIRST_MATCH and CALLBACK_TYPE_MATCH_LOST flags in your ScanSettings.</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> scanSettings = ScanSettings.Builder()
    .setCallbackType(ScanSettings.CALLBACK_TYPE_FIRST_MATCH or ScanSettings.CALLBACK_TYPE_MATCH_LOST)
    .build()
</code></pre>
<p>Now, in your ScanCallback, the callbackType parameter in onScanResult will tell you what happened.</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onScanResult</span><span class="hljs-params">(callbackType: <span class="hljs-type">Int</span>, result: <span class="hljs-type">ScanResult</span>)</span></span> {
    <span class="hljs-keyword">when</span> (callbackType) {
        ScanSettings.CALLBACK_TYPE_FIRST_MATCH -&gt; {
            Log.d(<span class="hljs-string">"PresenceDetector"</span>, <span class="hljs-string">"Found them! <span class="hljs-subst">${result.device.address}</span> has entered the building."</span>)
        }
        ScanSettings.CALLBACK_TYPE_MATCH_LOST -&gt; {
            Log.d(<span class="hljs-string">"PresenceDetector"</span>, <span class="hljs-string">"They're gone! <span class="hljs-subst">${result.device.address}</span> has left the building."</span>)
        }
    }
}
</code></pre>
<p>The presence detection mechanism shown above represents a fundamental shift in how we think about Bluetooth scanning. Instead of treating scanning as a continuous stream of "here's what I see right now," we're now working with events: "this device appeared" and "this device disappeared." Let's dive deep into how this works and why it's so powerful.</p>
<p>When you set the callback type using the bitwise OR operator (<code>or</code> in Kotlin, <code>|</code> in Java), you're telling the Bluetooth hardware to track the presence state of devices over time. The code <code>CALLBACK_TYPE_FIRST_MATCH or CALLBACK_TYPE_MATCH_LOST</code> combines both flags, meaning you want to be notified both when a device first appears and when it disappears. You can use these flags individually if you only care about one type of event, but using both together gives you complete presence awareness.</p>
<p>Let's understand what "first match" and "match lost" actually mean. When the Bluetooth controller hears an advertisement from a device that matches your filters for the first time, it triggers a <code>CALLBACK_TYPE_FIRST_MATCH</code> event. This is different from <code>CALLBACK_TYPE_ALL_MATCHES</code> (the default), which would trigger every single time the device advertises. A device might advertise multiple times per second, so the difference is significant. With <code>FIRST_MATCH</code>, you get one notification when the device enters your scanning range, not a flood of notifications as it continues to advertise.</p>
<p>The <code>CALLBACK_TYPE_MATCH_LOST</code> event is even more interesting. The Bluetooth controller keeps track of when it last heard from each device. If a device stops advertising (because it moved out of range, was turned off, or its battery died), the controller notices the absence and triggers a <code>MATCH_LOST</code> event. This happens automatically: you don't have to manually track timestamps or implement timeout logic in your app. The hardware does it for you.</p>
<p>But how does the hardware know when a device is "lost"? It uses an internal timeout. If the controller hasn't heard from a device for a certain period (typically a few seconds, though the exact duration is implementation-dependent and not exposed to apps), it considers the device lost. This means there's a slight delay between when a device actually leaves range and when you get the <code>MATCH_LOST</code> callback, but this delay is usually acceptable for presence detection use cases.</p>
<p>In the code example above, we're using a <code>when</code> expression to handle the different callback types. When we receive a <code>FIRST_MATCH</code>, we know the device has just entered our scanning range, so we log "Found them!" This is perfect for triggering actions like unlocking a door when your phone comes near, or starting to sync data when your fitness tracker is detected.</p>
<p>When we receive a <code>MATCH_LOST</code>, we know the device has left our scanning range or stopped advertising, so we log "They're gone!" This is ideal for triggering cleanup actions like locking the door when your phone leaves, or stopping a data sync when your tracker disconnects.</p>
<p>This is incredibly useful for presence detection scenarios. Is your smart lock in range? Is your fitness tracker still connected? Is the user's phone nearby? Now you can know, with hardware-level certainty, and you can react to changes in presence without constantly polling or maintaining complex state machines in your app code.</p>
<p>Here's a practical example of how you might use this in a smart home app:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> presenceCallback = <span class="hljs-keyword">object</span> : ScanCallback() {
    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onScanResult</span><span class="hljs-params">(callbackType: <span class="hljs-type">Int</span>, result: <span class="hljs-type">ScanResult</span>)</span></span> {
        <span class="hljs-keyword">when</span> (callbackType) {
            ScanSettings.CALLBACK_TYPE_FIRST_MATCH -&gt; {
                <span class="hljs-comment">// User's phone detected - they're home!</span>
                Log.d(<span class="hljs-string">"SmartHome"</span>, <span class="hljs-string">"Welcome home! Unlocking door and turning on lights."</span>)
                unlockFrontDoor()
                turnOnLights()
                adjustThermostat(COMFORTABLE_TEMP)
            }
            ScanSettings.CALLBACK_TYPE_MATCH_LOST -&gt; {
                <span class="hljs-comment">// User's phone is gone - they left!</span>
                Log.d(<span class="hljs-string">"SmartHome"</span>, <span class="hljs-string">"Goodbye! Locking door and entering away mode."</span>)
                lockFrontDoor()
                turnOffLights()
                adjustThermostat(ENERGY_SAVING_TEMP)
                armSecuritySystem()
            }
        }
    }

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onScanFailed</span><span class="hljs-params">(errorCode: <span class="hljs-type">Int</span>)</span></span> {
        Log.e(<span class="hljs-string">"SmartHome"</span>, <span class="hljs-string">"Presence detection failed: <span class="hljs-variable">$errorCode</span>"</span>)
    }
}
</code></pre>
<p>One important consideration: <code>FIRST_MATCH</code> and <code>MATCH_LOST</code> are mutually exclusive with <code>CALLBACK_TYPE_ALL_MATCHES</code>. If you combine them with <code>ALL_MATCHES</code>, the behavior becomes undefined and varies by device. Stick to either <code>ALL_MATCHES</code> for continuous reporting, or <code>FIRST_MATCH</code>/<code>MATCH_LOST</code> for presence detection – don't try to use both at once.</p>
<p>Also, be aware that presence detection works best when combined with hardware filtering. If you're scanning for all devices without filters, the controller has to track the presence state of every single BLE device in range, which can overwhelm its internal tracking tables. Always use <code>ScanFilter</code> to narrow down which devices you care about when using presence detection.</p>
<p>By combining these advanced techniques – hardware filtering, batch scanning, and presence detection – you can build incredibly sophisticated and power-efficient Bluetooth applications. You're not just a developer anymore. You're a Bluetooth wizard, wielding the power to create apps that are aware of their surroundings, responsive to changes, and respectful of battery life.</p>
<p>Now, let's see where we can apply these magical powers in the real world.</p>
<h2 id="heading-real-world-use-cases-where-the-bluetooth-hits-the-road">Real-World Use Cases: Where the Bluetooth Hits the Road</h2>
<p>Okay, we've learned a ton of cool new tricks. We're basically Bluetooth black belts at this point. But what's the use of all this power if we don't use it for good (or at least for a cool app)? Let's explore some real-world scenarios where the new features in AOSP 16 can turn a good app into a great one.</p>
<h3 id="heading-1-the-find-my-everything-app">1. The "Find My Everything" App</h3>
<p>We've all been there. You're late for work, and your keys have decided to play a game of hide-and-seek in another dimension. This is the classic use case for a BLE tracker.</p>
<ul>
<li><p><strong>The Old Way:</strong> Your app would be constantly doing active scans, draining your battery while you frantically search. It would connect to every tracker in your house just to see if it's the right one.</p>
</li>
<li><p><strong>The AOSP 16 Way:</strong> Your app runs a passive scan in the background with a hardware filter for your tracker's specific Service UUID. The battery impact is minimal. When you open the app to find your keys, it already knows they're in the house because it's been listening silently. You hit the "Find" button, the app connects, and your keys start screaming from inside the couch cushions. And if the connection fails? Bond loss reason tells you if the tracker's battery died, so you're not looking for a dead device.</p>
</li>
</ul>
<h3 id="heading-2-the-smart-supermarket">2. The Smart Supermarket</h3>
<p>Imagine an app that gives you coupons for products as you walk past them in the store. This is the dream of proximity marketing, a dream that has been historically thwarted by, you guessed it, battery drain.</p>
<ul>
<li><p><strong>The Old Way:</strong> The app would need to constantly scan for beacons, turning the user's phone into a hot potato and a dead battery by the time they reach the checkout line.</p>
</li>
<li><p><strong>The AOSP 16 Way:</strong> The supermarket places BLE beacons in each aisle. Your app uses a passive, batched scan. It wakes up every minute or so, gets a list of all the beacons it has seen, and then goes back to sleep. When it sees you've been loitering in the cookie aisle for five minutes (it knows, it always knows), it uses the Service UUID from the advertisement to identify the "Cookie Aisle Beacon" and sends you a coupon for Oreos. It's targeted, it's efficient, and it doesn't kill your battery before you can pay.</p>
</li>
</ul>
<h3 id="heading-3-the-overly-attached-smart-home">3. The Overly-Attached Smart Home</h3>
<p>Your smart home should be, well, smart. It should know when you're home and when you've left. It should lock the door behind you and turn on the lights when you arrive.</p>
<ul>
<li><p><strong>The Old Way:</strong> You'd have to rely on GPS (a notorious battery hog) or Wi-Fi connections, which can be unreliable. BLE was an option, but constant scanning was a problem.</p>
</li>
<li><p><strong>The AOSP 16 Way:</strong> Your phone is the key. Your smart hub (acting as a central device) runs a continuous, low-power passive scan. When it sees your phone's BLE advertisement, it knows you're home. But what if you just walk by the house? This is where the OnFound/OnLost feature comes in. The hub can be configured to only trigger the "Welcome Home" sequence after it has seen your device consistently for a minute (OnFound), and to trigger the "Goodbye" sequence only after it hasn't seen you for five minutes (OnLost). It's a smarter, more reliable presence detection system that finally makes the smart home feel... smart.</p>
</li>
</ul>
<h3 id="heading-4-the-corporate-asset-tracker">4. The Corporate Asset Tracker</h3>
<p>In a large hospital or warehouse, keeping track of expensive, mobile equipment (like IV pumps or forklifts) is a huge challenge. BLE tags are the solution.</p>
<ul>
<li><p><strong>The Old Way:</strong> Employees would have to walk around with a tablet, doing active scans to take inventory. It's slow, manual, and inefficient.</p>
</li>
<li><p><strong>The AOSP 16 Way:</strong> A network of fixed BLE gateways is installed throughout the building. Each gateway is a simple device (like a Raspberry Pi) running a continuous passive scan. They collect all the advertisement data from the asset tags and send it to a central server. The server can now see, in real-time, that IV Pump #34 is in Room 201, and Forklift #3 is currently in the loading bay. No manual scanning required. It's a low-cost, low-power, real-time location system, all thanks to the efficiency of passive scanning.</p>
</li>
</ul>
<p>These are just a few examples. From fitness trackers to industrial sensors, the new Bluetooth features in AOSP 16 open up a world of possibilities for building apps that are not only powerful but also polite to your user's battery. Now, let's talk about how to make sure our shiny new app works on all devices, not just the new ones.</p>
<h2 id="heading-api-version-checking-how-to-not-crash-your-app">API Version Checking: How to Not Crash Your App</h2>
<p>So, you've built a beautiful, battery-sipping app using all the new hotness from AOSP 16's Q4 release. You're ready to ship it, become a millionaire, and retire to a private island. But then, a bug report comes in. Your app is crashing on a brand new Android 16 device. What gives?!</p>
<p>Welcome, my friend, to the wonderful world of API version checking. With Android's new release schedule, this has become more important (and slightly more complicated) than ever.</p>
<h3 id="heading-the-problem-a-tale-of-two-android-16s">The Problem: A Tale of Two Android 16s</h3>
<p>As we discussed, 2025 gave us two Android 16 releases:</p>
<ul>
<li><p><strong>The Q2 Release:</strong> The main "Baklava" release. Let's call this API level 36.0.</p>
</li>
<li><p><strong>The Q4 Release:</strong> The minor, feature-drop release. This is where our new Bluetooth toys live. Let's call this API level 36.1.</p>
</li>
</ul>
<p>Our new passive scanning API, setScanType(), only exists on 36.1 and later. If you try to call it on a device that's running the initial Q2 release (36.0), your app will crash with a NoSuchMethodError. It's the digital equivalent of asking for a menu item that was only added last night. The chef (your app) just gets confused and has a meltdown.</p>
<h3 id="heading-the-old-guard-sdkint">The Old Guard: SDK_INT</h3>
<p>For years, our trusty friend for checking API levels has been Build.VERSION.SDK_INT. It's simple and effective.</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">if</span> (Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.S) {
    <span class="hljs-comment">// Use an API from Android 12 (S) or higher</span>
}
</code></pre>
<p>But SDK_INT only knows about major releases. For both Android 16 Q2 and Q4, SDK_INT will just report 36. It has no idea about the minor version. It's like asking someone their age, and they just say "thirties." Not very specific.</p>
<h3 id="heading-the-new-hotness-sdkintfull">The New Hotness: SDK_INT_FULL</h3>
<p>To solve this, the Android team has given us a new, more precise tool: <code>Build.VERSION.SDK_INT_FULL</code>. This constant knows about both the major and minor version numbers. And to go with it, we have a new set of version codes: <code>Build.VERSION_CODES_FULL</code>.</p>
<p>So, to safely call our new passive scanning API, we need to do a more specific check:</p>
<pre><code class="lang-kotlin"><span class="hljs-comment">// Let's build our ScanSettings</span>
<span class="hljs-keyword">val</span> scanSettingsBuilder = ScanSettings.Builder()
    .setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)

<span class="hljs-comment">// Now, let's check if we can go passive</span>
<span class="hljs-keyword">if</span> (Build.VERSION.SDK_INT_FULL &gt;= Build.VERSION_CODES_FULL.BAKLAVA_1) {
    Log.d(<span class="hljs-string">"ApiCheck"</span>, <span class="hljs-string">"This device is cool. Going passive."</span>)
    <span class="hljs-comment">// This is the new API from the Q4 release (36.1)</span>
    scanSettingsBuilder.setScanType(ScanSettings.SCAN_TYPE_PASSIVE)
} <span class="hljs-keyword">else</span> {
    Log.d(<span class="hljs-string">"ApiCheck"</span>, <span class="hljs-string">"This device is old school. Sticking to active scanning."</span>)
    <span class="hljs-comment">// Fallback for devices that don't have the new API</span>
    <span class="hljs-comment">// We don't need to do anything here, as active is the default</span>
}

<span class="hljs-keyword">val</span> scanSettings = scanSettingsBuilder.build()
</code></pre>
<h3 id="heading-graceful-degradation-the-art-of-falling-with-style">Graceful Degradation: The Art of Falling with Style</h3>
<p>This brings us to a crucial concept: graceful degradation. It means your app should still work on older devices, even if it can't use the latest and greatest features. It should fall back gracefully.</p>
<p>In our example above, if the setScanType method isn't available, we just... don't call it. The app will default to a normal, active scan. It won't be as battery-efficient, but it will still work. The user on the older device gets a functional app, and the user on the newer device gets a more optimized experience. Everybody wins.</p>
<p>Here's a table to help you remember when to use which check:</p>
<table><tbody><tr><td><p><strong>If you're using an API from...</strong></p></td><td><p><strong>Use this check...</strong></p></td></tr><tr><td><p>A major Android release (for example, Android 16 Q2)</p></td><td><p>if (SDK_INT &gt;= VERSION_CODES.BAKLAVA)</p></td></tr><tr><td><p>A minor, feature-drop release (for example, Android 16 Q4)</p></td><td><p>if (SDK_INT_FULL &gt;= VERSION_CODES_FULL.BAKLAVA_1)</p></td></tr></tbody></table>

<p>Mastering this new API checking is non-negotiable. It's the key to writing modern Android apps that are both innovative and stable. Now that we know how to build a robust app, let's talk about how to fix it when it inevitably breaks.</p>
<h2 id="heading-testing-and-debugging-the-fun-part-said-no-one-ever">Testing and Debugging: The Fun Part (Said No One Ever)</h2>
<p>There are two universal truths in software development:</p>
<ul>
<li><p>It works on my machine, and</p>
</li>
<li><p>It will break in the most spectacular way possible during a live demo.</p>
</li>
</ul>
<p>Bluetooth development, in particular, seems to delight in this second truth. It's a fickle, invisible force that seems to have a personal vendetta against developers.</p>
<p>So, how do we fight back? With a solid testing and debugging strategy. It's not glamorous, but it's the only way to stay sane.</p>
<h3 id="heading-the-emulator-a-land-of-make-believe">The Emulator: A Land of Make-Believe</h3>
<p>Android Studio's emulator is a fantastic tool. It's fast, it's convenient, and it can simulate all sorts of devices. And for Bluetooth? It can... sort of help. The emulator does have virtual Bluetooth support. You can enable it, and your app will think it has a Bluetooth adapter. It's great for testing your UI and making sure your app doesn't crash when it tries to get the BluetoothLeScanner.</p>
<p>But here's the catch: it's not real. The emulator can't actually interact with the radio waves in your room. You can't use it to find your real-life BLE headphones. For that, you need to venture into the real world.</p>
<h3 id="heading-the-real-world-where-the-bugs-live">The Real World: Where the Bugs Live</h3>
<p>There is no substitute for testing on real, physical devices. Every phone manufacturer has its own special flavor of Bluetooth stack, its own quirky antenna design, and its own unique way of making your life difficult. A scan that works perfectly on a Google Pixel might fail miserably on another brand. The only way to know is to test.</p>
<p>Your testing arsenal should include:</p>
<ul>
<li><p><strong>A variety of phones:</strong> Different brands, different Android versions. The more, the better.</p>
</li>
<li><p><strong>A variety of BLE peripherals:</strong> Don't just test with one type of device. Get a few different beacons, sensors, or wearables. You'll be amazed at how differently they behave.</p>
</li>
</ul>
<h3 id="heading-common-errors-the-usual-suspects">Common Errors: The Usual Suspects</h3>
<p>When your scan inevitably fails, it will give you an error code. Here are a few of the most common culprits:</p>
<table><tbody><tr><td><p><strong>Error Code</strong></p></td><td><p><strong>The Problem</strong></p></td><td><p><strong>How to Fix It</strong></p></td></tr><tr><td><p>SCAN_FAILED_ALREADY_STARTED</p></td><td><p>You tried to start a scan that was already running.</p></td><td><p>You got too excited. Make sure you're not calling startScan() multiple times without calling stopScan() in between.</p></td></tr><tr><td><p>SCAN_FAILED_APPLICATION_REGISTRATION_FAILED</p></td><td><p>Something is fundamentally wrong with your app's setup.</p></td><td><p>This is a vague and unhelpful error. It usually means you have a problem with your permissions or the system is just having a bad day. Try restarting Bluetooth.</p></td></tr><tr><td><p>SCAN_FAILED_INTERNAL_ERROR</p></td><td><p>The Bluetooth stack had a panic attack.</p></td><td><p>This is the classic "it's not you, it's me" error. It's an internal issue with the device's Bluetooth controller. There's not much you can do except try again later.</p></td></tr><tr><td><p>SCAN_FAILED_FEATURE_UNSUPPORTED</p></td><td><p>You tried to use a feature the hardware doesn't support.</p></td><td><p>You might be trying to use batch scanning on a device that doesn't support it. Use your API version checks!</p></td></tr></tbody></table>

<h3 id="heading-debugging-tools-your-ghost-hunting-kit">Debugging Tools: Your Ghost-Hunting Kit</h3>
<p>When things go wrong, you need the right tools to see what's happening in the invisible world of Bluetooth.</p>
<ul>
<li><p><strong>logcat:</strong> This is your best friend. Be generous with your log statements. Log when you start a scan, when you stop a scan, when you find a device, and when a scan fails. Create a filter for your app's tag so you can see the signal through the noise.</p>
</li>
<li><p><strong>Android's Bluetooth HCI Snoop Log:</strong> This is the holy grail of Bluetooth debugging. It's a developer option that records every single Bluetooth packet that goes in or out of your device. It's incredibly detailed and can be overwhelming, but it's the ultimate source of truth. You can open the generated log file in a tool like Wireshark to see the raw, unfiltered conversation between your phone and the BLE device. It's like having a wiretap on the radio waves.</p>
</li>
<li><p><strong>nRF Connect for Mobile:</strong> This is a free app from Nordic Semiconductor, and it's an essential tool for any BLE developer. It lets you scan for devices, see their advertising data, connect to them, and explore their GATT services. If your app can't find a device, the first thing you should do is see if nRF Connect can. If it can't, the problem is likely with the peripheral, not your app.</p>
</li>
</ul>
<p>Testing and debugging Bluetooth is a marathon, not a sprint. It requires patience, a methodical approach, and a healthy dose of self-deprecating humor. But with the right tools and techniques, you can tame the beast.</p>
<p>Now, let's talk about how to make sure our well-behaved app is also a good citizen when it comes to performance.</p>
<h2 id="heading-performance-and-best-practices-how-to-be-a-good-bluetooth-citizen">Performance and Best Practices: How to Be a Good Bluetooth Citizen</h2>
<p>Writing code that works is one thing. Writing code that works well, is efficient, and doesn't make your users want to throw their phone against a wall is another thing entirely. When it comes to Bluetooth, being a good citizen is all about one thing: battery, battery, battery.</p>
<p>The Bluetooth radio is a powerful piece of hardware, but it's also a thirsty one. Every moment it's active, it's sipping power. Your job is to make sure it's only sipping when absolutely necessary. Here are the golden rules of being a good Bluetooth citizen.</p>
<h3 id="heading-1-dont-scan-if-you-dont-have-to">1. Don't Scan If You Don't Have To</h3>
<p>This sounds obvious, but it's the most common mistake. Before you even think about starting a scan, ask yourself: "Do I really need to do this right now?" If the user is not on the screen that needs scan results, don't scan. If the app is in the background, be extra critical. Background scanning is a huge drain on battery and is heavily restricted by Android for that very reason.</p>
<h3 id="heading-2-stop-your-scan">2. Stop Your Scan!</h3>
<p>I'm going to say it again because it's that important: always stop your scan when you're done. A scan that's left running is like a leaky faucet for your battery. It will drain and drain until there's nothing left. The best practice is to tie your scan lifecycle to your UI lifecycle.</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onPause</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">super</span>.onPause()
    <span class="hljs-comment">// The user can't see the screen, so they don't need the results.</span>
    stopBleScan()
}

<span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onResume</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">super</span>.onResume()
    <span class="hljs-comment">// The user is back on the screen, let's start scanning again.</span>
    startBleScan()
}
</code></pre>
<p>If you find the device you're looking for, stop the scan immediately. There's no need to keep looking.</p>
<h3 id="heading-3-choose-the-right-scan-mode">3. Choose the Right Scan Mode</h3>
<p>ScanSettings gives you a few different modes. Choose wisely.</p>
<ul>
<li><p><strong>SCAN_MODE_LOW_POWER:</strong> This is your default, everyday mode. It scans in intervals, balancing discovery speed and battery life. Use this for most foreground scanning.</p>
</li>
<li><p><strong>SCAN_MODE_BALANCED:</strong> A middle ground. It scans more frequently than low power mode.</p>
</li>
<li><p><strong>SCAN_MODE_LOW_LATENCY:</strong> This is the "I need to find it NOW" mode. It scans continuously. This will find devices the fastest, but it will also drain your battery the fastest. Only use this for short, critical operations.</p>
</li>
<li><p><strong>SCAN_MODE_OPPORTUNISTIC:</strong> This is the ultimate passive mode. Your app doesn't trigger a scan at all. It just gets results if another app happens to be scanning. It uses zero extra battery, but you have no guarantee of getting results. Use this for non-critical background updates.</p>
</li>
</ul>
<p>And of course, if you're on AOSP 16 QPR2 or later, use setScanType(SCAN_TYPE_PASSIVE) whenever you don't need the scan response data. It's the new king of power efficiency.</p>
<h3 id="heading-4-use-hardware-filtering-and-batching">4. Use Hardware Filtering and Batching</h3>
<p>We covered this in the advanced section, but it's a best practice that's worth repeating. If you're looking for a specific device, use a ScanFilter. If you're doing a long-running scan, use setReportDelay() to batch your results. These two techniques offload the work to the power-efficient Bluetooth controller and let your app's code sleep, which is the number one way to save battery.</p>
<h3 id="heading-5-be-mindful-of-memory">5. Be Mindful of Memory</h3>
<p>Every ScanResult object that your app receives takes up memory. If you're in a crowded area with hundreds of BLE devices, and you're not using filters, your app can quickly get overwhelmed and run out of memory. This is another reason why filtering is so important. Only get the results you actually care about.</p>
<p>By following these rules, you can build a Bluetooth app that is not only powerful and feature-rich but also respectful of your user's device. You'll be a true Bluetooth sensei. Now, let's wrap things up and look to the future.</p>
<h2 id="heading-conclusion-the-future-is-passive-and-thats-okay">Conclusion: The Future is Passive (and That's Okay)</h2>
<p>We've been on quite a journey, haven't we? We've traveled back in time to the dark ages of Classic Bluetooth, witnessed the renaissance of BLE, and emerged into the brave new world of AOSP 16. We've learned to be silent ninjas with passive scanning, played detective with bond loss reasons, and mastered the art of speed dating with service UUIDs from advertisements.</p>
<p>If there's one big takeaway from all of this, it's that the future of Bluetooth on Android is smarter, more efficient, and a whole lot less frustrating. The Android team is clearly listening to the pain points of developers and giving us the tools we need to build better, more battery-friendly apps. The introduction of passive scanning isn't just a new feature – it's a change in philosophy. It's an acknowledgment that sometimes, the best way to communicate is to just listen.</p>
<p>As developers, these new tools empower us to move beyond the simple "connect and stream" use cases. We can now build sophisticated, context-aware applications that are constantly aware of their surroundings without turning our users' phones into expensive paperweights. The dream of a truly smart, seamlessly connected world is a little bit closer, and it's going to be built on the back of these power-efficient technologies.</p>
<p>So, what's next? The world of Bluetooth is always evolving. We have Bluetooth 5.4 with Auracast, mesh networking, and even more precise location-finding on the horizon. The one thing we can be sure of is that the tools will continue to get better, and the challenges will continue to get more interesting.</p>
<p>For now, take a moment to appreciate the progress we've made. The next time you start a Bluetooth scan and it just works, take a moment to thank the hardworking engineers who made it possible. And the next time your app's battery graph is a beautiful, flat line instead of a terrifying ski slope, give a little nod to the power of passive scanning.</p>
<p>The Bluetooth beast may never be fully tamed, but with AOSP 16, we've been given a much stronger leash. Now go forth and build amazing things. And for the love of all that is holy, remember to stop your scan.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Scale Bluetooth Across Android, iOS, and Embedded Devices ]]>
                </title>
                <description>
                    <![CDATA[ Bluetooth is one of those inventions that seems magical the first time you use it. You turn on a gadget, pair it with your phone, and suddenly they are talking to each other without a single wire in sight. Music plays through your headphones, your sm... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-scale-bluetooth-across-devices/</link>
                <guid isPermaLink="false">691742dfb6a85c7f18a5fc15</guid>
                
                    <category>
                        <![CDATA[ bluetooth ]]>
                    </category>
                
                    <category>
                        <![CDATA[ iOS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Android ]]>
                    </category>
                
                    <category>
                        <![CDATA[ iot ]]>
                    </category>
                
                    <category>
                        <![CDATA[ embedded systems ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Nikheel Vishwas Savant ]]>
                </dc:creator>
                <pubDate>Thu, 13 Nov 2025 23:00:00 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1763131642774/dd2366f8-f491-4313-901e-acd4c1d937e2.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Bluetooth is one of those inventions that seems magical the first time you use it. You turn on a gadget, pair it with your phone, and suddenly they are talking to each other without a single wire in sight. Music plays through your headphones, your smartwatch shows messages from your friends, and for a brief moment it feels like technology finally has its act together. Everything works and life is good.</p>
<p>Then you try to connect one more thing. Maybe a fitness band, a smart lock, or that tiny temperature sensor you ordered online because it was on sale. That is when the charm fades and reality walks in. Suddenly the connection drops, your phone cannot find the device anymore, and the once-friendly Bluetooth logo on your screen starts to feel like a taunt. You restart, you unpair, you try again, and somehow it only gets worse. What was once effortless turns into a puzzle with no clear solution.</p>
<p>Here is the secret that few people know: Bluetooth was never meant to handle the chaos we put it through today. When engineers designed it in the late 1990s, they imagined a world of simple one-to-one connections. A laptop talking to a mouse. A phone connecting to a headset. That was the whole idea. Fast-forward to the present and we are using the same technology to run entire networks of wearables, sensors, and smart appliances. We ask it to connect not just one or two devices but sometimes dozens of them at the same time, each running on different hardware and software. It is a miracle that it works at all.</p>
<p>To make things even more interesting, these devices live in very different worlds. Android devices are like an open playground where every manufacturer adds its own slide and swing set. iPhones live inside Apple’s carefully fenced garden where everything is polished but also tightly controlled. Embedded devices, like the ones built on tiny chips inside sensors or IoT boards, are the quiet introverts of the group. They have little memory, tiny batteries, and a strong preference for naps to save power. Getting all three to cooperate is a bit like trying to organize a band where one member only plays jazz, another insists on classical, and the third speaks in Morse code.</p>
<p>That is what engineers mean when they talk about scaling Bluetooth. It is not just about adding more devices. It is about making sure completely different systems can talk to each other reliably and continuously without draining their batteries or losing their minds. It requires design decisions that consider timing, power management, data formats, and even how the operating system schedules background tasks.</p>
<p>This article will guide you through that strange world. We will peel back the layers of how Bluetooth actually works and what happens when Android, iOS, and embedded devices try to share the same airwaves. We will explore why each one behaves the way it does and what you can do to build systems that stay connected instead of collapsing under their own complexity.</p>
<p>By the end, you will see that Bluetooth is not really broken. It is simply overworked. It is a polite translator trying to keep three very different languages in sync. Once you learn how to manage its quirks and give it the structure it needs, Bluetooth becomes not a source of frustration but a quiet, invisible network that holds the modern world together.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-bluetooth-has-two-personalities-meet-classic-and-ble">Bluetooth Has Two Personalities — Meet Classic and BLE</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-android-ios-and-embedded-devices-the-odd-trio">Android, iOS, and Embedded Devices — The Odd Trio</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-architecting-for-scale-herding-cats-but-wirelessly">Architecting for Scale — Herding Cats, but Wirelessly</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-connection-discovery-and-data-flow-the-bluetooth-dating-game">Connection, Discovery, and Data Flow — The Bluetooth Dating Game</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-platform-quirks-and-how-to-stay-sane">Platform Quirks — And How to Stay Sane</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-security-and-privacy-at-scale">Security and Privacy at Scale</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-power-and-performance-tuning">Power and Performance Tuning</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-provisioning-and-firmware-updates-welcome-to-device-kindergarten">Provisioning and Firmware Updates — Welcome to Device Kindergarten</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-debugging-monitoring-and-testing-across-platforms">Debugging, Monitoring, and Testing Across Platforms</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-real-world-architecture-example-when-bluetooth-finally-behaves">Real-World Architecture Example — When Bluetooth Finally Behaves</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-checklist-building-a-truly-scalable-bluetooth-system">Checklist — Building a Truly Scalable Bluetooth System</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-wrap-up-lessons-from-the-field">Wrap-Up — Lessons from the Field</a></p>
</li>
</ul>
<h2 id="heading-bluetooth-has-two-personalities-meet-classic-and-ble">Bluetooth Has Two Personalities — Meet Classic and BLE</h2>
<p><img src="https://elainnovation.com/wp-content/uploads/2021/12/Bluetooth-VS-BLE-EN.jpg.webp" alt="What is the difference between Bluetooth and Bluetooth Low Energy (BLE)?" width="600" height="400" loading="lazy"></p>
<p>Before we can talk about scaling Bluetooth, we have to understand that Bluetooth itself has a bit of an identity crisis. It actually comes in two flavors: Classic Bluetooth and Bluetooth Low Energy, also called BLE. They share the same name and sometimes even live on the same chip, but under the hood they behave very differently. Think of them as twins who went to completely different schools and now have opposite personalities.</p>
<p>Classic Bluetooth is the older sibling. It was designed for steady, high-speed data streams. This is the version your headphones, speakers, and car systems use. It is reliable for sending large amounts of data like audio, but it is also chatty and power-hungry. It likes to stay connected all the time, constantly keeping the line open so it can send sound packets smoothly. You could say Classic Bluetooth is like that one friend who calls instead of texting and keeps the conversation going even when there is nothing left to say.</p>
<p>Then there is Bluetooth Low Energy, the younger, more introverted sibling. BLE was designed for devices that need to last for weeks or months on tiny batteries. It does not keep a constant connection open. Instead, it wakes up, sends or receives a little bit of data, and then goes back to sleep. It is the protocol behind fitness trackers, heart rate monitors, smart locks, and most modern IoT devices. If Classic Bluetooth is a full-time conversation, BLE is more like sending quick text messages throughout the day, short, efficient, and battery-friendly.</p>
<p>The funny thing is that even though they share the same wireless spectrum and sometimes even the same antenna, these two modes do not talk to each other directly. A BLE device cannot communicate with a Classic Bluetooth-only device. This is why your wireless headphones can pair with your phone, but your BLE heart rate monitor cannot talk to your old Bluetooth speaker. They live in the same neighborhood but never attend the same parties.</p>
<p>Most of the world’s scaling problems come from BLE, not Classic Bluetooth. Classic has been around long enough that its use cases are stable and well understood. BLE, on the other hand, is used in thousands of different kinds of devices, each with different timing requirements, power limits, and operating systems. When you try to make Android, iOS, and embedded systems all use BLE together, you are juggling three slightly different interpretations of the same rulebook.</p>
<p>To make things trickier, each platform implements BLE its own way. Android exposes it through flexible but sometimes unpredictable APIs. iOS keeps it tidy under Apple’s strict Core Bluetooth framework. Embedded devices rely on lightweight vendor stacks that can vary from chip to chip. Every one of these stacks follows the same Bluetooth specification, but like recipes written by different chefs, the results can taste a little different.</p>
<p>Understanding this dual nature is key to building anything that scales. You must know when to use Classic Bluetooth for high-speed continuous data, when to use BLE for low-power bursts, and how to design your system so that the right devices use the right mode. It is the first step in turning Bluetooth from a confusing mystery into a reliable network you can actually control.</p>
<h2 id="heading-android-ios-and-embedded-devices-the-odd-trio">Android, iOS, and Embedded Devices — The Odd Trio</h2>
<p><img src="https://cdn.dca-design.com/uploads/images/News/_full_width_content_image/105358/Bluetooth_DCA_News_Article_003.webp?v=1749036238" alt="Working with Bluetooth Low Energy across Android and iOS - News - DCA Design" width="600" height="400" loading="lazy"></p>
<p>Now that we know Bluetooth has two personalities, let’s meet the three characters that make scaling it so complicated: Android, iOS, and embedded devices. They all speak Bluetooth, but in their own unique accents. Sometimes they understand each other perfectly, and other times it feels like they’re arguing in three different languages while pretending they’re on the same page.</p>
<p>Let’s start with Android. Android is the enthusiastic extrovert of the group. It gives you tons of control and freedom. You can scan, connect, advertise, read, write, and basically poke around every corner of the Bluetooth stack. But that freedom comes with chaos. Because Android runs on phones made by dozens of manufacturers, each one tweaks the Bluetooth implementation a little differently. On one phone, everything works flawlessly. On another, the same code randomly drops connections or refuses to scan in the background. Even Android engineers joke that if your Bluetooth works the same on every device, you’ve probably entered a parallel universe.</p>
<p>Android is powerful but unpredictable. It’s like a sports car that can win a race on a good day but sometimes refuses to start if it doesn’t like the weather. The trick is to write code that expects weird behavior, to build your own connection queues, add retries, and prepare for the occasional glitch. Developers who survive Android Bluetooth bugs don’t just gain experience, they gain humility.</p>
<p>Then there’s iOS, Apple’s polished and opinionated perfectionist. Unlike Android, iOS is consistent. The same code usually behaves the same way across every iPhone and iPad. Apple’s Bluetooth framework, called Core Bluetooth, is beautifully organized and well-documented. But Apple also has strict rules about what you can and can’t do. Background scanning? Only in very specific cases. Advertising? Only for certain UUIDs. Access to lower-level Bluetooth layers? Absolutely not. Apple’s approach is like a luxury hotel: everything looks gorgeous, but you’re not allowed in the kitchen.</p>
<p>Working with iOS feels calm at first. Your connections are stable, your APIs are clear, and your devices behave predictably. But the moment you need to do something slightly unconventional, like connecting to multiple peripherals at once or keeping the app alive in the background, iOS politely says, “No, that’s not how we do things here.” Developers often end up performing delicate dances with background modes, notifications, and clever reconnection tricks just to make things feel seamless for users.</p>
<p>And then we have the third member of the trio: embedded devices. These are the quiet, uncomplaining ones that actually do most of the work. They live inside your smart sensors, wearables, and IoT nodes. They’re usually built around tiny chips with limited memory and low-power processors. They don’t have fancy operating systems or flashy UI frameworks. All they know is how to advertise, connect, send data, and then go back to sleep to save battery.</p>
<p>Embedded devices are loyal but easily overwhelmed. They can’t handle constant large data transfers, and they get cranky if you make them maintain too many simultaneous connections. Imagine trying to run a marathon after eating one grape, that’s what it’s like for a small BLE chip to handle too much traffic. Yet, these little devices are the backbone of every scalable Bluetooth network. They measure your heart rate, control your smart lights, and track your environmental sensors, all while running quietly in the background.</p>
<p>The real challenge begins when you try to make these three cooperate. Android wants freedom, iOS wants structure, and embedded devices just want a nap. Getting them all to work together is like managing a group project where one person writes essays at midnight, another color-codes everything, and the third forgets to charge their laptop. But when you finally get it right, when Android, iOS, and your embedded nodes connect seamlessly, it feels like magic again.</p>
<p>In the next section, we’ll explore how to actually make that happen. You’ll see how to design a Bluetooth architecture that scales gracefully across these platforms instead of collapsing into a pile of logs and retries. It’s part engineering, part patience, and part diplomacy.</p>
<h2 id="heading-architecting-for-scale-herding-cats-but-wirelessly">Architecting for Scale — Herding Cats, but Wirelessly</h2>
<p>If there’s one secret to scaling Bluetooth, it’s this: treat it like herding cats. You’ll never truly <em>control</em> it, but with enough structure, patience, and a bit of catnip (or clever engineering), you can convince all the cats to move in roughly the same direction.</p>
<p>Building a Bluetooth system that spans Android, iOS, and embedded devices isn’t just about writing code that connects things. It’s about designing <em>relationships</em>, the rules and boundaries that keep those connections healthy. The key idea here is <strong>architecture</strong>, which is a fancy word for “deciding who does what, when, and how.” Without a solid architecture, your Bluetooth project quickly turns into a tangle of callbacks, disconnections, and unanswered packets.</p>
<p>The first principle of Bluetooth architecture is <strong>abstraction</strong>. Every platform has its own Bluetooth API, but the basic idea is always the same: scan for devices, connect, exchange data, and disconnect. So instead of writing separate logic for each platform, you create one unified interface, a sort of translator layer, that hides all the messy differences underneath. In practice, this means you can write something like <code>connect(device)</code> in your app, and whether you’re on Android, iOS, or even a Raspberry Pi, the underlying code figures out how to make it happen.</p>
<p>This abstraction layer is your peacekeeper. It prevents the rest of your app from needing to know whether it’s talking to a Nordic chip on a wristband, a smart bulb using an ESP32, or an iPhone pretending to be a peripheral. When you have hundreds or thousands of devices, abstraction isn’t just convenient, it’s survival.</p>
<p>Next comes <strong>connection management</strong>. BLE connections are like toddlers: they demand constant attention and can vanish the moment you look away. A scalable Bluetooth system can’t afford to panic every time a device disconnects. Instead, you design it to expect chaos. You add automatic retries, reconnection strategies, and timeouts that gracefully handle failures instead of freezing your app. Good systems don’t assume the network will always behave, they assume it won’t.</p>
<p>Then there’s <strong>data orchestration</strong>, deciding who talks first, how much data gets sent, and how you keep multiple connections from tripping over each other. Imagine you’re a conductor in an orchestra where half the instruments fall asleep randomly to save power. You need a plan that lets each device play its part in harmony without draining its battery. That’s what managing Bluetooth data flow feels like.</p>
<p>And finally, there’s <strong>power strategy</strong>. Embedded devices live on tight energy budgets. Every scan, advertisement, and data exchange eats into their lifespan. So, your architecture must schedule communication intelligently, let devices wake up briefly, share data, and return to sleep before they burn out. The best Bluetooth systems look lazy on the surface but are actually brilliant planners underneath.</p>
<p>When you put all of this together, abstraction, connection management, orchestration, and power control, you get something that <em>scales</em>. It doesn’t matter if you’re managing three wearables or three thousand sensors. The system behaves predictably, logs issues instead of panicking, and recovers from disconnections automatically.</p>
<p>Think of it like a well-run airport. Planes (your devices) take off and land constantly. The control tower (your app’s Bluetooth manager) keeps track of who’s in the air, who’s landing next, and who needs maintenance. No single pilot needs to know everything, they just follow the protocol.</p>
<p>Scaling Bluetooth isn’t about being clever with one device. It’s about designing systems that keep working even when dozens of devices act unpredictably. You don’t tame Bluetooth by force; you do it by creating a world where even chaos feels organized.</p>
<p>In the next section, we’ll dig deeper into how these connections actually behave in real time, how devices discover each other, exchange data, and, sometimes, break up without warning.</p>
<h2 id="heading-connection-discovery-and-data-flow-the-bluetooth-dating-game">Connection, Discovery, and Data Flow — The Bluetooth Dating Game</h2>
<p>Every Bluetooth connection starts like a modern love story. One device sends out signals into the air, announcing that it’s available. Another device scans the surroundings, hoping to find something compatible. When they finally spot each other, they exchange a few polite packets, decide they’re a good match, and try to make it official with a connection. It’s wireless romance, until one of them walks away without saying goodbye.</p>
<p>This is the heart of how Bluetooth works: <strong>advertising, discovery, and connection</strong>. An embedded sensor or wearable device usually plays the role of the advertiser. It broadcasts tiny packets called advertisements that contain just enough information to say, “Hey, I’m here, and I can measure temperature or heart rate or unlock your door.” These packets are intentionally small because transmitting data takes energy, and low-power devices have to conserve every drop of battery life.</p>
<p>Meanwhile, your phone or tablet acts as the scanner, it listens to the radio waves around it, searching for those signals. When it finds one that matches what it’s looking for, it sends a request to connect. If the peripheral accepts, they move into a new relationship phase: the <strong>GATT connection</strong>. GATT stands for Generic Attribute Profile, which is basically the language they use to talk. Once connected, your phone can ask the device for specific data, like reading a heart rate measurement or writing a configuration setting.</p>
<p>Now, if all of this sounds peaceful and predictable, that’s because we haven’t talked about what happens in the real world. In reality, devices move around, signals weaken, and phones go into power-saving modes that forget they were even connected. Connections drop. Pairing sometimes fails. And when you have ten or more devices talking at once, managing all those tiny wireless conversations becomes a circus act.</p>
<p>Scaling Bluetooth is all about keeping this circus under control. You can’t force every device to stay connected forever, that would drain batteries and jam the radio channels. Instead, you design a rhythm. Devices connect only when needed, exchange data quickly, and then disconnect to rest. This constant dance of connecting and disconnecting keeps the system efficient and stable.</p>
<p>Think of it like a well-run coffee shop. Customers (phones) walk in, place their order (data request), get their coffee (response), and leave. The barista (the embedded device) doesn’t serve one person all day, it serves everyone in quick cycles. The trick is to make sure no one gets stuck waiting for their latte forever.</p>
<p>Timing is everything in this dance. If a device advertises too infrequently, the phone might not discover it in time. If it advertises too often, it wastes power. If the phone sends too many requests at once, the device might crash or slow down. Bluetooth connections live in this delicate balance between performance and efficiency.</p>
<p>When you scale, you also have to think about coordination. Imagine one phone trying to talk to ten sensors at once. You can’t have it flood them all with requests simultaneously, it needs a queue, a polite way of saying “you first, then me.” This is called <strong>connection orchestration</strong>, and it’s one of the hardest parts of scaling BLE systems.</p>
<p>And then there’s the breakup. Devices disconnect all the time, sometimes intentionally, sometimes accidentally. The best Bluetooth systems treat disconnections not as failures but as normal events. The app automatically retries, reconnects, and syncs data without asking the user to “try again.” To users, it feels seamless. Underneath, there’s a lot of quiet heroism happening, background threads, timers, and reconnection logic all working together to patch up relationships on the fly.</p>
<p>So, at its core, Bluetooth is less like a stable marriage and more like speed dating with excellent scheduling. Everyone meets briefly, exchanges information, and moves on. When done right, this model scales effortlessly. When done wrong, it’s chaos.</p>
<p>In the next section, we’ll explore the quirks that make Android, iOS, and embedded devices behave differently in this dating game, and how to keep the peace when one of them inevitably ghosts the others.</p>
<h2 id="heading-platform-quirks-and-how-to-stay-sane">Platform Quirks — And How to Stay Sane</h2>
<p>Once you start scaling Bluetooth, you’ll notice something odd. The same code that works perfectly on one device suddenly refuses to behave on another. It’s like watching identical twins argue about who gets the last slice of pizza, they may look the same, but their personalities couldn’t be more different.</p>
<p>Let’s start with Android, the unpredictable one. Android gives developers more power than any other mobile platform. You can scan however you like, filter by services, read and write any characteristic, and even customize connection intervals. But that power comes at a price. Every phone manufacturer modifies the Bluetooth stack slightly. Samsung, Pixel, OnePlus, Xiaomi, each adds its own flavor of “enhancement,” which sometimes translates to “surprise, nothing works the same.”</p>
<p>One Android phone might handle ten connections at once without blinking. Another might drop all of them the moment the screen turns off. Some versions ignore Bluetooth permissions until you grant location access. Others claim they’re scanning when they actually stopped five minutes ago. Android developers eventually stop asking <em>why</em> and simply build more logging instead. The rule of thumb with Android Bluetooth is simple: test everything, assume nothing, and expect the unexpected.</p>
<p>Then there’s iOS, which at first feels like a breath of fresh air. Apple’s Core Bluetooth framework is clean, consistent, and almost elegant. You get predictable callbacks, smooth reconnections, and well-behaved devices. But if you step outside Apple’s boundaries, you’ll quickly find invisible fences. iOS doesn’t let apps scan in the background freely. It limits how often you can advertise. And if your app tries to keep too many simultaneous connections alive, iOS politely steps in and shuts them down.</p>
<p>Apple’s philosophy is control. It wants Bluetooth connections to behave in ways that don’t drain the battery or clutter the radio. That’s great for users, but for developers it can feel like being handed the keys to a Ferrari and told you can only drive in the parking lot. It works beautifully, as long as you color inside the lines.</p>
<p>And then we have embedded devices, which are in a category of their own. These are the little chips sitting inside your wearables, sensors, or IoT gadgets. They don’t have operating systems or background processes. They just run tiny loops of firmware that listen, respond, and sleep. Their quirks are more about physics than software. If the antenna isn’t tuned properly, signals drop. If the power supply fluctuates, the radio turns off. Sometimes they disconnect simply because a human walked between two devices and absorbed the signal.</p>
<p>Embedded Bluetooth stacks also differ by manufacturer. Nordic, Espressif, Silicon Labs, Texas Instruments, each has its own libraries, quirks, and limitations. Even small changes like increasing the packet size or adjusting the advertising interval can make or break communication. It’s a careful dance between efficiency and reliability.</p>
<p>Now imagine you’re trying to get all three of these worlds to cooperate. Android wants freedom, iOS enforces discipline, and embedded devices want long naps. Building a Bluetooth system that works across all of them is like running a daycare with overachievers, rule-followers, and kids who fall asleep mid-activity. You can’t treat them all the same, but you can design a routine that keeps everyone content.</p>
<p>The secret is resilience. Instead of expecting perfect behavior, build your system around imperfections. Add retries when connections fail. Cache data so you don’t lose progress during disconnections. Keep your embedded devices simple, your mobile apps forgiving, and your logs brutally honest.</p>
<p>If you design with these quirks in mind, your Bluetooth system will feel almost magical, even though, behind the scenes, it’s a web of error handling, reconnections, and polite compromise.</p>
<p>In the next section, we’ll take a look at another side of scaling: keeping everything secure and private while all these devices whisper secrets over the air.</p>
<h2 id="heading-security-and-privacy-at-scale">Security and Privacy at Scale</h2>
<p>Once your Bluetooth system starts working reliably, there’s another challenge waiting in the wings: keeping it <strong>secure</strong>. It’s one thing to get devices talking to each other, it’s another to make sure no one else is eavesdropping on the conversation. Bluetooth security can sound intimidating, but at its core, it’s about making sure your devices trust each other and that strangers can’t sneak into the chat.</p>
<p>Let’s start with pairing. Pairing is Bluetooth’s version of saying, “Hey, can I trust you?” It’s a handshake where two devices exchange keys that let them communicate securely in the future. There are a few ways this handshake can happen. The simplest is called <em>Just Works</em>, which basically means, “We’ll trust each other without asking too many questions.” It’s convenient but about as safe as leaving your front door unlocked because you live in a nice neighborhood. For harmless gadgets like wireless speakers, that’s fine. But for medical devices or smart locks, “Just Works” can turn into “Just Got Hacked.”</p>
<p>A safer approach is <strong>Passkey Entry</strong>, where one device shows a code and the other types it in, proving they’re physically near each other. Even better is <strong>Out-of-Band (OOB)</strong> pairing, where the devices exchange security information through another method, maybe a QR code, NFC tap, or even an optical blink, before connecting over Bluetooth. OOB pairing is like verifying someone’s identity face-to-face before continuing a conversation online.</p>
<p>Once paired, devices use <strong>encryption</strong> to scramble their communication. Anyone listening nearby will hear only gibberish. The strength of that encryption depends on the version of Bluetooth being used. Modern devices using Bluetooth 4.2 or later support something called <em>LE Secure Connections</em>, which is based on advanced cryptography. Older devices use weaker methods that are easier to crack. So, if you’re building something new, never rely on outdated pairing modes.</p>
<p>But security isn’t just about encryption. It’s also about <strong>privacy</strong>. Every Bluetooth device has an address, kind of like its phone number, that it uses when broadcasting. If that address stays the same, someone could track you by following your device’s broadcasts. That’s why newer standards support <em>random address rotation</em>, where devices periodically change their Bluetooth address. Your phone and smartwatch still recognize each other, but strangers can’t follow your signal around the city.</p>
<p>When you scale Bluetooth systems, these little details become critical. A single insecure device in your network can become the weak link that compromises everything. It’s like locking every door in your house but leaving one window open. Attackers don’t need to break the whole system, they just need to find the lazy one.</p>
<p>Building security into a large Bluetooth deployment means standardizing your pairing process, using strong encryption everywhere, and handling key storage carefully. On embedded devices, that can be tricky because they have limited memory and no secure element by default. Still, even small steps help, like regenerating keys periodically and disabling “Just Works” mode for devices that control anything important.</p>
<p>On mobile platforms, the rules are slightly different. Android and iOS handle much of the heavy lifting for you, but you still have to design your app logic carefully. Always confirm which device you’re connecting to before exchanging sensitive data. Always check bonding state before sending configuration commands. In short, treat Bluetooth communication with the same seriousness you’d give to a login session or an online payment.</p>
<p>At scale, security isn’t something you bolt on later. It’s part of the system’s DNA. You can’t fix a weak handshake by adding a stronger password later. You have to start from the first pairing and make sure every connection trusts the right partner.</p>
<p>The reward is worth it. When done right, your Bluetooth network becomes invisible but secure, a quiet, encrypted web of trust that just works. No drama, no leaks, and no nearby strangers hijacking your sensors.</p>
<p>In the next section, we’ll talk about another invisible problem that decides whether your Bluetooth network lives for days or months: power. Because what good is a secure device if its battery dies halfway through the handshake?</p>
<h2 id="heading-power-and-performance-tuning">Power and Performance Tuning</h2>
<p>If you’ve ever wondered why your Bluetooth gadget dies right when you need it most, you’ve just met the oldest enemy in wireless communication: power consumption. Bluetooth may be clever, flexible, and everywhere, but it also has a bit of a caffeine problem. It loves to talk, and talking burns energy. Keeping your devices alive longer, especially when you scale, means learning the quiet art of power management.</p>
<p>At first, it’s easy to assume that Bluetooth is low power by default. After all, it’s called <strong>Bluetooth Low Energy</strong>, right? But BLE’s efficiency only shines when it’s used correctly. A poorly tuned BLE system can drain a battery faster than streaming music over Classic Bluetooth. The magic lies in controlling when devices talk, how long they talk, and how much they say each time.</p>
<p>Let’s start with the <strong>advertising interval</strong>. This is how often a device shouts, “I’m here!” into the air. If you set it to broadcast every 20 milliseconds, you’ll discover devices quickly, but you’ll also burn through the battery like it’s running a marathon. Increase the interval to once every second, and your device will last much longer, but phones may take a moment to find it. It’s a tradeoff between speed and stamina. Every system has to find its sweet spot.</p>
<p>Next comes the <strong>connection interval</strong>, how often two connected devices exchange data. This is like deciding how frequently you check your messages. If you check every second, you stay perfectly up to date but never get anything else done. If you check once every minute, you save time but risk missing something important. In Bluetooth terms, a shorter connection interval means faster communication but higher power usage. Longer intervals conserve battery but add delay. Smart systems adjust these intervals dynamically depending on what the device is doing.</p>
<p>Then there’s the <strong>MTU</strong>, or Maximum Transmission Unit, the size of each Bluetooth data packet. Bigger packets mean fewer total transmissions for large chunks of data, which can improve efficiency. But some devices, especially older ones, can’t handle large MTUs, so finding the right balance is important.</p>
<p>Power management is not just about numbers, it’s about habits. A well-designed embedded device spends most of its life asleep. It wakes up only to advertise or exchange data, then returns to rest as quickly as possible. Imagine a hummingbird darting out for a sip of nectar and then zipping back to rest before anyone notices. That’s how efficient Bluetooth devices survive on coin-cell batteries for months or even years.</p>
<p>On the phone side, energy management is just as critical, especially when your app needs to handle multiple connections. Constant scanning, reconnecting, or keeping GATT channels open drains your user’s battery, and patience. Android and iOS both have built-in mechanisms that throttle background Bluetooth activity to save power. Developers have to work with these rules, not against them. The best apps schedule scans intelligently, reconnect only when necessary, and avoid holding connections open when no data needs to be sent.</p>
<p>Scaling Bluetooth systems makes these power decisions even more important. When you have one device, wasting a bit of energy doesn’t matter. When you have hundreds of devices, each one burning just a few extra milliwatts, the total waste adds up quickly. Power efficiency becomes the difference between a network that runs for months and one that collapses after a week.</p>
<p>The golden rule of power tuning is simple: talk less, talk smarter. A Bluetooth device that knows when to speak and when to stay quiet can scale beautifully, even in large networks. It’s not about being fast all the time, it’s about being clever with timing.</p>
<p>In the next section, we’ll look at how these devices join your network in the first place and what happens when you need to update their software later. Because once your system scales, you’re not just connecting devices, you’re managing an entire population.</p>
<h2 id="heading-provisioning-and-firmware-updates-welcome-to-device-kindergarten">Provisioning and Firmware Updates — Welcome to Device Kindergarten</h2>
<p>Imagine setting up one Bluetooth device. It’s easy: you pair it, give it a name, and maybe tweak a few settings. Now imagine doing that a hundred times. Or a thousand. Suddenly, what felt like a simple task starts to look like a factory assembly line powered by frustration. That’s where <strong>provisioning</strong> comes in, the process of onboarding new devices into your Bluetooth network so they can start working right away, without manual babysitting.</p>
<p>Provisioning is like a first day at school for your devices. Each new student needs to be identified, assigned to a class, and given a name tag. In the Bluetooth world, a newly manufactured device begins life in an “unprovisioned” state. It doesn’t belong to any network yet, so it advertises with a special signal that says, “Hey, I’m new here.” When your mobile app or gateway spots that advertisement, it can connect, authenticate the device, and hand over the credentials it needs to join the system.</p>
<p>The app usually performs a few key steps during provisioning. It verifies that the device is genuine, assigns it a unique identifier, and exchanges security keys so future connections can happen securely. It might also store metadata like which room the sensor belongs to or what type of data it will report. After provisioning, the device switches to its normal operation mode, where it advertises with its new identity and starts behaving like a member of the family.</p>
<p>When you have just one or two devices, you can do all this manually. But when you scale up to hundreds or thousands, manual setup becomes impossible. That’s when you start thinking about automation, QR codes on packaging, NFC tags for instant pairing, or out-of-band provisioning where a separate channel (like Wi-Fi or a wired link) handles secure onboarding. The goal is to make provisioning quick, repeatable, and error-free, even when your factory or users are adding new devices by the dozens.</p>
<p>Once your devices are out in the world, the next challenge appears: <strong>firmware updates</strong>. Every system eventually needs to fix bugs, patch security holes, or add new features. For Bluetooth devices, this means pushing new firmware over the same wireless link, a process known as <strong>FOTA</strong>, or firmware-over-the-air updates.</p>
<p>Updating firmware over Bluetooth can be nerve-wracking. The connection is relatively slow, and interruptions can leave a device half-updated and confused about who it is. Good update systems handle this carefully. They divide the firmware into chunks, verify each piece with checksums, and only switch to the new version once the whole update has been safely received and validated. If anything fails midway, the device rolls back to the old firmware instead of bricking itself.</p>
<p>Scaling makes this even more complex. Updating ten devices is fine. Updating a thousand can overwhelm your network if you try to do them all at once. Smart systems stagger the updates in waves, track which devices have finished, and retry the ones that didn’t. Some even let devices report their status back to a central dashboard, so you can see which ones are ready and which ones are still stuck halfway through.</p>
<p>Provisioning and firmware updates might not sound glamorous, but they’re the backbone of every scalable Bluetooth system. Without smooth onboarding and reliable updates, your network slowly falls apart as devices drift out of sync or miss critical fixes.</p>
<p>Think of it this way: provisioning is how devices <em>join the family</em>, and firmware updates are how they <em>grow up</em>. Both are essential if you want your Bluetooth ecosystem to stay healthy and dependable over time.</p>
<p>In the next section, we’ll talk about what happens when something inevitably goes wrong, how to debug and monitor a network full of devices without losing your mind.</p>
<h2 id="heading-debugging-monitoring-and-testing-across-platforms">Debugging, Monitoring, and Testing Across Platforms</h2>
<p>At some point, every Bluetooth developer faces the same moment of quiet despair. The logs look fine, the devices are paired, the code hasn’t changed, and yet… nothing works. Connections fail, packets vanish, and everything that worked yesterday now refuses to cooperate. Welcome to the wonderful, mysterious world of Bluetooth debugging, a place where logic takes a vacation and patience becomes your most valuable skill.</p>
<p>Debugging Bluetooth is tricky because so much of it happens invisibly. The data is flying through the air, hopping between frequencies dozens of times per second, and all you can see is whether the connection succeeds or fails. It’s like trying to diagnose a conversation between two people whispering in another room. You can tell they’re talking, but not what they’re saying.</p>
<p>The first rule of Bluetooth debugging is simple: <strong>log everything</strong>. Log when you start scanning, when you find a device, when you connect, and when you disconnect. Log the signal strength, the UUIDs you discover, the number of bytes you read, and the time it took. Bluetooth problems rarely announce themselves loudly, they hide in tiny details. A small delay in a callback or a missing acknowledgment can reveal exactly why your system seems haunted.</p>
<p>Different platforms give you different kinds of help. Android, for example, offers detailed Bluetooth logs through developer options or tools like <code>adb</code>. You can capture the raw Bluetooth HCI logs and analyze them later to see what really happened under the hood. iOS, on the other hand, gives you less direct visibility. Apple handles most of the Bluetooth stack internally, so your only clues come from Core Bluetooth callbacks. Embedded devices often let you log directly from the firmware, showing connection events, error codes, and sometimes even packet-level information if the stack supports it.</p>
<p>Testing across platforms is just as important as debugging. You can’t assume that if it works on one phone, it will work on another. Android devices, especially, have a habit of interpreting Bluetooth timing slightly differently. A system that’s rock-solid on a Pixel may stutter on a Samsung or freeze on a low-cost tablet. The only cure is diversity, test on multiple brands, OS versions, and firmware builds until you’re confident the system behaves everywhere.</p>
<p>For embedded devices, testing is a different challenge. Because they often run continuously, you need long-term endurance tests to catch issues that only appear after hours or days of operation. You might discover that a connection fails only after 300 reconnections, or that a memory leak appears after a week of normal use. Building test rigs that automate these scenarios: connecting, disconnecting, and verifying data repeatedly, is a huge time saver.</p>
<p>Monitoring is what happens after you’ve deployed your devices into the real world. It’s like keeping a health tracker on your entire Bluetooth network. Your mobile apps or gateways can collect statistics such as signal strength, connection failures, uptime, and battery levels. That data tells you which devices are performing well and which ones might be drifting toward trouble.</p>
<p>Adding this kind of visibility pays off enormously at scale. When you’re managing hundreds of devices, it’s impossible to check each one manually. Instead, you rely on trends, for example, if one location shows consistently weak signal strength, maybe there’s interference nearby. If multiple devices drop connections at the same time, maybe the central device needs a firmware update. Monitoring transforms guesswork into insight.</p>
<p>The truth is, debugging and monitoring never really end. Even after your system is stable, new versions of Android and iOS will appear with small Bluetooth changes that break something you didn’t know could break. Treat Bluetooth maintenance like car maintenance: routine, ongoing, and essential.</p>
<p>Once you learn to capture good logs, read them calmly, and build systems that report their own health, debugging stops being a nightmare and becomes a science. Bluetooth may always be a little mysterious, but with the right tools and attitude, you can keep the ghosts out of your connection list.</p>
<p>In the next section, we’ll put everything together with a real-world example of what scaling Bluetooth actually looks like when all the pieces: mobile apps, embedded devices, and architecture, finally work in harmony.</p>
<h2 id="heading-real-world-architecture-example-when-bluetooth-finally-behaves">Real-World Architecture Example — When Bluetooth Finally Behaves</h2>
<p>Let’s take everything we’ve talked about and bring it to life with a real-world scenario. Imagine you’re building a smart factory system with hundreds of Bluetooth sensors scattered across the floor. Each sensor measures temperature, vibration, or humidity. Some are attached to machines, others hang on walls, and a few are hidden in places even the janitor doesn’t know about. Your goal is simple on paper: collect data from all these sensors, send it to a central dashboard, and keep everything running smoothly.</p>
<p>The reality, of course, is much more complicated. Each sensor is an embedded device powered by a coin-cell battery that has to last for months. They advertise periodically to announce they’re alive. Your Android or iOS tablets, placed around the factory as gateways, act as Bluetooth centrals. Their job is to scan, connect to nearby sensors, read data, and upload it to the cloud. It sounds straightforward, but you’re juggling dozens of invisible connections at once, and they all have different moods.</p>
<p>The architecture begins with careful planning. Each gateway tablet knows which part of the factory it’s responsible for. That way, you avoid overcrowding the airwaves with multiple devices trying to connect to the same sensors. The sensors use slightly staggered advertising intervals so they don’t all shout at the same time. The gateways maintain a queue, connecting to a few sensors at a time, reading data, and then disconnecting before moving on to the next group. This rotation keeps everything balanced and prevents Bluetooth traffic jams.</p>
<p>Power management is built into every step. Each sensor wakes up, advertises briefly, sends its data when connected, and goes right back to sleep. The connection interval and MTU size are tuned for efficiency, large enough for smooth data transfer, but not so large that slower devices choke. Every byte is treated like gold because every transmission costs energy.</p>
<p>The gateways handle the messy parts: reconnections, retries, and data aggregation. They buffer readings in case the Wi-Fi link to the cloud goes down and sync later when it’s back. They also monitor each sensor’s signal strength, battery level, and uptime. If a sensor hasn’t reported in a while, the system flags it automatically so a technician can check on it.</p>
<p>Now imagine scaling this setup to multiple factory buildings. Suddenly, you’re managing thousands of sensors, dozens of gateways, and countless wireless interactions. At this scale, the design choices you made early, abstracted Bluetooth logic, retry mechanisms, power optimization, and logging, are the difference between a quiet, self-running network and a system that collapses into constant reconnections.</p>
<p>When everything works as intended, something beautiful happens. The sensors collect data silently. The gateways synchronize automatically. The dashboards stay green. Nobody has to restart anything, and Bluetooth quietly fades into the background where it belongs. It’s the rare moment when technology stops demanding attention and simply does its job.</p>
<p>This kind of architecture isn’t science fiction. Companies use it in factories, hospitals, and warehouses every day. From smart lighting systems to patient monitors, Bluetooth at scale can be astonishingly reliable, but only if you treat it like a distributed system, not a single gadget. Each device is a citizen of a larger ecosystem, and your job as the architect is to keep that ecosystem healthy.</p>
<p>The biggest takeaway is that success doesn’t come from fancy algorithms or expensive hardware. It comes from the small, deliberate decisions that make your system resilient: how you handle disconnections, how you schedule connections, how you monitor performance. Scaling Bluetooth is not about avoiding problems, it’s about designing a system that recovers gracefully when problems happen.</p>
<p>In the next section, we’ll wrap up everything we’ve learned into a practical checklist, a simple guide you can use whenever you’re designing a Bluetooth system that has to survive in the wild.</p>
<h2 id="heading-checklist-building-a-truly-scalable-bluetooth-system">Checklist — Building a Truly Scalable Bluetooth System</h2>
<p>By now, you’ve seen Bluetooth in all its moods, charming, confusing, unpredictable, and surprisingly capable when handled with care. So how do you actually put everything together? What makes a Bluetooth system <em>scalable</em> instead of just “working on my desk”? The answer isn’t a single trick or secret API. It’s a mindset, a way of designing your system to expect chaos and still function gracefully when it happens.</p>
<p>The first part of that mindset is consistency. Every Bluetooth system should have one clear and stable way of communicating. Keep your data formats simple, your GATT profiles predictable, and your naming conventions sensible. If you have ten devices made by ten different vendors, make them all speak the same language. The moment one device starts improvising, the whole orchestra sounds off.</p>
<p>Next comes patience, and in Bluetooth, patience means retries. Connections drop. Devices go out of range. A phone might go to sleep or decide that scanning is no longer fashionable. Instead of treating every disconnection as a crisis, treat it as part of the process. A good Bluetooth app quietly retries in the background, restores the connection, and carries on as if nothing happened. To the user, it feels seamless. Underneath, it’s a flurry of logic keeping the experience smooth.</p>
<p>Then there’s the question of power. Remember that every advertisement and connection eats into battery life. A scalable Bluetooth system doesn’t talk all the time, it talks <em>smart</em>. It plans when to wake up, when to exchange data, and when to stay silent. Devices that last longer need fewer replacements, fewer updates, and far less human attention. Power efficiency is the hidden currency of scalability.</p>
<p>Monitoring is another essential habit. If you can’t see what’s happening inside your system, you’re flying blind. Log your connections, track your signal strengths, record how often devices drop out, and visualize it somewhere. A simple dashboard that shows which devices are healthy and which ones are struggling can save you countless hours later. When you scale, visibility turns guesswork into control.</p>
<p>Security, too, can’t be an afterthought. Use secure pairing, proper encryption, and rotating addresses. The bigger your system gets, the more interesting it becomes to people who might want to peek at it. Make sure they can’t. A secure Bluetooth network doesn’t just protect users, it protects your reputation.</p>
<p>Finally, build for change. Bluetooth isn’t static, Android and iOS update their stacks every year, chip vendors release new firmware, and new security standards appear. A scalable system doesn’t break when something changes, it adapts. That’s why abstraction layers, modular code, and updatable firmware matter so much. They keep your system flexible long after the first version ships.</p>
<p>If you do all of this, keep it consistent, patient, efficient, observable, secure, and adaptable, something magical happens. Your Bluetooth system starts to feel less like a fragile web of devices and more like a living network. It keeps running, keeps healing, and quietly gets the job done without constant supervision. That’s when you know you’ve built something that scales.</p>
<p>In the final section, we’ll step back and reflect on the bigger picture, what scaling Bluetooth really teaches us about building technology that has to work not just once, but over and over again in the messy, beautiful real world.</p>
<h2 id="heading-wrap-up-lessons-from-the-field">Wrap-Up — Lessons from the Field</h2>
<p>If you’ve made it this far, you’ve probably realized that scaling Bluetooth isn’t really about Bluetooth at all. It’s about learning how complex systems behave when they leave the comfort of your desk and enter the real world. It’s about understanding that wireless connections are not just electrical signals, they’re relationships between unpredictable, battery-powered, opinionated little machines.</p>
<p>Bluetooth gets a bad reputation because people expect it to be simple. They imagine it’s like Wi-Fi or USB, plug and play, pair and forget. But in truth, Bluetooth is more like a polite conversation at a crowded party. Everyone is talking at the same time, the music is loud, and you have to keep repeating yourself until the other person hears you correctly. When you think of it that way, it’s a miracle that it works as well as it does.</p>
<p>Scaling Bluetooth across Android, iOS, and embedded devices teaches you humility. You stop assuming things will always behave, and instead you start building systems that <em>recover</em> when they don’t. You learn that error handling is not an afterthought, it’s the main event. You discover that batteries are precious, timing is everything, and the smallest design decisions can ripple through an entire ecosystem of devices.</p>
<p>You also start to appreciate the quiet beauty of resilience. There’s something deeply satisfying about watching dozens of sensors, gateways, and phones connect, share data, and disconnect, all without human intervention. When it works, it feels effortless. You forget about the retries, the power cycles, the reconnections, and the debugging sessions that made it possible. All you see is a smooth network humming quietly in the background, doing exactly what it was meant to do.</p>
<p>And that’s the real magic of Bluetooth, not the flashy tech demos or the pairing animations, but the invisible collaboration that happens beneath the surface. It’s the heartbeat of every wearable, every sensor, every tiny device that quietly makes our lives a little easier. Scaling it isn’t just an engineering challenge; it’s a lesson in patience, design, and empathy for systems that can’t always speak for themselves.</p>
<p>So, the next time your Bluetooth device disconnects, take a breath. Somewhere in the chaos, it’s just trying to reconnect, to find its partner again and pick up where it left off. Because deep down, that’s what Bluetooth really is: a network built on trust, persistence, and tiny packets of hope flying through the air.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How Bluetooth Socket Settings Power Android’s Low Power Island: A Friendly Deep Dive into AOSP’s Hidden Energy Saver ]]>
                </title>
                <description>
                    <![CDATA[ Picture this: you’re sitting in a café with your laptop open, phone on the table, smartwatch buzzing every few minutes, and Bluetooth earbuds playing music. From your perspective, life is peaceful. From your phone’s perspective, it’s juggling a ridic... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-bluetooth-socket-settings-power-androids-low-power-island-a-friendly-deep-dive-into-aosps-hidden-energy-saver/</link>
                <guid isPermaLink="false">69164aadd6505b750fa4b659</guid>
                
                    <category>
                        <![CDATA[ BluetoothSocket ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Offload ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Android ]]>
                    </category>
                
                    <category>
                        <![CDATA[ LowPowerConsumption ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Nikheel Vishwas Savant ]]>
                </dc:creator>
                <pubDate>Thu, 13 Nov 2025 21:16:29 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1763071691608/30075d98-7eb4-4f87-9396-d76d91c92fea.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Picture this: you’re sitting in a café with your laptop open, phone on the table, smartwatch buzzing every few minutes, and Bluetooth earbuds playing music. From your perspective, life is peaceful. From your phone’s perspective, it’s juggling a ridiculous number of tiny Bluetooth packets all the time.</p>
<p>Every time your watch syncs your steps, every time your earbuds receive another chunk of audio, every time a background device checks in – the main application processor inside your phone is forced to wake up, look at the data, decide what to do with it, and then go back to sleep. Do that a few thousand times, and suddenly that nice 5000 mAh battery starts feeling suspiciously small.</p>
<p>Android engineers looked at this pattern and basically said, what if we don’t wake up the big CPU for every tiny Bluetooth thing? What if we had a smaller helper brain whose entire job is to handle boring repetitive Bluetooth traffic while the main CPU relaxes? That’s exactly where the concept of a Low Power Island, usually shortened to LPI, comes in.</p>
<p>In modern Android Bluetooth architecture, especially from the <a target="_blank" href="https://source.android.com/docs/whatsnew/android-16-release">AOSP 16</a> generation onward, a good chunk of Bluetooth work can be offloaded to a dedicated low power processor that sits closer to the Bluetooth radio. This little processor is embedded in the Bluetooth controller or SoC and is designed to run very efficiently. It consumes much less power than the main CPU and can stay awake without draining your battery like a full application processor would. Android’s job is to decide which traffic can live on this island and which traffic still needs the main CPU.</p>
<p>But how does Android make that decision in practice? This is where Bluetooth sockets and something called <a target="_blank" href="https://developer.android.com/reference/android/bluetooth/BluetoothSocketSettings">BluetoothSocketSettings</a> enter the story.</p>
<p>In a regular app, when you open a <a target="_blank" href="https://developer.android.com/reference/android/bluetooth/BluetoothSocket">BluetoothSocket</a>, it feels like you’re just opening a pipe so you can send and receive bytes. Under the hood though, the framework is asking a much deeper question: should this pipe go through the big highway that wakes up the main CPU, or can this pipe be connected directly into the low power island’s private road network?</p>
<p>In the latest AOSP Bluetooth stack, the answer to that question is expressed through a tiny configuration object: BluetoothSocketSettings. This class lets system level code describe how a socket should behave. It can specify whether the data should be kept on the normal host path or offloaded into a hardware data path that ends on the low power processor.</p>
<p>Inside, there are fields like <code>DATA_PATH_NO_OFFLOAD</code> and <code>DATA_PATH_HARDWARE_OFFLOAD</code>, plus extra information like <code>hubId</code>, <code>endpointId</code>, and <code>requestedMaximumPacketSize</code> that help the controller understand how to route packets in the LPI world.</p>
<p>From the outside, it still looks like you’re dealing with a normal BluetoothSocket. Inside the Bluetooth framework though, that socket is now tagged with extra metadata that quietly tells the Bluetooth stack: this one is special, send it to the island.</p>
<p>The host stack then talks to a new layer of code in the Bluetooth system called the LPP offload manager and a socket specific HAL (Hardware Abstraction Layer) so that the low power processor can be informed whenever a socket is opened or closed, and can claim responsibility for handling the data.</p>
<p>So if we keep the café analogy, previously every Bluetooth customer shouted their order directly at the main barista. With Low Power Island and BluetoothSocketSettings, Android can say, “these regular espresso orders can go through the junior barista at the side counter. Only the weird custom drinks still go to the main barista”. Same Bluetooth experience for the user, but far less chaos and far less wasted energy behind the counter.</p>
<p>In this article, we will zoom in from this high level story into the actual Android APIs. We’ll look at how BluetoothSocketSettings is defined in the framework, how you request hardware offload, and what those scary looking fields like hubId and endpointId actually mean in plain English.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><p><a class="post-section-overview" href="#heading-the-anatomy-of-bluetoothsocketsettings">The Anatomy of BluetoothSocketSettings</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-inside-the-hal-how-bluetooth-offload-really-works">Inside the HAL: How Bluetooth Offload Really Works</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-when-the-cpu-sleeps-but-bluetooth-doesnt-power-management-in-action">When the CPU Sleeps but Bluetooth Doesn’t: Power Management in Action</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-developers-can-harness-bluetoothsocketsettings">How Developers Can Harness BluetoothSocketSettings</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-grand-finale-the-elegance-of-sleeping-smart">The Grand Finale: The Elegance of Sleeping Smart</a></p>
</li>
</ol>
<h2 id="heading-the-anatomy-of-bluetoothsocketsettings">The Anatomy of BluetoothSocketSettings</h2>
<p>So far we’ve been talking about BluetoothSocketSettings like it’s some magical ticket that sends your packets to a sunny low-power island somewhere inside your phone. Now let’s actually look at what that ticket looks like in code.</p>
<p>If you open the Android Open Source Project tree and navigate to the framework layer, you will find a class definition hiding under <code>frameworks/base/core/java/android/bluetooth/BluetoothSocketSettings.java</code>. At first glance it looks small, almost too simple for something that saves you so much battery. But this little class carries the secret instructions that tell the Bluetooth stack where your socket’s data should flow.</p>
<p>Here’s what a stripped-down version looks like:</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">final</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BluetoothSocketSettings</span> <span class="hljs-title">implements</span> <span class="hljs-title">Parcelable</span> {</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">int</span> DATA_PATH_NO_OFFLOAD = <span class="hljs-number">0</span>;
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">int</span> DATA_PATH_HARDWARE_OFFLOAD = <span class="hljs-number">1</span>;

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> mDataPath;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> mHubId;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> mEndpointId;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> mRequestedMaxPacketSize;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">BluetoothSocketSettings</span><span class="hljs-params">(<span class="hljs-keyword">int</span> dataPath, <span class="hljs-keyword">int</span> hubId, <span class="hljs-keyword">int</span> endpointId,
                                   <span class="hljs-keyword">int</span> requestedMaxPacketSize)</span> </span>{
        mDataPath = dataPath;
        mHubId = hubId;
        mEndpointId = endpointId;
        mRequestedMaxPacketSize = requestedMaxPacketSize;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> <span class="hljs-title">getDataPath</span><span class="hljs-params">()</span> </span>{ <span class="hljs-keyword">return</span> mDataPath; }
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> <span class="hljs-title">getHubId</span><span class="hljs-params">()</span> </span>{ <span class="hljs-keyword">return</span> mHubId; }
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> <span class="hljs-title">getEndpointId</span><span class="hljs-params">()</span> </span>{ <span class="hljs-keyword">return</span> mEndpointId; }
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> <span class="hljs-title">getRequestedMaxPacketSize</span><span class="hljs-params">()</span> </span>{ <span class="hljs-keyword">return</span> mRequestedMaxPacketSize; }
}
</code></pre>
<p>When a new socket is created in Android Bluetooth, the system or privileged service can pass one of these settings objects down to the stack. The key line is <code>DATA_PATH_HARDWARE_OFFLOAD</code>. That’s the switch that tells the Bluetooth system, <em>hey, try to keep this traffic on the controller’s microprocessor rather than waking up the main CPU.</em></p>
<p><code>hubId</code> and <code>endpointId</code> are like addresses on the island. They tell the firmware which logical port or queue to use for that particular socket. The <code>requestedMaxPacketSize</code> helps it tune buffer allocation, so it can balance throughput and power efficiency.</p>
<p>At this point you might be wondering, how does this tiny Java object actually make its way down to the hardware? The answer lies in the HAL (Hardware Abstraction Layer). When you call something like <code>BluetoothSocket.connect()</code>, it eventually funnels down through native code in files such as <code>btif_sock.cc</code> and <code>btif_core.cc</code>. There, you will see traces like:</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">bt_status_t</span> status = BTA_SockConnect(type, addr, channel, flags);
<span class="hljs-keyword">if</span> (settings.data_path == DATA_PATH_HARDWARE_OFFLOAD) {
    BTIF_TRACE_DEBUG(<span class="hljs-string">"Configuring socket for hardware offload path"</span>);
    BTA_SockSetOffloadParams(settings.hub_id, settings.endpoint_id);
}
</code></pre>
<p>This snippet may look simple, but it represents a major shift in responsibility. Instead of sending every packet up to the host stack, the Bluetooth controller can now claim ownership of the data path. The Bluetooth firmware inside the SoC will then take over, handling packet retransmissions, acknowledgments, and flow control without constantly waking the main CPU.</p>
<p>If you monitor your device’s kernel log during such a connection, you might even spot something like:</p>
<pre><code class="lang-cpp">bt_vendor: enabling LPI offload <span class="hljs-keyword">for</span> handle <span class="hljs-number">0x0041</span>
bt_controller: lpi path active, cpu wakelocks released
</code></pre>
<p>That log line is your quiet confirmation that the data path has successfully migrated to the low power island.</p>
<p>In human terms, the phone just decided that this Bluetooth conversation is predictable enough to be handled by the mini-processor, so it politely told the big CPU, “You can take a nap now. I got this.”</p>
<p>In the next section we will follow this journey one level deeper, right into the HAL and firmware boundary, to see how these socket settings turn into actual low-power data routing inside the controller chip. This is where the real hardware magic happens, and where the savings start adding up every milliwatt at a time.</p>
<h2 id="heading-inside-the-hal-how-bluetooth-offload-really-works">Inside the HAL: How Bluetooth Offload Really Works</h2>
<p>So far, we’ve stayed mostly in Android’s Java and native layers, the comfy apartment where frameworks and system services live. But beneath that lies a basement full of clever machinery: the <strong>Hardware Abstraction Layer</strong>, or HAL. This is where Android stops talking in “objects” and starts speaking in opcodes and buffers, and it’s the bridge between software and silicon.</p>
<p>When the BluetoothSocketSettings flag tells the system “please use hardware offload”, that request doesn’t magically teleport to the chip. It walks step by step down the Bluetooth stack, crossing through JNI (Java Native Interface) into C++, then into HAL, which is defined inside <code>hardware/interfaces/bluetooth/</code>.</p>
<p>Starting from Android 14 and especially in AOSP 16, the HAL has grown smarter: it now understands LPI capabilities and can route certain socket traffic to them.</p>
<p>Let’s take a peek inside a simplified HAL function. This is not a fictional snippet. It’s close to what you might find in <code>bluetooth_audio_hw.cc</code> or <code>bluetooth_socket_hal.cc</code>:</p>
<pre><code class="lang-cpp"><span class="hljs-function">Return&lt;<span class="hljs-keyword">void</span>&gt; <span class="hljs-title">BluetoothHci::createSocketChannel</span><span class="hljs-params">(
        <span class="hljs-keyword">const</span> hidl_string&amp; device, <span class="hljs-keyword">const</span> BluetoothSocketSettings&amp; settings,
        createSocketChannel_cb _hidl_cb)</span> </span>{
    <span class="hljs-keyword">int</span> fd = <span class="hljs-number">-1</span>;
    <span class="hljs-keyword">if</span> (settings.data_path == DATA_PATH_HARDWARE_OFFLOAD) {
        ALOGI(<span class="hljs-string">"LPI offload requested for socket on hub %d endpoint %d"</span>,
              settings.hub_id, settings.endpoint_id);
        fd = controller-&gt;allocateLpiChannel(settings.hub_id, settings.endpoint_id);
    } <span class="hljs-keyword">else</span> {
        fd = controller-&gt;allocateHostChannel();
    }
    _hidl_cb(Status::SUCCESS, fd);
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">void</span>();
}
</code></pre>
<p>In plain English, this method is like the traffic officer at the Bluetooth crossroads. It looks at your socket settings and decides which road to send your data on. If <code>DATA_PATH_HARDWARE_OFFLOAD</code> is set, the data path is wired to the controller’s internal MCU instead of the regular host-side buffer.</p>
<p>The call to <code>controller-&gt;allocateLpiChannel()</code> is where the HAL says, “Okay chip, please create a queue that lives entirely inside your low-power processor.” This microcontroller is physically closer to the Bluetooth radio. It can handle acknowledgments, small data bursts, and even some protocol timing on its own, things that would normally require waking the main CPU.</p>
<p>Once this channel is created, the Android framework and apps still see a normal file descriptor, as if the socket were entirely local. The magic lies in the fact that this descriptor is backed by firmware-managed memory and DMA paths rather than by Linux kernel buffers.</p>
<p>If you were to attach a debugger or dump logs from the controller, you might see something like:</p>
<pre><code class="lang-cpp">bt_lpi_mcu: channel <span class="hljs-number">0x03</span> opened <span class="hljs-keyword">for</span> handle <span class="hljs-number">0x0041</span>
bt_hci: diverting ACL packets to LPI path
bt_lpi_mcu: sleeping host processor
</code></pre>
<p>That third line, <code>sleeping host processor</code>, is the dream come true for every power engineer. The phone literally turns off big chunks of the CPU subsystem while keeping Bluetooth alive.</p>
<p>This is also where vendors like Qualcomm or Broadcom add their special sauce. Their HALs often include extra hooks for “keep-alive” timers, “coalescing intervals,” and “firmware-driven retransmissions.” These ensure the connection feels smooth even though the main processor is off-duty.</p>
<p>From a high-level view, the pipeline now looks like this:</p>
<pre><code class="lang-cpp">App -&gt; Bluetooth Framework -&gt; JNI -&gt; btif_sock -&gt; HAL -&gt; <span class="hljs-function">Controller <span class="hljs-title">MCU</span> <span class="hljs-params">(LPI)</span></span>
</code></pre>
<p>Every layer understands just enough to pass the baton cleanly to the next. The HAL acts as the translator, taking high-level settings and turning them into low-level commands that the chip firmware can execute.</p>
<p>By the time your smartwatch sends a packet or your earbuds request an audio chunk, the main CPU doesn’t even blink. The entire transaction lives and dies within the Bluetooth controller’s tiny domain, sipping power rather than gulping it.</p>
<p>In the next section, we’ll explore how this offload architecture integrates with Android’s power management system, including wakelocks, doze modes, and kernel coordination, and how it ensures that even though the main CPU is asleep, the connection never misses a beat.</p>
<h2 id="heading-when-the-cpu-sleeps-but-bluetooth-doesnt-power-management-in-action">When the CPU Sleeps but Bluetooth Doesn’t: Power Management in Action</h2>
<p>Alright, we have seen how the socket offload travels from the app layer down into the HAL and finally lands on that tiny MCU that lives inside the Bluetooth chip. But what happens next? What if your phone’s main CPU decides to take a nap while a file transfer or an audio stream is still going on? Doesn’t that risk breaking the Bluetooth connection?</p>
<p>This is where Android’s <strong>power management choreography</strong> steps in. It is a dance between three performers: the <strong>Power HAL</strong>, the <strong>Bluetooth stack</strong>, and the <strong>kernel wakelock system</strong>.</p>
<p>When a Bluetooth socket gets configured for Low Power Island, Android’s Bluetooth stack signals the kernel that this connection can be maintained without the help of the main CPU. Internally, it clears or downscales the wakelock timers that would normally keep the processor awake during Bluetooth traffic. In kernel logs, you might see something like this:</p>
<pre><code class="lang-cpp">wakelock: release <span class="hljs-string">"bt_wake"</span> (LPI mode active)
bt_controller: firmware handling link supervision locally
</code></pre>
<p>This message is gold for system engineers. It tells you the controller has taken full ownership of the connection. The Bluetooth firmware is now monitoring supervision timeouts, handling retransmissions, and maintaining encryption counters.</p>
<p>From the power manager’s point of view, the Bluetooth device looks “idle” because no interrupts are being generated toward the main CPU. Meanwhile, the controller MCU quietly exchanges packets with your earbuds or smartwatch using its own low-power clock domain.</p>
<p>To coordinate this, the Bluetooth HAL exposes small callbacks that inform the Power HAL whenever traffic levels change. You might find a snippet like this in <code>bt_vendor_qcom.cc</code>:</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">bt_lpi_activity_update</span><span class="hljs-params">(<span class="hljs-keyword">bool</span> active)</span> </span>{
    <span class="hljs-keyword">if</span> (active)
        power_hint(POWER_HINT_LPI_ACTIVITY, <span class="hljs-number">1</span>);
    <span class="hljs-keyword">else</span>
        power_hint(POWER_HINT_LPI_ACTIVITY, <span class="hljs-number">0</span>);
}
</code></pre>
<p>When <code>active</code> goes to zero, the Power HAL knows it can allow deeper system sleep states (like suspend-to-RAM), because Bluetooth will keep things alive on its own.</p>
<p>The real magic is that the user never notices any of this. The phone can appear “asleep”, display off, CPU cores gated, yet your Bluetooth audio still plays, your smartwatch still syncs, and your phone remains discoverable.</p>
<p>It’s almost poetic. The main processor is dreaming, the controller hums softly, and your playlist keeps rolling like nothing happened.</p>
<p>If you want to verify this on a real Android device, you can use the command:</p>
<pre><code class="lang-cpp">adb shell cat /sys/kernel/debug/wakeup_sources | grep bt
</code></pre>
<p>When you see that <code>bt_wake</code> counter stays low even during streaming, congratulations! The Low Power Island offload is doing its job beautifully.</p>
<p>In the next section, we’ll climb back up from the firmware depths to see how all this fits into the everyday developer’s world. Can you, as an app or system developer, actually control or benefit from these socket settings directly? And how can understanding them help you build Bluetooth apps that sip rather than chug power?</p>
<h2 id="heading-how-developers-can-harness-bluetoothsocketsettings">How Developers Can Harness BluetoothSocketSettings</h2>
<p>Now that we’ve peered deep into the heart of the Bluetooth stack, let’s climb back up to where you and I actually live: the developer layer. You might be wondering, “Okay, all that hardware wizardry is cool, but what can I actually <em>do</em> with it?”</p>
<p>Here’s the fun part: even though Low Power Island is mostly a system-level feature, understanding how it works can still help you design Bluetooth apps that are more power-friendly and predictable.</p>
<p>At the framework level, you can’t directly toggle LPI on or off from your app. Those switches live deep in system components like BluetoothService and BluetoothSocketManagerService. But every time you use a <code>BluetoothSocket</code> or <code>BluetoothServerSocket</code>, your data silently flows through those layers that check whether LPI offload is available.</p>
<p>That means your app benefits automatically, <em>as long as you don’t do anything that forces the CPU to stay awake unnecessarily</em>. For example, using proper thread sleeps, avoiding busy loops, and letting Android’s own Bluetooth I/O streams handle buffering will keep you in the good graces of the offload logic.</p>
<p>If you dive into AOSP’s system server logs while connecting a Bluetooth socket, you might notice something like this:</p>
<pre><code class="lang-cpp">BluetoothSocketManager: Offload eligible socket detected, enabling LPI mode
Bluetooth HAL: LPI channel activated <span class="hljs-keyword">for</span> fd=<span class="hljs-number">42</span>
</code></pre>
<p>That little line tells you that your socket has been quietly rerouted through the island, without you lifting a finger.</p>
<p>Underneath, the framework created a <code>BluetoothSocketSettings</code> object and passed it down the chain when the socket was opened. In pseudo-Java, it looks like this:</p>
<pre><code class="lang-cpp">BluetoothSocketSettings settings =
    <span class="hljs-keyword">new</span> BluetoothSocketSettings(
        BluetoothSocketSettings.DATA_PATH_HARDWARE_OFFLOAD,
        <span class="hljs-comment">/* hubId */</span> <span class="hljs-number">1</span>,
        <span class="hljs-comment">/* endpointId */</span> <span class="hljs-number">2</span>,
        <span class="hljs-comment">/* maxPacketSize */</span> <span class="hljs-number">512</span>);

BluetoothSocket socket = adapter.createSocket(device, settings);
socket.connect();
</code></pre>
<p>Of course, this isn’t part of the public SDK yet, but system apps or privileged frameworks use similar calls to describe how traffic should be handled.</p>
<p>So why should you, the developer, care? Because knowing that such a path exists means you can <em>design with it in mind</em>. For instance, you can:</p>
<ul>
<li><p>Batch small BLE writes instead of sending them one by one, allowing the controller to process them efficiently inside the offload buffer.</p>
</li>
<li><p>Avoid frequent connect/disconnect cycles, which would force the stack to wake the main CPU repeatedly.</p>
</li>
<li><p>Structure your background transfers to fit neatly within the limits of low-power buffers (think smaller chunks and longer intervals).</p>
</li>
</ul>
<p>Essentially, the more predictable your data pattern is, the more likely it is to stay in the island without waking the host.</p>
<p>If you’re building system software, say for a custom Android device or embedded product, then you can go even further. You can tweak the HAL behavior, assign custom hub or endpoint IDs, and even tune the maximum packet size that the firmware uses for DMA transfers. This allows you to build Bluetooth features: such as low-energy telemetry streaming or wearable sensor sync, that run almost entirely offloaded.</p>
<p>At that point, your Bluetooth chip becomes a mini server that keeps working while the main OS sleeps, delivering remarkable battery life and snappy reconnections.</p>
<p>In the final section, we’ll wrap things up and look back at the big picture, why BluetoothSocketSettings and Low Power Island together represent one of the most elegant examples of Android’s “invisible engineering.” It’s one of those quiet triumphs you’ll rarely see in a keynote but feel every day when your phone still has juice at midnight.</p>
<h2 id="heading-the-grand-finale-the-elegance-of-sleeping-smart">The Grand Finale: The Elegance of Sleeping Smart</h2>
<p>Let’s take a step back for a moment. We started in a coffee shop with an overworked barista. Then we discovered a hidden assistant, the Low Power Island, that quietly keeps the café running even when the main barista steps away.</p>
<p>We followed the path of a humble Bluetooth socket, watched it get wrapped in <code>BluetoothSocketSettings</code>, journeyed through the HAL, and finally land on a miniature processor inside the controller that hums along while the big CPU dreams.</p>
<p>And that’s the beauty of it: Android’s Bluetooth offload mechanism is one of the most elegant examples of invisible engineering. It doesn’t announce itself with a new API or a fancy animation. It just silently makes your battery last longer, your Bluetooth more reliable, and your phone feels smoother, all without you even knowing it’s there.</p>
<p>From a technical point of view, the brilliance lies in the balance. The system still allows full-featured sockets and rich protocol handling when you need it, but for common data flows, audio, telemetry, notifications, or heart rate streaming, it lets the low-power controller take the wheel. It’s like Android learned to delegate.</p>
<p>Every time your smartwatch syncs while your phone screen is off, or your earbuds stay connected during a long flight without draining your battery, you are seeing <code>BluetoothSocketSettings</code> and the Low Power Island framework at work. They are part of a larger philosophy in modern Android design, moving intelligence closer to hardware. The more we teach our chips to handle autonomic tasks, the more we can let the main processor rest.</p>
<p>If you are a developer or system engineer, understanding this architecture isn’t just academic. It can inspire how you design your own features. Whether you’re building a custom Android ROM, optimizing firmware for wearables, or creating IoT devices with a Bluetooth chip, the lesson is clear: don’t make your main CPU babysit every packet. Offload when you can, sleep when you should, and your devices will thank you with hours of extra uptime.</p>
<p>So the next time you plug in your earbuds and notice your phone staying cool and your battery percentage barely moving, remember: somewhere deep inside, a tiny Bluetooth MCU is doing all the heavy lifting while the main CPU enjoys a nap in its low-power hammock.</p>
<p>That’s the quiet genius of Android’s Low Power Island and BluetoothSocketSettings. It’s not just about Bluetooth. It’s about teaching our devices to be smarter, not busier. And maybe, just maybe, that’s a lesson worth remembering for ourselves too.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ The Secret Life of Your CPU: Exploring the Low Power Island in Android Bluetooth ]]>
                </title>
                <description>
                    <![CDATA[ If your phone were a person, it would probably be that overachieving friend who cannot sit still. The kind who insists they are relaxing while secretly running errands, replying to messages, and checking the weather at the same time. Inside your Andr... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/the-secret-life-of-your-cpu-exploring-the-low-power-island-in-android-bluetooth/</link>
                <guid isPermaLink="false">69164a5b08d80a5fa5d56f1e</guid>
                
                    <category>
                        <![CDATA[ Android ]]>
                    </category>
                
                    <category>
                        <![CDATA[ bluetooth ]]>
                    </category>
                
                    <category>
                        <![CDATA[ LowPowerConsumption ]]>
                    </category>
                
                    <category>
                        <![CDATA[ aosp ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Chip ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Nikheel Vishwas Savant ]]>
                </dc:creator>
                <pubDate>Thu, 13 Nov 2025 21:15:07 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1763065956169/7d83bf98-a7a8-42cd-b27b-f6c202612959.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>If your phone were a person, it would probably be that overachieving friend who cannot sit still. The kind who insists they are relaxing while secretly running errands, replying to messages, and checking the weather at the same time.</p>
<p>Inside your Android device, something very similar is happening every moment. One second the processor is streaming your playlist over Bluetooth, the next it’s processing notifications, tracking your location, or syncing data in the background. Somehow it manages all this without melting through your jeans or begging for a charger before lunch.</p>
<p>The secret behind this superhuman stamina lies in a small sanctuary inside the silicon known as the Low Power Island, often abbreviated as LPI. Think of it as a meditation corner for your processor. When there is nothing urgent to do, parts of the chip quietly retreat into this space to rest, while a few essential components stay awake to keep an eye on the world.</p>
<p>Imagine your CPU as a busy coffee shop. The main baristas are the high-performance cores, darting around to prepare fancy espresso drinks for demanding apps like games or video editors. The smaller efficiency cores handle lighter orders such as notifications or background tasks. Now picture a lonely drip coffee machine humming in the corner after closing hours. It keeps the essentials running without using much energy. That humble machine is your Low Power Island.</p>
<p>When Android realizes that no one is touching the screen, no heavy computation is in progress, and no critical wake locks are active, it lets the device drift into this gentle half-sleep. The system is not entirely unconscious because someone still needs to listen for alarms, network activity, or Bluetooth packets. It’s more like a cat napping with one ear twitching for sound.</p>
<p>This design allows modern devices to conserve power while staying responsive. In older systems, going to sleep meant shutting everything down and then painfully waking up for a single event. That would be like turning off the coffee shop’s electricity every time there were no customers, then waiting for the machines to warm up when the next order arrived. The Low Power Island avoids that waste by keeping only the essentials alive.</p>
<p>So the next time your phone lights up instantly after hours of lying still, remember that deep inside your processor, a few quiet transistors were guarding the gates. They were not fully awake or fully asleep but floating peacefully in the middle. That is the Low Power Island, the hidden hero of Android’s battery endurance.</p>
<p>In this article, we’re going to lift the curtain on that hero. You’ll see how the LPI works, not just as a sleepy nook for the CPU but as a full-fledged power-management strategy woven into Android’s architecture. We’ll also explore how Bluetooth keeps chatting quietly inside the island without waking the big cores, how the Power HAL and kernel orchestrate every nap and wake cycle, and how firmware plays the role of a tireless night guard.</p>
<p>You’ll get real AOSP snippets, real kernel logs, and practical advice on writing Bluetooth code that cooperates with the island instead of barging in loudly.</p>
<p>By the end, you’ll understand why your phone lasts as long as it does, and how this hidden corner of silicon keeps everything running with calm precision.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-is-the-low-power-island-lpi-in-android-bluetooth">What is the Low Power Island (LPI) in Android Bluetooth?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-silent-orchestra-how-lpi-works-with-power-hal--kernel">The Silent Orchestra: How LPI Works with Power HAL &amp; Kernel</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-debugging-and-verifying-low-power-island-in-bluetooth">Debugging and Verifying Low Power Island in Bluetooth</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-teaching-bluetooth-to-nap-smarter">Teaching Bluetooth to Nap Smarter</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion-the-quiet-genius-inside-your-phone">Conclusion: The Quiet Genius Inside Your Phone</a></p>
</li>
</ul>
<h2 id="heading-what-is-the-low-power-island-lpi-in-android-bluetooth">What is the Low Power Island (LPI) in Android Bluetooth?</h2>
<p>Bluetooth is a social butterfly. Even when the screen is dark, it keeps whispering to your earbuds, smartwatch, or car stereo, exchanging packets of data that make life feel seamless. The problem is that constant conversation consumes energy. Waking the entire phone every few seconds just to send a few bytes would be like turning on stadium floodlights to find your keys.</p>
<p>This is where the Low Power Island becomes the hero again. Inside modern Android phones, Bluetooth communication is handled by a dedicated <strong>Bluetooth controller</strong>, a small microprocessor within the same system-on-chip as the main CPU. This controller has its own memory and its own power domain. It can stay partially awake while the big CPU cores rest, maintaining connections and handling radio traffic with almost no help from the main processor.</p>
<p>When Android’s <strong>Power Manager</strong> decides the system can sleep, it sends signals through the <strong>Bluetooth HAL</strong> and vendor driver to let the controller know that the host side is entering a low-power state. The controller then takes over lightweight tasks on its own, such as keeping connections alive, scheduling sniff intervals, and handling encryption handshakes. The result is a seamless experience where your earbuds remain paired and responsive while the rest of your phone quietly saves power.</p>
<p>A simplified peek inside AOSP’s Bluetooth service shows this collaboration in action:</p>
<pre><code class="lang-cpp"><span class="hljs-comment">// From system/bt/service/btif/src/btif_core.cc</span>

<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">btif_pm_enter_low_power_mode</span><span class="hljs-params">()</span> </span>{
    LOG_INFO(<span class="hljs-string">"%s: entering low power mode"</span>, __func__);
    <span class="hljs-comment">// Notify controller to enter sleep mode</span>
    BTA_dm_pm_btm_status_evt(BTA_DM_PM_BTM_STATUS_IDLE);
    <span class="hljs-comment">// Suspend host stack threads</span>
    btif_thread_suspend();
}

<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">btif_pm_exit_low_power_mode</span><span class="hljs-params">()</span> </span>{
    LOG_INFO(<span class="hljs-string">"%s: exiting low power mode"</span>, __func__);
    <span class="hljs-comment">// Resume host stack threads</span>
    btif_thread_resume();
    <span class="hljs-comment">// Notify controller that the host is active again</span>
    BTA_dm_pm_btm_status_evt(BTA_DM_PM_BTM_STATUS_ACTIVE);
}
</code></pre>
<p>These functions represent a small slice of a much larger conversation between Android and the controller. The host stack quietly pauses while the controller keeps watch. On many chip vendor platforms, this state is called <strong>Controller Sleep</strong> or <strong>Snooze Mode</strong>. The Bluetooth controller can wake the host only when something meaningful occurs, such as an incoming call or a button press from your headset.</p>
<p>It works like a night security guard who patrols a building after everyone has gone home. The lights stay off, the air is still, but someone is always alert. If something happens, the guard rings the bell, and the rest of the crew wakes up. That is how your phone’s Bluetooth keeps working even when the display is dark and the CPU cores are resting inside the Low Power Island.</p>
<p>This collaboration between hardware, firmware, and Android’s power management makes it possible for you to listen to music, receive smartwatch notifications, or resume playback instantly without draining the battery. It’s quiet efficiency at its finest, a balance between awareness and rest that defines the beauty of modern Android design.</p>
<h2 id="heading-the-silent-orchestra-how-low-power-island-works-with-android-power-hal-and-the-kernel">The Silent Orchestra: How Low Power Island Works with Android Power HAL and the Kernel</h2>
<p>If you could peek under Android’s hood while your phone is asleep, you would see something that looks a lot like a perfectly timed orchestra. Every instrument knows when to play softly, when to rest, and when to come back in without missing a beat.</p>
<p>The Low Power Island is not a solo performer in this show. It is more like the gentle rhythm section, coordinated by a set of invisible conductors that live inside the <strong>Power HAL</strong>, the <strong>kernel</strong>, and the <strong>firmware</strong>.</p>
<p>Let’s start with the <strong>Power HAL</strong>, or Hardware Abstraction Layer. In Android, the Power HAL acts as the middleman between the system framework and the low-level kernel drivers. Whenever Android decides it can lower power consumption, it communicates this decision through HAL interfaces. The Power HAL talks to the chipset vendor’s implementation to decide which parts of the hardware can safely go to sleep. It controls not only the CPU clusters but also the GPU, display pipeline, and peripheral controllers like Bluetooth and Wi-Fi.</p>
<p>In a simplified sense, Android’s power manager says something like, “Hey HAL, we are idle now, can we nap for a bit?” The Power HAL then checks with the kernel and hardware to see who can afford to sleep. If the Bluetooth controller confirms that it can handle ongoing communication alone, the Power HAL signals the kernel to start shutting down parts of the main processor.</p>
<p>The <strong>kernel</strong>, in turn, manages this transition through its <strong>power domains</strong> and <strong>clock gating</strong> systems. Each hardware block in the chip belongs to a specific power domain. The kernel knows which domains can be turned off entirely and which must stay partially active.</p>
<p>The Bluetooth controller usually belongs to a domain that supports <strong>retention mode</strong>, meaning that some of its memory and logic stay powered just enough to preserve state.</p>
<p>A typical flow looks something like this inside the kernel logs when the device starts entering LPI mode:</p>
<pre><code class="lang-bash">PM: <span class="hljs-built_in">suspend</span> entry (deep)
controller-bluetooth 0001:00:00.0: entering controller sleep
PM: <span class="hljs-built_in">suspend</span> devices complete
PM: <span class="hljs-built_in">suspend</span> <span class="hljs-built_in">exit</span>
controller-bluetooth 0001:10:00.0: waking host
</code></pre>
<p>In this short exchange, you can see how Android’s power manager orchestrates the entire sleep-wake process. The Bluetooth driver reports that it’s entering controller sleep, the kernel confirms that all devices have suspended, and then later wakes everything up when an interrupt occurs.</p>
<p>At the hardware level, this behavior depends on <strong>voltage islands</strong> and <strong>clock domains</strong> defined by the SoC manufacturer. The term “island” is not metaphorical here – it literally represents an electrically isolated region on the chip that can be powered independently. When the kernel puts the main CPU to sleep, power to that island is lowered or shut off, while another island containing the Bluetooth controller continues to operate using a small independent oscillator.</p>
<p>Meanwhile, the <strong>firmware</strong> running on the Bluetooth controller performs light housekeeping. It manages scheduled events such as connection intervals, sniff subrate transitions, and link supervision timeouts. It can even decrypt or re-encrypt packets without disturbing the host processor. This allows Android to maintain a live Bluetooth connection while consuming a fraction of the power it would normally use.</p>
<p>When an event that requires higher-level attention occurs, such as a user pressing a button on their headset, the controller raises a <strong>host wake signal</strong> over the UART or shared memory transport. The kernel receives this interrupt, restores the CPU clock, and resumes Android’s power manager. The host stack reactivates, processes the event, and then gracefully hands control back once it’s idle again.</p>
<p>This dance between the Power HAL, kernel, and firmware might sound complicated, but it’s one of the most elegant designs inside Android. Each layer plays its role precisely. The Power HAL negotiates the policies, the kernel enforces them, and the firmware quietly executes them in the background. Together, they make sure that your phone feels instantly awake even after hours of rest.</p>
<p>The next time your earbuds reconnect without delay after your phone has been sleeping in your pocket, know that a whole chain of software and silicon cooperated flawlessly to make it happen. The Low Power Island was not just saving power – it was conducting a silent orchestra beneath your fingertips.</p>
<h2 id="heading-debugging-and-verifying-low-power-island-in-bluetooth">Debugging and Verifying Low Power Island in Bluetooth</h2>
<p>If you have ever watched a sleeping cat twitch its ears and wondered whether it’s dreaming, that’s pretty much what debugging the Low Power Island looks like on Android. The device may appear still, but deep within the logs, tiny ripples of life show up every few seconds. Engineers love this quiet chaos because it tells them the system is balancing perfectly between rest and readiness.</p>
<p>When Bluetooth enters its low power phase, Android leaves behind a breadcrumb trail of clues. You can see them in both <strong>logcat</strong> and <strong>kernel dmesg</strong> outputs. These logs help confirm whether the Bluetooth controller is indeed entering its low power state while the host CPU retreats to the island of calm.</p>
<p>A simple way to peek into this process is to run:</p>
<pre><code class="lang-bash">adb logcat -b all | grep -i <span class="hljs-string">"btif_pm"</span>
</code></pre>
<p>You might see something like this:</p>
<pre><code class="lang-bash">08-05 12:23:44.732  1712  1725 I bt_btif_pm: entering low power mode
08-05 12:23:44.733  1712  1725 I bt_btif_pm: controller idle, suspending host threads
08-05 12:23:46.008  1712  1725 I bt_btif_pm: exiting low power mode
</code></pre>
<p>Each line tells part of the story. The first message confirms that Android’s Bluetooth stack has requested entry into the low power state. The second shows that the host-side threads have paused, and the final message shows that the controller has woken the host again.</p>
<p>To see what is happening underneath, you can check kernel logs:</p>
<pre><code class="lang-bash">adb shell dmesg | grep -i bluetooth
</code></pre>
<p>You might find entries such as:</p>
<pre><code class="lang-bash">[ 1423.347102] controller-bluetooth 0001:00:00.0: entering controller sleep
[ 1423.347117] PM: <span class="hljs-built_in">suspend</span> entry (deep)
[ 1425.105993] controller-bluetooth 0001:00:00.0: host wake received
[ 1425.106005] PM: resume complete
</code></pre>
<p>These lines confirm that the Bluetooth driver and the power management system are cooperating correctly. The controller went to sleep, the kernel suspended the CPU clusters, and everything woke back up when a wake signal arrived from the Bluetooth controller.</p>
<p>If you ever see the host waking up too frequently, it usually means some component is not respecting sleep boundaries. Common culprits include misbehaving wake locks, noisy apps requesting continuous scanning, or timers that never expire. In such cases, Android’s <strong>PowerStats HAL</strong> and <strong>Batterystats</strong> framework can help track down who is preventing deep sleep.</p>
<p>You can check the overall low-power statistics using:</p>
<pre><code class="lang-bash">adb shell dumpsys batterystats | grep <span class="hljs-string">"bluetooth"</span>
</code></pre>
<p>This reveals how long the Bluetooth subsystem stayed active compared to how long the system was in low power mode. Ideally, the numbers should show that Bluetooth remains mostly idle except for brief wake periods.</p>
<p>Engineers working on system bring-ups often use specialized tracing tools such as <code>systrace</code>, <code>ftrace</code>, or <code>perfetto</code> to visualize power transitions. A power trace shows a rhythm: a long flat line representing sleep, interrupted by sharp spikes of activity when the controller wakes the host for a meaningful event. If those spikes are too frequent, you know the system is not entering Low Power Island efficiently.</p>
<p>Here is an excerpt from a typical Perfetto trace snippet:</p>
<pre><code class="lang-bash">bluetooth_host_state: IDLE → SUSPENDED
bluetooth_controller_state: ACTIVE → SLEEP
kernel_cpu_cluster_0: ACTIVE → RETENTION
kernel_cpu_cluster_1: ACTIVE → POWER_OFF
</code></pre>
<p>This simple sequence tells a powerful story. The host stack suspended, the controller slept, and the CPU clusters powered down gracefully. When the next event occurs, the transitions reverse, and the device wakes almost instantly.</p>
<p>Behind the scenes, vendor firmware plays a crucial role in making this magic look effortless. The Bluetooth controller firmware maintains timing slots, sniff intervals, and link-layer encryption keys, all while running on a few milliwatts of power. It’s astonishingly efficient. A typical controller can maintain an active ACL connection with power consumption under one milliwatt, even while the main CPU cores are completely powered down.</p>
<p>Debugging this system feels a bit like birdwatching. You have to stay patient, quiet, and observant. Most of the time, nothing dramatic happens in the logs. But when you finally catch a perfect sleep–wake cycle, it feels like witnessing nature in harmony. That is the beauty of Android’s Low Power Island at work with Bluetooth.</p>
<p>So when your earbuds reconnect in half a second or your smartwatch syncs data silently while your phone rests on the table, remember this quiet orchestra behind the scenes. It’s not brute power but smart power management that makes the experience feel smooth. The Low Power Island is the invisible craftsman that gives your Android Bluetooth its calm precision, saving battery one sleepy packet at a time.</p>
<h2 id="heading-teaching-bluetooth-to-nap-smarter">Teaching Bluetooth to Nap Smarter</h2>
<p>If the Low Power Island were a yoga retreat for your processor, then your job as a developer would be to make sure your Bluetooth code doesn’t show up with a drum set. It’s easy to accidentally keep the system awake when you don’t need to. A single careless wake lock, a recurring timer, or a never-ending scan request can prevent the hardware from entering that calm, power-efficient state.</p>
<p>The goal of optimizing for Low Power Island is not to make your Bluetooth logic work less. It’s to make it <strong>work wisely</strong>, to let the controller handle small background exchanges while the main CPU sleeps peacefully. Android’s Bluetooth stack and vendor drivers already handle most of the heavy lifting, but developers can make a big difference by writing energy-conscious code that respects those boundaries.</p>
<p>The first rule is simple: <strong>scan responsibly</strong>. Continuous scanning is the number-one villain in Bluetooth power profiles. Each scan wakes the radio, the controller, and often the host processor. If your app continuously calls <code>BluetoothLeScanner.startScan()</code> without a clear stop condition, you are effectively shining a flashlight into the Low Power Island every few seconds.</p>
<p>Instead, batch your scans and use filters. The system’s <code>ScanSettings.SCAN_MODE_LOW_POWER</code> mode is specifically designed to allow scanning that cooperates with LPI transitions.</p>
<p>Here’s an example from AOSP that shows how you can trigger a scan in a power-friendly way:</p>
<pre><code class="lang-java">ScanSettings settings = <span class="hljs-keyword">new</span> ScanSettings.Builder()
        .setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
        .setReportDelay(<span class="hljs-number">5000</span>) <span class="hljs-comment">// batch results every 5 seconds</span>
        .build();

bluetoothLeScanner.startScan(filters, settings, scanCallback);
</code></pre>
<p>By batching results and letting the hardware handle scanning internally, you reduce host wakeups dramatically. The Bluetooth controller can gather advertisements on its own, waking the CPU only once every few seconds to deliver results.</p>
<p>The second rule is to <strong>let the stack sleep</strong>. Many developers unknowingly block Bluetooth threads by holding wake locks or running unnecessary callbacks. The Android Bluetooth stack maintains internal synchronization through message loops that can safely pause during idle periods.</p>
<p>Avoid long-running operations in callbacks such as <code>BluetoothGattCallback.onCharacteristicChanged()</code>. Instead, offload work to background executors that respect Android’s Doze and App Standby policies.</p>
<p>Another optimization lies in <strong>using connection intervals and latency wisely</strong>. BLE connections allow you to configure how frequently devices exchange packets. A shorter interval improves responsiveness but burns energy. A longer interval gives more opportunities for the controller to rest between events. If your use case allows it, choose higher connection intervals and peripheral latency values when initializing connections.</p>
<pre><code class="lang-java"><span class="hljs-comment">// Example: Requesting a higher connection interval in GATT</span>
bluetoothGatt.requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_LOW_POWER);
</code></pre>
<p>Under the hood, this tells the Bluetooth controller to lengthen its sniff interval, letting both ends of the link spend more time in low power mode. The result is longer battery life with almost no visible impact on user experience for background updates or sensor reads.</p>
<p>At the system level, engineers tuning platform behavior can also adjust parameters in the Power HAL and kernel configuration. The <code>/sys/power</code> directory contains tunables for CPU retention and controller wake thresholds. Tools like perfetto, systrace, and btsnooz.py can visualize Bluetooth power events, helping verify that sleep cycles are happening as expected.</p>
<p>For example, a trace showing too many wakeups per second might look like this:</p>
<pre><code class="lang-bash">bluetooth_host_state: SUSPENDED → ACTIVE
reason: controller wake (LL control packet)
interval: 150 ms
</code></pre>
<p>If you see dozens of such wakeups in a short time, it might indicate an overly aggressive connection interval or constant GATT notifications from a peripheral. Adjusting those parameters can bring the wake interval down to seconds instead of milliseconds, drastically improving power efficiency.</p>
<p>The third and perhaps most important rule is <strong>know when to let go</strong>. When your app finishes a Bluetooth operation, always close the GATT connection, stop scanning, and release references. Many developers forget this step, leaving ghost connections or scans running silently in the background. Each one is like leaving a window open during winter: the heater works harder, and battery life suffers.</p>
<p>Finally, remember that not every Bluetooth event deserves a host wakeup. Modern controllers can handle encryption refreshes, supervision timeouts, and advertisement filtering entirely on their own. Trust the hardware. Android’s Low Power Island and Bluetooth stack are designed to delegate intelligently. The less your app interferes, the smoother the dance becomes.</p>
<p>Optimizing for Low Power Island is not about disabling features. It’s about building harmony between layers. The Android framework, kernel, and controller firmware already communicate like seasoned musicians in an orchestra. Your code is another instrument in that ensemble. Play lightly, leave room for silence, and let the rest of the system breathe.</p>
<p>When you do it right, your users will never notice a thing. Their earbuds will reconnect instantly, their fitness trackers will sync quietly, and their phones will last an extra few hours each day. Behind the scenes, that serene rhythm of sleep and wake continues, powered by the elegant balance that Low Power Island brings to Android Bluetooth.</p>
<h2 id="heading-conclusion-the-quiet-genius-inside-your-phone">Conclusion: The Quiet Genius Inside Your Phone</h2>
<p>If your phone were a musician, the Low Power Island would be its silent metronome, keeping time, holding rhythm, and making sure the melody never skips a beat. It does not demand attention or boast about its work. It simply exists in the background, saving power in ways most people never realize.</p>
<p>Throughout this journey, we have seen how the Low Power Island serves as the meeting point between hardware and software, where silence becomes strategy. We began with the idea that your CPU, much like a restless friend, needs a place to breathe. We then saw how Bluetooth, the most social of all radios, learns to whisper instead of shout when the rest of the system drifts to sleep. Together, they form one of the most delicate yet powerful mechanisms in Android’s design.</p>
<p>The Bluetooth controller becomes the night guard of the silicon city. While the big CPU cores sleep soundly behind closed gates, the controller patrols quietly, keeping connections alive, listening for signals, and ringing the bell only when something truly important happens. It’s a small but crucial act of cooperation that gives modern Android devices their elegance.</p>
<p>Behind the scenes, the Power HAL negotiates policies, the kernel enforces them, and the firmware executes them with surgical precision. They move like an orchestra, sometimes lively, sometimes silent, but always in harmony. And when your phone wakes instantly to play music, take a call, or reconnect your earbuds, that smoothness is not luck. It is the Low Power Island doing exactly what it was built for: making power management feel invisible.</p>
<p>For developers, understanding this system is not just an exercise in curiosity. It’s a reminder that true optimization does not always come from brute force or faster code. Sometimes it comes from restraint, from knowing when to let go, when to rest, and when to let the system do its quiet magic. Each small decision, batching scans, adjusting connection intervals, respecting sleep boundaries, contributes to a bigger story of balance.</p>
<p>The next time your phone makes it through an entire day of Bluetooth streaming, navigation, and notifications without flinching, take a moment to appreciate what’s happening beneath that glass screen. Inside, a city of transistors is asleep yet awake, calm yet alert, working together in perfect synchronization. The Low Power Island is not just an engineering trick. It is a philosophy: that even in the world of machines, peace and patience can be more powerful than constant motion.</p>
<p>And if you think about it, that is a lesson worth keeping, for both phones and humans alike.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Google Play’s 16 KB Page Size Compatibility Requirement — What You Should Know, and How to Upgrade Your App ]]>
                </title>
                <description>
                    <![CDATA[ Android is always evolving, and sometimes those changes happen a bit under the hood. One such change that's been gaining traction—and now has a firm deadline from Google—is the move to a 16 KB page size. If you're an Android developer, especially wit... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/google-16-kb-page-size-requirement-what-to-do/</link>
                <guid isPermaLink="false">68d703052043890036a92cb0</guid>
                
                    <category>
                        <![CDATA[ Android ]]>
                    </category>
                
                    <category>
                        <![CDATA[ android app development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ mobile app development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Arunachalam B ]]>
                </dc:creator>
                <pubDate>Fri, 26 Sep 2025 21:17:57 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1758921064544/80db1a03-73e1-48c3-b2a0-566f20244431.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Android is always evolving, and sometimes those changes happen a bit under the hood. One such change that's been gaining traction—and now has a firm deadline from Google—is the move to a 16 KB page size. If you're an Android developer, especially with native code in your app, understanding this shift is really important for keeping your apps smooth and compatible.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-is-a-page-size">What is a Page Size?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-why-is-this-change-being-implemented-now">Why is this Change Being Implemented Now?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-are-the-pros-and-cons-of-this-change">What are the Pros and Cons of this Change?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-should-you-worry-about-this-change">Should You worry About this Change?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-is-this-mandatory">Is this Mandatory?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-if-you-dont-upgrade-your-app">What if You Don’t Upgrade Your App?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-does-this-affect-hybrid-apps">How Does this Affect Hybrid Apps?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-would-be-the-code-change-for-this">What Would be the Code Change for This?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-verify-if-your-app-is-upgraded-to-a-16-kb-page-size">How to Verify if Your App is Upgraded to a 16 KB Page Size</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-what-is-a-page-size">What is a Page Size?</h2>
<p>Think of your device's memory like a book. An operating system doesn't read memory one tiny word at a time; it reads in chunks. These chunks are called "pages." For a long time, on most ARM64 Android devices, these pages were 4 KB in size. Now, for some newer Android devices (specifically those launching with Android 13 and later), that page size has quadrupled to 16 KB.</p>
<h2 id="heading-why-is-this-change-being-implemented-now">Why is this Change Being Implemented Now?</h2>
<p>It's all about making Android run better on modern hardware. Here are some of the reasons why is it being implemented:</p>
<p><strong>Better Performance:</strong> Modern processors can handle larger memory chunks more efficiently. A 16 KB page size means the CPU spends less time managing tiny bits of memory and more time doing actual work, which can lead to faster app performance.</p>
<p><strong>Smoother Operations:</strong> With fewer, larger pages to keep track of, the system itself has a little less overhead, making things a bit more streamlined.</p>
<p><strong>Keeping Up with Tech:</strong> This change helps Android align with how newer ARM64 processors are designed to work best.</p>
<h2 id="heading-what-are-the-pros-and-cons-of-this-change">What are the Pros and Cons of this Change?</h2>
<p>Every big change has its own pros and cons.</p>
<h3 id="heading-pros">Pros</h3>
<ul>
<li><p>Apps that move a lot of data around or are memory-intensive might just feel a bit snappier</p>
</li>
<li><p>The system could run a bit more efficiently, benefiting all apps indirectly</p>
</li>
</ul>
<h3 id="heading-cons">Cons</h3>
<ul>
<li><p>If your native code is constantly asking for very small bits of memory (less than 16 KB), each of those might now take up a full 16 KB page, potentially using a little more memory than before.</p>
</li>
<li><p>If your native code makes assumptions that "memory pages are always 4 KB," it could run into issues on 16 KB page devices.</p>
</li>
</ul>
<h2 id="heading-should-you-worry-about-this-change">Should You worry About this Change?</h2>
<p>You need to pay attention if:</p>
<ul>
<li><p>Your app includes native libraries (like <code>.so</code> files) written in C/C++. This is where the impact is most direct. If your native code does anything with memory mapping (mmap, shmem) or file I/O where it calculates offsets or sizes based on a fixed page size.</p>
</li>
<li><p>You're developing games or other highly performance-sensitive apps with native components.</p>
</li>
<li><p>You're targeting Android 15+ with your app updates.</p>
</li>
</ul>
<p>You need not worry if:</p>
<ul>
<li><p>Your app is built purely in Java or Kotlin with no native components. The Android Runtime (ART) handles memory for you, so these underlying page size changes are largely invisible. You'll still get the performance benefits!</p>
</li>
<li><p>You're using React Native or Flutter, unless you've added custom native modules that directly deal with memory mapping or page-size-dependent operations</p>
</li>
</ul>
<h2 id="heading-is-this-mandatory">Is this Mandatory?</h2>
<p>Yes. Google Play is making this a requirement for app updates. You would have received an email from Google Play if your app does not support 16 KB page size yet.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758343797173/da2aff04-d5ae-4964-9d72-02f21b4a0d96.png" alt="Your app is affected by Google Play's 16KB page size requirements" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>As the screenshot clearly shows, "From Nov 1, 2025, if your app updates do not support 16 KB memory page sizes, you won't be able to release these updates" for apps targeting Android 15+. This gives us a solid timeframe to get things ready.</p>
<h2 id="heading-what-if-you-dont-upgrade-your-app">What if You Don’t Upgrade Your App?</h2>
<p>You could notice some serious issues if your app has native libraries that aren't ready for the 16 KB page size by the deadline. Here are a few:</p>
<ul>
<li><p><strong>Crashes:</strong> This is the most serious. Your app might crash unexpectedly (often with a "segmentation fault") if it tries to access memory incorrectly due to old page size assumptions.</p>
</li>
<li><p><strong>Wasted Memory:</strong> If your code allocates memory in smaller chunks than 16 KB, it could end up using more memory than necessary, potentially slowing things down or hitting memory limits.</p>
</li>
<li><p><strong>Performance Hit:</strong> Instead of gaining speed, your app might actually run slower if its memory operations aren't aligned with the larger page size.</p>
</li>
</ul>
<p>Essentially, your app might work fine today, but become unstable or inefficient on newer Android devices if its native components aren't updated.</p>
<h2 id="heading-how-does-this-affect-hybrid-apps">How Does this Affect Hybrid Apps?</h2>
<p>Generally, if you're building a standard hybrid app (React Native or Flutter app) without custom native modules, you're in a pretty good spot. The frameworks themselves, and the underlying runtimes (JavaScript engine for React Native, Dart VM for Flutter), usually handle memory management, abstracting away the page size.</p>
<p>However, if you've implemented custom native modules in C++ for performance-critical tasks or specific hardware interactions, then you do need to check those modules.</p>
<p>For the vast majority of standard React Native and Flutter apps, you likely won't need direct code changes related to page size, but always ensure you're using the latest SDK versions for your framework to benefit from any underlying platform updates.</p>
<h2 id="heading-what-would-be-the-code-change-for-this">What Would be the Code Change for This?</h2>
<p>The biggest thing to avoid in your native code is making assumptions about memory page sizes. Instead of hardcoding 4096 (for 4 KB), always ask the operating system what its current page size is.</p>
<h3 id="heading-steps-to-take"><strong>Steps to Take:</strong></h3>
<ol>
<li><p><strong>Audit Your Native Code:</strong> Search your <code>.cpp</code>, <code>.c</code>, and <code>.h</code> files for any direct use of 4096 or 4 KB in memory allocation, buffer sizing, or alignment calculations</p>
</li>
<li><p><strong>Replace with</strong> <code>sysconf(_SC_PAGESIZE)</code> <strong>or</strong> <code>getpagesize()</code><strong>:</strong> Update any fixed values to dynamically retrieve the actual page size.</p>
</li>
<li><p><strong>Recompile with Latest NDK:</strong> Make sure you're building your native libraries with a recent Android NDK (r25 or newer is a good target). This ensures your toolchain is aware of the 16 KB page size and provides correct system definitions.</p>
</li>
</ol>
<h2 id="heading-how-to-verify-if-your-app-is-upgraded-to-a-16-kb-page-size">How to Verify if Your App is Upgraded to a 16 KB Page Size</h2>
<p>You can verify if your app is upgraded by running extensive testing. However, here are the few more steps.</p>
<ol>
<li><p><strong>Check Your Test Device's Page Size:</strong></p>
<ul>
<li><p>Connect your Android 13+ test device (preferably a newer one like a Pixel) via ADB</p>
</li>
<li><p>Run <code>adb shell getconf PAGE_SIZE</code></p>
</li>
<li><p>If it returns 16384, you're testing on a 16 KB page device! If it returns 4096, you'll need to find a different device to properly test for this change</p>
</li>
<li><p>Here’s an example screenshot from my device</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758368982307/f5696dab-f23a-4731-95b4-0372159d2107.png" alt="Find page size of an Android device/emulator" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
</li>
</ul>
</li>
<li><p><strong>Run Your App Extensively:</strong> Once you have a 16 KB page device, put your app through its paces. Try all features, especially those involving native code, heavy data loading, or complex operations.</p>
</li>
<li><p><strong>Monitor for Crashes:</strong> Keep a close eye on your crash reporting tools (like Crashlytics). Specifically look for native crashes (<code>SIGSEGV</code>, <code>SIGBUS</code>) coming from Android 13+ devices, as these could be related to page size issues.</p>
</li>
<li><p><strong>Memory Profiling:</strong> While less direct, if you suspect memory inefficiency in your native code, use Android Studio's Memory Profiler to see if allocations are unexpectedly large or if there's excessive memory usage.</p>
</li>
</ol>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this blog, we learnt about page size in Android, and why and how to upgrade your app to support 16 KB page size. I hope you have a clear idea about 16 KB page size in Android. By being proactive now, you can avoid last-minute scrambling and ensure your apps continue to perform beautifully on the latest Android devices, well past the November 2025 deadline!</p>
<p>You can follow my <a target="_blank" href="https://x.com/AI_Techie_Arun">Twitter/X account</a> to receive the top AI news everyday. If you wish to learn more about mobile app development, subscribe to my email newsletter (<a target="_blank" href="https://5minslearn.gogosoon.com/?ref=fcc_android_16kb_page_size">https://5minslearn.gogosoon.com/</a>) and follow me on social media.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Make Bluetooth on Android More Reliable ]]>
                </title>
                <description>
                    <![CDATA[ You may have had this happen before: your wireless earbuds connect perfectly one day, and the next they act like they’ve never met your phone. Or your smartwatch drops off in the middle of a run. Bluetooth is amazing when it works, but maddening when... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-make-bluetooth-on-android-more-reliable/</link>
                <guid isPermaLink="false">68b78f7fba46c4e7c6266797</guid>
                
                    <category>
                        <![CDATA[ Android ]]>
                    </category>
                
                    <category>
                        <![CDATA[ bluetooth ]]>
                    </category>
                
                    <category>
                        <![CDATA[ wireless network ]]>
                    </category>
                
                    <category>
                        <![CDATA[ iot ]]>
                    </category>
                
                    <category>
                        <![CDATA[ debugging ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Nikheel Vishwas Savant ]]>
                </dc:creator>
                <pubDate>Wed, 03 Sep 2025 07:00:00 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1756860272946/83be340a-dcce-4d2f-a6eb-0d70164b11b6.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>You may have had this happen before: your wireless earbuds connect perfectly one day, and the next they act like they’ve never met your phone. Or your smartwatch drops off in the middle of a run. Bluetooth is amazing when it works, but maddening when it doesn’t.</p>
<p>I work as a Bluetooth software engineer on wearable devices like smart-glasses, and I’ve spent more time than I’d like to admit chasing down why these things break.</p>
<p>In this article, I’ll give you a peek behind the curtain: how Android’s Bluetooth stack actually works, why it sometimes feels unpredictable, and what you can do as a developer to make your apps or system more reliable.</p>
<h2 id="heading-bluetooth-in-plain-english">Bluetooth in Plain English</h2>
<p>At its core, Bluetooth is just a conversation between two devices. But it isn’t one simple line of communication – it’s multiple layers stacked on top of each other.</p>
<ul>
<li><p><strong>The radio (Controller):</strong> Sends and receives the actual signals over the air medium.</p>
</li>
<li><p><strong>The software brain (Host stack):</strong> Decides whom to talk to and how, as well as if it wants to.</p>
</li>
<li><p><strong>Profiles:</strong> Define the purpose of the conversation – like streaming music or syncing health data.</p>
</li>
<li><p><strong>Protocols:</strong> Define how to talk to the other device.</p>
</li>
</ul>
<p>There are two big “flavors” of bluetooth:</p>
<ul>
<li><p><strong>Classic (BR/EDR):</strong> Used for things like headphones and car kits. Can lift more weight.</p>
</li>
<li><p><strong>Low Energy (LE):</strong> Used for fitness bands, beacons, and most wearables. Can sustain longer.</p>
</li>
</ul>
<p>Most modern gadgets use both at once. That’s powerful, but it also opens the door for more things to go wrong.</p>
<h2 id="heading-why-android-adds-its-own-quirks">Why Android Adds Its Own Quirks</h2>
<p><img src="https://source.android.com/static/docs/core/connect/bluetooth/images/fluoride_architecture.png" alt="Diagram showing the layers of the Android Bluetooth stack." width="600" height="400" loading="lazy"></p>
<p>On Android, Bluetooth isn’t just one neat package. It’s a chain of moving parts:</p>
<ul>
<li><p>Your app calls <code>BluetoothAdapter</code>.</p>
</li>
<li><p>Those go into <strong>system services</strong> like <code>AdapterService</code>.</p>
</li>
<li><p>Then into native code through <strong>JNI</strong> (Java Native Interface).</p>
</li>
<li><p>Then into the <strong>chip vendor’s Bluetooth stack</strong>.</p>
</li>
<li><p>Finally, it hits the <strong>radio hardware</strong>.</p>
</li>
</ul>
<p>Every phone maker ships a slightly different Bluetooth chip and firmware. That means the exact same Bluetooth app might behave differently on a Samsung, a Pixel, or any other budget phone running Android.</p>
<h2 id="heading-the-real-problems-behind-it-just-disconnected">The Real Problems Behind “It Just Disconnected”</h2>
<p>Here are a few of the common headaches I see, explained simply:</p>
<h3 id="heading-bonding-issues-the-lost-keys-problem"><strong>Bonding issues (the “lost keys” problem)</strong></h3>
<p>When two Bluetooth devices pair, they exchange encryption keys (link keys for Classic, Long Term Keys for LE) and store them in non-volatile memory. These keys are what let the devices recognize each other later and reconnect securely without asking the user again.</p>
<p>A “mismatched memory” problem happens when one device’s stored keys don’t match the other’s anymore. This can be caused by:</p>
<ul>
<li><p>A firmware update or OS upgrade that wipes or regenerates keys.</p>
</li>
<li><p>A factory reset or “forget device” on one side but not the other.</p>
</li>
<li><p>Keys being corrupted or evicted by the system to free up storage.</p>
</li>
</ul>
<p>From the user’s perspective, the device may still <em>look</em> paired (shows up in the Bluetooth menu), but connections mysteriously fail with errors like “Authentication Failed” or “Insufficient Encryption.” The only cure is usually to delete the device on both ends and re-pair, which feels ridiculous to non-technical users.</p>
<h3 id="heading-timing-mismatches"><strong>Timing mismatches</strong></h3>
<p>Bluetooth devices don’t just chat whenever they want, they agree on a connection interval – essentially a schedule for when each side will “wake up” and exchange packets. Think of it as two people agreeing to meet every 30 minutes at a café.</p>
<p>A mismatch happens when:</p>
<ul>
<li><p>The two sides negotiate different intervals but don’t fully agree (for example, one thinks it’s 30ms, the other 50ms).</p>
</li>
<li><p>One side’s firmware update or configuration change alters its timing policy.</p>
</li>
<li><p>Radio conditions cause one side to miss multiple scheduled check-ins, drifting the clocks apart.</p>
</li>
<li><p>Power-saving logic (like a phone going into Doze mode) silently stretches out the interval.</p>
</li>
</ul>
<p>This explains why a connection might work fine at first but start failing later: the devices initially synced on an interval, but then one side’s policy or behavior shifted. From the user’s perspective, it looks like audio stuttering, laggy input (on game controllers), or random disconnects after “it was working fine before.”</p>
<h3 id="heading-unexpected-disconnections"><strong>Unexpected disconnections</strong></h3>
<p>When a Bluetooth link ends, the radio layer (the controller) and the higher-level OS stack (the host) are supposed to exchange clear signals. The controller sends an HCI Disconnection Complete event (basically: <em>“Goodbye, we’re done”</em>). And the host should then update its internal state, clean up the GATT/ACL session, and be ready for reconnection.</p>
<p>But in practice, this doesn’t always line up:</p>
<ul>
<li><p>Sometimes the controller says goodbye cleanly, but the host stack doesn’t update its state properly. The app still “thinks” the connection is active, so reconnect attempts silently fail.</p>
</li>
<li><p>Some platforms aggressively cache connection state (especially iOS). If the OS believes the connection is still valid, it won’t trigger a new connection attempt until you toggle Bluetooth or reboot.</p>
</li>
<li><p>A race condition can occur if the disconnection event happens while another operation (for example, service discovery, bonding, or encryption setup) is in flight. The OS may get confused about what state the device is <em>really</em> in.</p>
</li>
<li><p>On some devices, a fast reconnect attempt after a clean disconnection collides with internal cooldown timers. The controller ignores it, leaving the app waiting.</p>
</li>
</ul>
<p>From the user’s perspective, the device looks “stuck.” The only way to recover is to toggle Bluetooth, restart the app, or power cycle the accessory, even though technically nothing “failed.”</p>
<h2 id="heading-how-developers-can-do-better">How Developers Can Do Better</h2>
<p>If you’re building a Bluetooth app, here are a few habits that save a lot of pain:</p>
<h3 id="heading-check-for-bonded-devices-first"><strong>Check for bonded devices first</strong></h3>
<p>One of the most common causes of failed connections is mismatched bonding information: the phone and the accessory no longer share the same encryption keys. Even if the device appears in the UI, the OS may have lost its keys.</p>
<p>Before attempting a connection, always query the system’s bonded device list with <code>BluetoothAdapter.getBondedDevices()</code>. For example:</p>
<pre><code class="lang-java"><span class="hljs-keyword">if</span> (adapter.getBondedDevices().contains(targetDevice)) {
    targetDevice.connectGatt(context, <span class="hljs-keyword">false</span>, gattCallback);
} <span class="hljs-keyword">else</span> {
    showToast(<span class="hljs-string">"Please re-pair this device to restore the connection."</span>);
}
</code></pre>
<p>This ensures you only attempt secure connects to devices the OS still trusts. If the target device isn’t in the bonded list, you can give the user a clear instruction (“Please re-pair this device”) instead of leaving them with confusing connection errors.</p>
<h3 id="heading-handle-callbacks-carefully"><strong>Handle callbacks carefully</strong></h3>
<p>Another subtle pitfall is assuming that a <code>STATE_CONNECTED</code> event means a connection was successful. In reality, <code>onConnectionStateChange()</code> can report a connected state even when the underlying operation failed, the real result is in the <code>status</code> argument. To avoid chasing phantom connections, always check both <code>status</code> and <code>newState</code>:</p>
<pre><code class="lang-java"><span class="hljs-keyword">if</span> (status == BluetoothGatt.GATT_SUCCESS &amp;&amp;
    newState == BluetoothProfile.STATE_CONNECTED) {
    gatt.discoverServices();
} <span class="hljs-keyword">else</span> {
    gatt.close();
}
</code></pre>
<p>This pattern prevents you from attempting service discovery on a dead connection and ensures stale sessions are closed promptly, leaving the stack ready for a clean retry.</p>
<h3 id="heading-expect-failures"><strong>Expect failures</strong></h3>
<p>Bluetooth connections fail all the time in the real world – devices drift out of range, interference spikes in the 2.4 GHz band, or the radio is simply busy. The worst thing an app can do is retry instantly in a tight loop, which drains the battery and makes the stack unstable.</p>
<p>A better approach is to implement exponential backoff like this:</p>
<pre><code class="lang-java"><span class="hljs-keyword">long</span> delay = (<span class="hljs-keyword">long</span>) Math.min(<span class="hljs-number">250</span> * Math.pow(<span class="hljs-number">2</span>, attempt), <span class="hljs-number">30000</span>);
<span class="hljs-keyword">new</span> Handler(Looper.getMainLooper()).postDelayed(connectAction, delay);
</code></pre>
<p>This means your first retry happens quickly (~250 ms), but subsequent retries slow down (500 ms, 1 s, 2 s…), capped at a reasonable maximum. Backoff makes your app resilient without overwhelming the radio or the OS.</p>
<h3 id="heading-use-the-right-tools"><strong>Use the right tools</strong></h3>
<p>Without visibility into what’s happening under the hood, connection problems look random. Tools like <em>nRF Connect</em> let you interactively scan, connect, and run GATT operations against your device, while Android’s Bluetooth HCI snoop log reveals the actual packets being exchanged. For example:</p>
<pre><code class="lang-bash">Settings.Secure.putInt(context.getContentResolver(), <span class="hljs-string">"bluetooth_hci_log"</span>, 1);
</code></pre>
<p>Once enabled, you can capture a logcat trace and confirm whether a failure is due to missing keys (<code>Insufficient Authentication</code>), a timing mismatch, or interference. Using these tools not only helps you debug your app, it also proves whether the issue lies in your code, the OS, or the accessory firmware.</p>
<p><img src="https://www.beaconzone.co.uk/blog/wp-content/uploads/2019/08/nrfconnectios.png" alt="Completely New nRF Connect for iOS – BeaconZone Blog" width="600" height="400" loading="lazy"></p>
<h2 id="heading-bigger-lessons">Bigger Lessons</h2>
<p>Working with Bluetooth taught me lessons that apply to engineering in general:</p>
<ul>
<li><p>Wireless is never perfect, so always build with recovery in mind.</p>
</li>
<li><p>Logs and metrics aren’t optional. They’re your map through the chaos.</p>
</li>
<li><p>The simplest solution usually survives best in the messy real world.</p>
</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Bluetooth is messy because it’s a chain of hardware, firmware, and software all trying to cooperate. On Android, the variety of chips and vendors makes it even trickier.</p>
<p>But that doesn’t mean you’re helpless. By understanding how the layers work and designing your apps with retries, checks, and proper logging, you can make Bluetooth feel a lot less “weird” for your users.</p>
<p>The next time your earbuds misbehave, you’ll know – it’s not you. It’s just Bluetooth being Bluetooth.</p>
<p>⚡ <em>This is the first of a number of articles I’m going to write on Bluetooth development. In the next one, we’ll dive deeper into how to build a secure Bluetooth Low Energy (BLE) GATT client and server on Android. Stay tuned!</em></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Convert Your Website into an Android App Using Bubblewrap ]]>
                </title>
                <description>
                    <![CDATA[ If you are a web developer who doesn’t know about App Development (like me!), then this article is for you. I’ll teach you how to turn your website into a native app, without new frameworks or languages. You’ll learn how to convert a website to a PWA... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-convert-your-website-into-an-android-app-using-bubblewrap/</link>
                <guid isPermaLink="false">68a4b9d4f2bced8c3a658f5a</guid>
                
                    <category>
                        <![CDATA[ Android ]]>
                    </category>
                
                    <category>
                        <![CDATA[ PWA ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Sanjay ]]>
                </dc:creator>
                <pubDate>Tue, 19 Aug 2025 17:52:20 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1755625913612/bfffd5f9-f4d6-4f8d-aae8-72f5730bd7e9.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>If you are a web developer who doesn’t know about App Development (like me!), then this article is for you. I’ll teach you how to turn your website into a native app, without new frameworks or languages. You’ll learn how to convert a website to a PWA (Progressive Web App) that you can publish on the Play Store.</p>
<p>First, we’ll turn your website into a Progressive Web App (PWA). Then we'll use a free command-line tool from Google called <strong>Bubblewrap</strong> to package that PWA into an Android app. Let’s get started.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>If you follow along with this tutorial, there are some prerequisites:</p>
<ul>
<li><p>Basic knowledge of web development</p>
</li>
<li><p>Your site should be live to the public, and you’ll need to have access to its source code.</p>
</li>
<li><p>We'll use npm to install the necessary tools, so make sure you have Node.js installed.</p>
</li>
</ul>
<p><strong>Note:</strong> This tutorial is based on a <strong>Vite</strong> project, but the final steps with Bubblewrap are the same for any web framework.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><p><a class="post-section-overview" href="#heading-what-is-a-pwa">What is a PWA?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-is-bubblewrap">What is Bubblewrap?</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-is-a-twa-trusted-web-activity">What is a TWA (Trusted Web Activity)?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-twa-verifies-trust">How TWA Verifies Trust</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-step-1-configure-your-pwa-in-vite">Step 1 – Configure Your PWA in Vite</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-create-your-app-icons">Create Your App Icons</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-install-the-vite-pwa-plugin">Install the Vite PWA plugin.</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-configure-the-plugin">Configure the Plugin</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-step-2-create-the-android-app">Step 2 – Create the Android App</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-create-a-build-folder">Create a Build Folder</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-install-the-bubblewrap-cli">Install the Bubblewrap CLI</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-initialize-the-project">Initialize the Project</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-lets-troubleshoot-the-init-command">Let’s troubleshoot the init command</a>.</p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-step-3-answer-bubblewrap-questions">Step 3 – Answer Bubblewrap Questions</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-4-build-the-app">Step 4 – Build the App</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-5-setting-up-twa-validation">Step 5 – Setting Up TWA Validation</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-is-the-well-known-folder">What is the .well-known folder?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-is-delegatepermissioncommonhandleallurls">What is delegate_permission/common.handle_all_urls?</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-step-6-optional-customize-the-in-app-experience">Step 6 (Optional) – Customize the In-App Experience</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-wrapping-up">Wrapping Up</a></p>
</li>
</ol>
<h2 id="heading-what-is-a-pwa">What is a PWA?</h2>
<p>PWA stands for <strong>Progressive Web Application</strong>, and its goal is to make your website look and feel just like a native app. If you’ve visited a website in your browser and seen an install icon that lets you download it to your phone or laptop, you've used a PWA.</p>
<p>But it’s not just about the look and feel. A PWA also has app-like features, such as working offline, sending push notifications, and more.</p>
<p>There are two main components of a PWA.</p>
<ul>
<li><p>The manifest file describes your app, such as its name, icons, start URL, and so on.</p>
</li>
<li><p>A service worker is a background JavaScript file that acts as a proxy. The caching and push notifications are handled by a service file, which runs as a different thread apart from the main thread.</p>
</li>
</ul>
<p>Without these two components, browsers won’t let users download the app locally.</p>
<p>The manifest file and the service worker are like a checklist for the browser. When you visit a website, the browser looks for both of these components. If they are present and correctly configured, the browser knows it's a true PWA and will show the "install" icon, allowing users to download the app locally. Without them, the browser just sees a regular website, and the option to install won't be available.</p>
<h2 id="heading-what-is-bubblewrap">What is Bubblewrap?</h2>
<p>Bubblewrap is a command-line tool made by Google that takes your PWA and turns it into an Android App using a Trusted Web Activity (TWA).</p>
<p>Bubblewrap simplifies the process of creating a TWA, turning a PWA's manifest file into an Android app package (APK or AAB).</p>
<h3 id="heading-what-is-a-twa-trusted-web-activity">What is a TWA (Trusted Web Activity)?</h3>
<p>A Trusted Web Activity (TWA) is a modern Android feature that lets you display your live website full-screen inside an Android app. Basically, it runs the website on the browser, but it doesn’t show the browser address bar on the App. This helps it feel like a native app.</p>
<p>To unlock this full-screen feature, your app needs to be “Trusted“.</p>
<p>This is where the "secret handshake" comes in. Android needs to be sure that the person who built the app and the person who owns the website are the same. Without this proof of ownership, the TWA will run in a fallback mode and show the browser address bar at the top, ruining the native app feel.</p>
<h3 id="heading-how-twa-verifies-trust">How TWA Verifies Trust</h3>
<p>This trust is verified using a system called <strong>Digital Asset Links</strong>. You place a special file on your website (we'll do this in the implementation part) that contains your app's unique digital fingerprint. When a user opens your app, the Android OS checks this file. If the fingerprints match, it grants your app "trusted" status, removes the address bar, and enables other features like deep linking.</p>
<p>You can check this relationship yourself using Google's official testing tool: <a target="_blank" href="https://developers.google.com/digital-asset-links/tools/generator">Digital Asset Links Verifier.</a></p>
<p>Now that you understand the project and tools, let’s start building.</p>
<h2 id="heading-step-1-configure-your-pwa-in-vite">Step 1 – Configure Your PWA in Vite</h2>
<p>The first step is to add the two main components for a PWA: the manifest file and service worker. This is what will allow the browser to recognize it as "installable."</p>
<p>This guide is based on a project built with Vite, which makes this process easy with a special plugin. If you're using a different tool, the concepts are the same, but you'll need to look up different resources about the specific steps for your environment.</p>
<h3 id="heading-create-your-app-icons">Create Your App Icons</h3>
<p>Before we touch any code, we need the icons for our app. Android requires specific sizes for the app's launcher icon (what you see on your home screen) and the splash screen (what you see when the app starts).</p>
<p>You'll need two main sizes: <code>192x192</code> pixels and <code>512x512</code> pixels. You can use this <a target="_blank" href="https://realfavicongenerator.net/">Favicon Generator</a> to generate your logo in the respective sizes. You can upload your main logo, and it will generate all the necessary sizes for you.</p>
<p>Then just download the generated files and place the <code>192x192</code> and <code>512x512</code> files into the <code>public</code> folder of your project.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1755067586673/f7e06fc2-4b55-4ec3-af05-b2e78bf19273.png" alt="f7e06fc2-4b55-4ec3-af05-b2e78bf19273" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h3 id="heading-install-the-vite-pwa-plugin">Install the Vite PWA plugin.</h3>
<p>A PWA requires a manifest file and a service worker. We can create these manually, but this plugin automates that entire process. It will automatically generate a <code>manifest.json</code> and <code>service-worker.js</code> for you every time you build your project.</p>
<pre><code class="lang-bash">npm install vite-plugin-pwa -D
</code></pre>
<h3 id="heading-configure-the-plugin">Configure the Plugin</h3>
<p>In this step, we’ll use this plugin and configure our app's manifest. Edit the <code>vite.config.ts</code> file. This configuration will tell the plugin what to name your app, which icons to use, and so on.</p>
<p>In <code>vite.config.ts</code>:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> defineConfig({
  plugins: [
    VitePWA({
      registerType: <span class="hljs-string">"autoUpdate"</span>,   
      manifest: {
        name: <span class="hljs-string">"your app name"</span>,
        short_name: <span class="hljs-string">"your app short name"</span>,
        description: <span class="hljs-string">"write any description"</span>,
        theme_color: <span class="hljs-string">"#0d1117"</span>,
        background_color: <span class="hljs-string">"#ffffff"</span>,
        display: <span class="hljs-string">"standalone"</span>,
        start_url: <span class="hljs-string">"/"</span>,
        icons: [
          {
            src: <span class="hljs-string">"/web-app-manifest-192x192.png"</span>,
            sizes: <span class="hljs-string">"192x192"</span>,
            <span class="hljs-keyword">type</span>: <span class="hljs-string">"image/png"</span>,
          },
          {
            src: <span class="hljs-string">"/web-app-manifest-512x512.png"</span>,
            sizes: <span class="hljs-string">"512x512"</span>,
            <span class="hljs-keyword">type</span>: <span class="hljs-string">"image/png"</span>,
          },
        ],
      },
    }),
  ]
</code></pre>
<p>Now, when you run <code>npm run build</code>, the plugin will automatically generate the manifest and service worker files for you. With that done, deploy the changes. Now your website is a PWA.</p>
<h2 id="heading-step-2-create-the-android-app">Step 2 – Create the Android App</h2>
<p>Now that your website is a PWA, let’s use Bubblewrap to package it into an Android app.</p>
<h3 id="heading-create-a-build-folder">Create a Build Folder</h3>
<p>Create a dedicated folder for your Android project files. In your project's root, create a new folder. I'll call mine <code>android</code>.</p>
<pre><code class="lang-plaintext">project/
├── client/
├── server/
└── android/
</code></pre>
<p>Now navigate to the new folder that you created.</p>
<h3 id="heading-install-the-bubblewrap-cli">Install the Bubblewrap CLI</h3>
<pre><code class="lang-bash">npm install -g @bubblewrap/cli
</code></pre>
<h3 id="heading-initialize-the-project">Initialize the Project</h3>
<p>Next, run the <code>init</code> command. Bubblewrap will connect to your live website, read the <code>manifest.webmanifest</code> file that Vite created, and use that information to generate a basic Android project.</p>
<pre><code class="lang-bash">bubblewrap init --manifest=https://your-website-domain/manifest.webmanifest
</code></pre>
<p>Run the command, replacing <code>your-website-domain</code> with your actual URL:</p>
<h3 id="heading-lets-troubleshoot-the-init-command">Let’s troubleshoot the <code>init</code> command</h3>
<p>As you run the <code>init</code> command, Bubblewrap will need two key software packages: the <strong>Java Development Kit (JDK)</strong> and the <strong>Android SDK</strong>. It will offer to install them for you.</p>
<h4 id="heading-jdk-setup">JDK setup:</h4>
<pre><code class="lang-bash">? Do you want Bubblewrap to install the JDK (recommended)?
  (Enter <span class="hljs-string">"No"</span> to use your own JDK 17 installation) (Y/n)
</code></pre>
<p>In my case, when I let Bubblewrap install the JDK, the process downloaded the files but then failed at the "decompressing" step. If you face this same problem, don't worry! The fix is to install it manually.</p>
<ul>
<li><p>Say <strong>No</strong> to the prompt.</p>
</li>
<li><p>Download the recommended version (usually JDK 17) from a source like <a target="_blank" href="https://adoptium.net/temurin/releases/?version=17">Adoptium</a>.</p>
</li>
<li><p>Install it and set up your system's environment variables to include the JDK's <code>bin</code> path. If you’re not sure how to set environment variables, you can check out this site: <a target="_blank" href="https://www.c-sharpcorner.com/article/how-to-addedit-path-environment-variable-in-windows-11/">Set Environment Variables</a>.</p>
</li>
<li><p>When Bubblewrap asks for the path, provide it directly, such as <code>C:\java\jdk-17.0.16.8-hotspot</code>.</p>
</li>
</ul>
<h4 id="heading-android-sdk-setup">Android SDK setup:</h4>
<p>Once the JDK is set up successfully, the next step is to configure the Android SDK.</p>
<pre><code class="lang-bash">? Do you want Bubblewrap to install the Android SDK (recommended)?
  (Enter <span class="hljs-string">"No"</span> to use your own Android SDK installation) (Y/n)
</code></pre>
<p>Since I didn't have the Android SDK, I let Bubblewrap handle this by selecting <strong>Yes</strong>. I didn't face any problems here.</p>
<p>If you face any problem in setting up on Android SDK, just set it up manually and give the path, just like the JDK setup.</p>
<h2 id="heading-step-3-answer-bubblewrap-questions">Step 3 – Answer Bubblewrap Questions</h2>
<p>After the SDK is set up, Bubblewrap will ask a bunch of questions to configure your app. This information is used to create the <code>twa-manifest.json</code> file, which is the blueprint for your App.</p>
<pre><code class="lang-plaintext">Domain: Press Enter (auto-filled from your manifest)

Application name: Your full app name

Application ID: (e.g, chat.yourapp.twa)

Display mode: standalone

Orientation: portrait

Status bar color: Press Enter (accepts default)

Splash screen color: Press Enter (accepts default)

Icon URL: Press Enter (accepts default)

Include support for Play Billing?: Type Y if your app uses Google Play in-app purchases. Otherwise, N

Request geolocation permission?: Type Y if your app needs location access. Otherwise, N
</code></pre>
<p>In these questions, the important part is the key store and the key.</p>
<pre><code class="lang-plaintext">First and Last names: Your full name

Organizational Unit: Developer or anything

Organization: Your organization name

Country (2-letter code): Your country code

Password for key store: Enter a new password

Password for key: Re-enter the same password
</code></pre>
<p><strong>Note:</strong> These passwords for both the key store and key should be the same, or else it will throw an error. <strong>Refer to this issue:</strong> <a target="_blank" href="https://github.com/GoogleChromeLabs/bubblewrap/issues/713">Bubblewrap Issue</a>.</p>
<h2 id="heading-step-4-build-the-app">Step 4 – Build the App</h2>
<pre><code class="lang-bash">bubblewrap build --universalApk
</code></pre>
<p>This command starts building your application. Here, the flag <code>universalApk</code> will produce the <code>.apk</code> and <code>.abb</code>. If you’re going to publish your application in the Play Store, upload the <code>.abb</code> file to the Play Store. For our testing, we need an APK file, so this flag <code>universalApk</code> will produce both files. If we didn't give this flag, it would only give us <code>.abb</code>.</p>
<h2 id="heading-step-5-setting-up-twa-validation">Step 5 – Setting Up TWA Validation</h2>
<p>Once the build is done, you’ll get the APK. Transfer it to your phone and test it. When you open the app, you’ll see the browser address bar. This is because we haven't set up the "trust" between your app and your website yet. Let's fix that now.</p>
<p>In your frontend project, go to the <code>public</code> folder, create a new folder called <code>.well-known</code>, and inside that, create a file called <code>assetlinks.json</code>.</p>
<pre><code class="lang-bash">frontend/
├── public/
    ├── .well-known/
        └── assetlinks.json
</code></pre>
<h3 id="heading-what-is-the-well-known-folder">What is the <code>.well-known</code> folder?</h3>
<p>A well-known folder is used to store files that define configurations for protocols, as it’s used for external sources to find the validation for your website. In our case, our app checks the well-known folder from our website and verifies the validation.</p>
<p>Paste the following into <code>assetlinks.json</code>:</p>
<pre><code class="lang-json">[
  {
    <span class="hljs-attr">"relation"</span>: [<span class="hljs-string">"delegate_permission/common.handle_all_urls"</span>],
    <span class="hljs-attr">"target"</span>: {
      <span class="hljs-attr">"namespace"</span>: <span class="hljs-string">"android_app"</span>,
      <span class="hljs-attr">"package_name"</span>: <span class="hljs-string">"chat.yourapp.twa"</span>,
      <span class="hljs-attr">"sha256_cert_fingerprints"</span>: [
       <span class="hljs-string">"your_sha256_fingerprint"</span>
      ]
    }
  }
]
</code></pre>
<h3 id="heading-what-is-delegatepermissioncommonhandleallurls">What is <code>delegate_permission/common.handle_all_urls</code>?</h3>
<p>This is a special flag that opens all the links from the app instead of the domain. Simply put, it acts as a deeplink. After you install the app, if you click your website link from WhatsApp or from somewhere, it will open your app instead of opening in a browser, acting as a deeplink.</p>
<p>The <code>package_name</code> field should be the <code>packageId</code>, which you can get from your Android build folder in <code>twa-manifest.json</code>.</p>
<p>Now, get your fingerprints. Run the following command to do so:</p>
<pre><code class="lang-bash">keytool -list -v -keystore android.keystore -<span class="hljs-built_in">alias</span> android
</code></pre>
<p>The alias name should be the value that you created. Once you enter this command, it’ll ask for the key store password. Enter that, and you’ll get your <code>SHA256</code> fingerprint. Copy that and paste it into the <code>assetslinks.json</code> file in the <code>sha256_cert_fingerprints</code> array. Now push these changes to production. You can verify the validation in <a target="_blank" href="https://developers.google.com/digital-asset-links/tools/generator">Digital Asset Links</a></p>
<p>That’s it! Now you can install the app and test it.</p>
<h2 id="heading-step-6-optional-customize-the-in-app-experience"><strong>Step 6 (Optional) – Customize the In-App Experience</strong></h2>
<p>Now, additionally, there will be some cases where we want to show different content to users on the website vs the mobile app. Can we do that? Yes!</p>
<p>In your Android build folder, in <code>twa-manifest.json</code>, there will be a field called <code>startUrl</code>. If not, add it and add the value  <code>"startUrl": "/?twa=true"</code>. The <code>startUrl</code> is the entry point. I have a query parameter of value <code>twa=true</code>.</p>
<p>Run the build again with <code>bubblewrap build --universalApk</code>.</p>
<p>Now, if you open your app, it will open the app with the entry URL as <code>yourwebsitedomain.com/?twa=true</code>.</p>
<p>In your frontend:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> twaParam = queryParams.get(<span class="hljs-string">"twa"</span>);

<span class="hljs-keyword">const</span> [isTwa, setIsTwa] = useState&lt;<span class="hljs-built_in">boolean</span>&gt;(<span class="hljs-function">() =&gt;</span> {
   <span class="hljs-keyword">return</span> <span class="hljs-built_in">localStorage</span>.getItem(<span class="hljs-string">"isTwa"</span>) === <span class="hljs-string">"true"</span>;
});

useEffect(<span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">if</span> (twaParam === <span class="hljs-string">"true"</span>) {
    <span class="hljs-built_in">localStorage</span>.setItem(<span class="hljs-string">"isTwa"</span>, <span class="hljs-string">"true"</span>); <span class="hljs-comment">// set the value to local storage</span>
    setIsTwa(<span class="hljs-literal">true</span>);
  }
}, [twaParam]);
</code></pre>
<pre><code class="lang-typescript"> {isTwa? (
    &lt;Link to=<span class="hljs-string">"/contact"</span> className=<span class="hljs-string">"underline hover:text-primary"</span>&gt;
       Contact
    &lt;/Link&gt; 
  ) : (
     &lt;Link to=<span class="hljs-string">"/download"</span> className=<span class="hljs-string">"underline hover:text-primary"</span>&gt;
       Download App
      &lt;/Link&gt;
  )}
</code></pre>
<p>In the code above, we check for the <code>twa=true</code> query parameter in the URL. If it's present, we save that information to local storage, and then we conditionally render the content for the user.</p>
<p>That's it. We have created an App.</p>
<p>If you want to change any name, colour, or splash screen, you can change it in <code>twa-manifest.json</code> and run the build again.</p>
<h2 id="heading-wrapping-up">Wrapping Up</h2>
<p>Bubblewrap is only for Android. If you want the app to support cross-platform, there are some other platforms, like Capacitor, which I’ll write about in another article.</p>
<p>By the way, you can check out the App that I made using Bubblewrap here: <a target="_blank" href="https://strangertalk.chat/download">Stranger Talk</a>.</p>
<p>If there are any mistakes or you have any questions, contact me on <a target="_blank" href="https://www.linkedin.com/in/sanjay-r-ab6064294/">LinkedIn</a> or <a target="_blank" href="https://www.instagram.com/heheheh_pet/profilecard/?igsh=eXh3MWw4ZzZ3NTRq">Instagram</a>.</p>
<p>Thank you for reading!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Audit Android Accessibility with the Accessibility Scanner App ]]>
                </title>
                <description>
                    <![CDATA[ The Web Content Accessibility Guidelines (WCAG 2.1 Level AA) is an internationally recognized standard for digital accessibility. Meeting these guidelines helps you make sure that your website is usable by people with visual, motor, hearing, and cogn... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-audit-android-accessibility-with-the-accessibility-scanner-app/</link>
                <guid isPermaLink="false">6862d146cc277a35bb68ec20</guid>
                
                    <category>
                        <![CDATA[ Android ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Accessibility ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Mobile app accessibility testing ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Ilknur Eren ]]>
                </dc:creator>
                <pubDate>Mon, 30 Jun 2025 18:02:46 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1751301060182/df4d483a-8dd6-45ce-a665-76cbf45ef945.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>The Web Content Accessibility Guidelines (WCAG 2.1 Level AA) is an internationally recognized standard for digital accessibility. Meeting these guidelines helps you make sure that your website is usable by people with visual, motor, hearing, and cognitive impairments.</p>
<p>Google’s <a target="_blank" href="https://play.google.com/store/apps/details?id=com.google.android.apps.accessibility.auditor&amp;hl=en_US">Accessibility Scanner</a> on Google Play is a free app that offers developers, designers, and product leaders the ability to audit their app to find accessibility issues. The app is designed to highlight accessibility issues that might not meet the WCAG 2.1 Level AA standards. </p>
<p>Once installed, the Accessibility Scanner allows you to take screenshots or video recordings of your app, then highlights areas that may not meet accessibility requirements, like small touch targets, low color contrast, or missing content labels.</p>
<h3 id="heading-heres-what-well-cover">Here’s what we’ll cover:</h3>
<ol>
<li><p><a class="post-section-overview" href="#heading-how-to-download-and-enable-the-accessibility-scanner">How to Download and Enable the Accessibility Scanner</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-use-the-accessibility-scanner">How to Use the Accessibility Scanner</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-how-to-use-the-snapshot-feature">How to Use the Snapshot Feature</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-use-the-record-feature">How to Use the Record Feature</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-why-use-the-accessibility-scanner">Why Use the Accessibility Scanner?</a></p>
</li>
</ol>
<h2 id="heading-how-to-download-and-enable-the-accessibility-scanner"><strong>How to Download and Enable the Accessibility Scanner</strong></h2>
<p>In five quick steps, you can download the Accessibility App and enable it on your Android device.</p>
<ol>
<li><p>Search “Accessibility Scanner” on Google Play Store and download it.</p>
</li>
<li><p>Find the downloaded app on your device and open it.</p>
</li>
<li><p>Turn on the Accessibility scanner by clicking on the “Turn on” button on the bottom right side of the page. This will take you to your Accessibility Settings.</p>
</li>
<li><p>In the Accessibility Setting page, click on the Accessibility Scanner button. This will take you to the Accessibility Scanner Settings.</p>
</li>
<li><p>Find Accessibility Scanner toggle and turn it on. (This will open a modal that asks if you allow “Accessibility Scanner” to have full control of your device, click Allow.</p>
</li>
</ol>
<p>After step five, you will have a blue checkmark icon will appear on the right side of your screen (see image below). This floating icon gives you quick access to start scanning any screen for accessibility issues.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750821547116/75f49863-7f19-4db5-ada1-45483c0df70b.png" alt="Facebook Log in Page with Accessibility Scanner toggle on the right with arrow pointing to it" class="image--center mx-auto" width="2680" height="1472" loading="lazy"></p>
<h2 id="heading-how-to-use-the-accessibility-scanner"><strong>How to Use the Accessibility Scanner</strong></h2>
<p>To scan or record your app to find accessibility issues, tap the blue checkmark icon. You’ll see a few options after clicking on the blue checkmark:</p>
<ul>
<li><p><strong>Record</strong>: Captures a short video of user interaction and generates a report of potential accessibility issues.</p>
</li>
<li><p><strong>Snapshot</strong>: Takes a static screenshot and flags issues found on that screen.</p>
</li>
<li><p><strong>Turn off:</strong> Turns the Accessibility Scanner off.</p>
</li>
<li><p><strong>Collapse:</strong> Collapses the options to show the initial blue checkmark.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750895121001/9673c7d5-5182-4c99-b36a-1b2a2e27986b.png" alt="Facebook Log in Page with Accessibility Scanner toggle opened on the right with arrow pointing to it" class="image--center mx-auto" width="1694" height="1038" loading="lazy"></p>
<p>You can choose between taking a single <strong>Snapshot</strong> or recording user flow using <strong>Record</strong> to evaluate multiple screens.</p>
<h3 id="heading-how-to-use-the-snapshot-feature">How to Use the Snapshot Feature</h3>
<p>The snapshot button will take a snapshot of the page you are currently in and give you a result of accessibility issues that may be on the page. The accessibility issues will be highlighted in red boxes.</p>
<p>The image below is the result of taking a snapshot of the Facebook log in page. The accessibility scanner states that there are 10 accessibility suggestions on this page alone.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750898582440/76cc763c-e6db-46a9-b062-2e29a57e7022.jpeg" alt="Facebook log in page with red boxes around several elements, highlighting accessibility issues." class="image--center mx-auto" width="1080" height="2400" loading="lazy"></p>
<p>You can click on the highlighted area in order to get more details of the potential accessibility issue. For example, you can click on the red box that is highlighting the “Mobile number or email” form that’s in the image above. Once you click on the highlighted area, you will get additional information.</p>
<p>The image below is the result of clicking on the “Mobile number or email” form element. Accessibility Scanner is highlighting errors it found on this email form.</p>
<p>The first suggestion it gives is to fix the item label, because the item may not have a label readable by screen readers. The second issue it highlights is the Touch Target and suggests that the target should be larger. The final suggestion is the Unexposed Text, possible text detected: Mobile number or email.</p>
<p>Snapshots allow us to take screenshots of our pages and highlight accessibility issues.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750898563142/ce93909e-b351-405c-8367-dd47d7d19c9f.jpeg" alt="Email form field is selected from Accessibility Scanner. Scanner shows three areas to fix." class="image--center mx-auto" width="1080" height="2400" loading="lazy"></p>
<h3 id="heading-how-to-use-the-record-feature">How to Use the Record Feature</h3>
<p>If you select to record, the Accessibility Scanner will take snapshots at intervals as you go through your app’s pages. To end the recording, tap the blue pause button (which replaces the original checkmark during recording).</p>
<p>Once you stop recording, Accessibility Scanner will give you the several snapshots and highlighted errors. The image below is the result of recording the Facebook log in page in less than a minute.</p>
<p>While recording, I navigated to other pages within the app. The recording gave 5 snapshots of the pages I was going through. You can see the snapshots on top of the page. In the image below, I am on screen one of five,. I can click to the other snapshots underneath the words, “Screen 1 of 5” and see issues for different snapshots taken during my recording. Similar to the snapshot accessibility audit, you can click on the red boxes and get more information on the errors.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750898542344/a390f512-262d-40c1-87ad-35e36c31def4.jpeg" alt="Facebook Log in Page with Accessibility Scanner highlighting elements with accessibility issues." class="image--center mx-auto" width="1080" height="2400" loading="lazy"></p>
<h2 id="heading-why-use-the-accessibility-scanner"><strong>Why Use the Accessibility Scanner?</strong></h2>
<p>The Accessibility Scanner is a valuable tool for teams throughout the app development lifecycle. Engineers can use it early in the process to scan the app locally, identify accessibility issues, and resolve them before release. During the QA phase, designers and product managers can use the scanner to audit user interfaces and flag potential accessibility concerns. Even after an app is in production, all teams can continue to use the scanner to monitor and improve accessibility.</p>
<p>But it’s important to note that the Accessibility Scanner is just one part of an accessibility strategy – it’s not a complete replacement for manual testing or audits. And it won’t catch all types of accessibility barriers – especially those that require keyboard navigation, screen reader testing, or cognitive usability reviews. But it is a simple and effective starting point for improving accessibility in Android apps.</p>
<p>You should use it alongside other tools, such as Android’s TalkBack for screen reader testing. Most importantly, real-world feedback from people who use assistive technologies is essential to identifying usability barriers that automated tools may miss.</p>
<p>With just a few taps, Accessibility Scanner helps surface issues that might otherwise be missed. It’s a free, lightweight, and essential tool for anyone building inclusive mobile experiences.</p>
<h2 id="heading-thanks-for-reading">Thanks for Reading!</h2>
<p>You should now know how to get started using the Accessibility Scanner to check your apps’ accessibility and make sure they’re usable by everyone.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Master Kotlin & Android 60-Hour Course ]]>
                </title>
                <description>
                    <![CDATA[ Do you want to create the next groundbreaking mobile app? Kotlin, a modern and powerful language officially backed by Google, not only makes Android development more efficient and enjoyable but also opens doors to diverse programming opportunities be... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/master-kotlin-and-android-60-hour-course/</link>
                <guid isPermaLink="false">6824aba7be2c002301d59188</guid>
                
                    <category>
                        <![CDATA[ Kotlin ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Android ]]>
                    </category>
                
                    <category>
                        <![CDATA[ youtube ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Beau Carnes ]]>
                </dc:creator>
                <pubDate>Wed, 14 May 2025 14:41:43 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1747233684976/019ffd11-b74c-437d-815f-857ab3465317.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Do you want to create the next groundbreaking mobile app? Kotlin, a modern and powerful language officially backed by Google, not only makes Android development more efficient and enjoyable but also opens doors to diverse programming opportunities beyond mobile. Whether you're looking to build innovative applications, solve real-world problems, or join a thriving global community of developers, learning Kotlin and Android is a great investment in your future.</p>
<p>We just posted a massive 60-hour Kotlin and Android Development course on the freeCodeCamp.org YouTube channel. This course will help you master modern Android practices. Alexandru Cristian developed this course. It’s packed with hands-on practice, ensuring you not only learn the theory but also apply it by building multiple real-world applications.</p>
<p>This is a thorough exploration of the Kotlin programming language and the Android development ecosystem. Here’s a glimpse of what you’ll learn:</p>
<h3 id="heading-kotlin-from-the-ground-up"><strong>Kotlin from the ground up</strong></h3>
<ul>
<li><p>Solidify your understanding of Kotlin syntax, variables, operators, control flow (loops, conditionals), and null safety.</p>
<ul>
<li><p>Dive deep into Object-Oriented Programming (OOP) with Kotlin, covering classes, inheritance, interfaces, abstract classes, and data classes.</p>
</li>
<li><p>Master Kotlin Collections (lists, sets, maps) and powerful functions to manipulate them.</p>
</li>
<li><p>Explore advanced concepts like Generics, Lambda functions, and Kotlin Coroutines for efficient asynchronous programming.</p>
</li>
<li><p>Even touch upon SQL basics to understand data persistence.</p>
</li>
</ul>
</li>
</ul>
<h3 id="heading-comprehensive-android-development"><strong>Comprehensive android development</strong></h3>
<ul>
<li><p>Get started with Android Studio and understand the Android project structure.</p>
<ul>
<li><p>Learn traditional UI development with XML, including various layouts (LinearLayout, RelativeLayout, ConstraintLayout) and UI widgets.</p>
</li>
<li><p>Master Android Activities and Fragments, their lifecycles, and how to navigate between screens using Intents and the modern Navigation Component.</p>
</li>
<li><p>Build dynamic lists with RecyclerView.</p>
</li>
<li><p>Understand and implement Material Design principles.</p>
</li>
<li><p>Work with data persistence using Room Database and connect to the cloud with Firebase Firestore.</p>
</li>
<li><p>Make network requests and handle APIs using Retrofit and parse JSON data.</p>
</li>
<li><p>Implement robust app architecture using MVVM (Model-View-ViewModel), LiveData, and potentially Dependency Injection.</p>
</li>
<li><p>Get an introduction to the future of Android UI with Jetpack Compose.</p>
</li>
</ul>
</li>
</ul>
<p>Theory is important, but practice is what makes a developer. Throughout this 60-hour course, you'll be building a portfolio of applications. The final project is an Uber clone that will have you implementing features like maps integration, user authentication, real-time location tracking, and more, demonstrating truly professional-grade development techniques.</p>
<h3 id="heading-start-learning-today">Start learning today</h3>
<p>Learning Kotlin and Android development opens doors to an exciting career in mobile technology. Android powers billions of devices worldwide, and skilled developers are in constant demand. Kotlin is a modern, concise, and powerful language officially supported by Google for Android development, making it an essential skill for today's app creators.</p>
<p>Watch the full course on <a target="_blank" href="https://youtu.be/blKkRoZPxLc">the freeCodeCamp.org YouTube channel</a> (60-hour watch).</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/blKkRoZPxLc" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Use Tooltips in Jetpack Compose ]]>
                </title>
                <description>
                    <![CDATA[ When I wrote my last article about Jetpack Compose, I stated there that Jetpack Compose is missing some (in my opinion) basic components, and one of them is the tooltip. At the time, there was no built-in composable to display tooltips and there were... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-use-tooltips-in-jetpack-compose/</link>
                <guid isPermaLink="false">66fd516514798d90f2228542</guid>
                
                    <category>
                        <![CDATA[ Android ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Jetpack Compose ]]>
                    </category>
                
                    <category>
                        <![CDATA[ tooltip ]]>
                    </category>
                
                    <category>
                        <![CDATA[ UI ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Tomer ]]>
                </dc:creator>
                <pubDate>Wed, 02 Oct 2024 13:57:57 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1727813989960/b0a7ab29-d87c-4d87-9847-70b7e1c341b1.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>When I wrote my <a target="_blank" href="https://medium.com/better-programming/is-jetpack-compose-ready-for-you-eae6c93ad3f8">last article about Jetpack Compose</a>, I stated there that Jetpack Compose is missing some (in my opinion) basic components, and one of them is the tooltip.</p>
<p>At the time, there was no built-in composable to display tooltips and there were several alternative solutions circling online. The problem with those solutions was that once Jetpack Compose released newer versions, those solutions might break. So it wasn’t ideal and the community was left hoping that sometime in the future, support would be added for tooltips.</p>
<p>I’m glad to say that since <a target="_blank" href="https://developer.android.com/jetpack/androidx/releases/compose-material3#1.1.0">version 1.1.0 of Compose Material 3</a>, we now have built in tooltip support. 👏</p>
<p>While this in itself is great, more than a year has passed since that version was released. And with subsequent versions, the API related to tooltips changed drastically as well.</p>
<p>If you go over the changelog, you will see how the public and internal APIs have changed. So bear in mind, that when you read this article, things may have continued to change as everything related to Tooltips is still marked by the annotation <strong>ExperimentalMaterial3Api::class</strong>.</p>
<p>❗️ The version of material 3 used for this article is 1.2.1, which was released on March 6th, 2024</p>
<h2 id="heading-tooltip-types">Tooltip Types</h2>
<p>We now have support for two different types of tooltips:</p>
<ol>
<li><p>Plain tooltip</p>
</li>
<li><p>Rich media tooltip</p>
</li>
</ol>
<h3 id="heading-plain-tooltip">Plain Tooltip</h3>
<p>You can use the first kind to provide information about an icon button that wouldn’t be clear otherwise. For example, you can use a plain tooltip to indicate to a user what the icon button represents.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727602449314/94cf84bf-dec0-462c-a8a0-6f878e0d5db3.gif" alt="Basic tooltip example" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>To add a tooltip to your application, you use the <strong>TooltipBox</strong> composable. This composable takes several arguments:</p>
<pre><code class="lang-kotlin"><span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">TooltipBox</span><span class="hljs-params">(
    positionProvider: <span class="hljs-type">PopupPositionProvider</span>,
    tooltip: @<span class="hljs-type">Composable</span> <span class="hljs-type">TooltipScope</span>.() -&gt; <span class="hljs-type">Unit</span>,
    state: <span class="hljs-type">TooltipState</span>,
    modifier: <span class="hljs-type">Modifier</span> = Modifier,
    focusable: <span class="hljs-type">Boolean</span> = <span class="hljs-literal">true</span>,
    enableUserInput: <span class="hljs-type">Boolean</span> = <span class="hljs-literal">true</span>,
    content: @<span class="hljs-type">Composable</span> () -&gt; <span class="hljs-type">Unit</span>,
)</span></span>
</code></pre>
<p>Some of these should be familiar to you if you have used Composables before. I’ll highlight the ones that have a specific use case here:</p>
<ul>
<li><p>positionProvider - Of <strong>PopupPositionProvider</strong> type, and is used to calculate the position of the tooltip.</p>
</li>
<li><p>tooltip - This is where you can design the UI of how the tooltip will look like.</p>
</li>
<li><p>state - This holds the state that is associated with a specific Tooltip instance. It exposes methods like showing/dismissing the tooltip and when instantiating an instance of one, you can declare if the tooltip should be persistent or not (meaning if it should keep displaying on the screen until a user performs a click action outside the tooltip).</p>
</li>
<li><p>content - This is the UI that the tooltip will display above/below.</p>
</li>
</ul>
<p>Here is an example of instantiating a <strong>BasicTooltipBox</strong> with all the relevant arguments filled in:</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class)</span>
<span class="hljs-meta">@Composable</span>
<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">BasicTooltip</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">val</span> tooltipPosition = TooltipDefaults.rememberPlainTooltipPositionProvider()
    <span class="hljs-keyword">val</span> tooltipState = rememberBasicTooltipState(isPersistent = <span class="hljs-literal">false</span>)

    BasicTooltipBox(positionProvider = tooltipPosition,
        tooltip =  { Text(<span class="hljs-string">"Hello World"</span>) } ,
        state = tooltipState) {
        IconButton(onClick = { }) {
            Icon(imageVector = Icons.Filled.Favorite, 
                 contentDescription = <span class="hljs-string">"Your icon's description"</span>)
        }
    }
}
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727602558759/e00e0bed-6a95-489e-af5c-a7d9dcc33fe6.gif" alt="A basic tooltip" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Jetpack Compose has a built in class called TooltipDefaults. You can use this class to help you instantiate arguments that make up a TooltipBox. For instance, you could use <strong>TooltipDefaults.rememberPlainTooltipPositionProvider</strong> to correctly position the tooltip in relation to the anchor element.</p>
<h3 id="heading-rich-tooltip">Rich Tooltip</h3>
<p>A rich media tooltip takes more space than a plain tooltip and can be used to provide more context about the functionality of an icon button. When the tooltip is shown, you can add buttons and links to it to provide further explanation or definitions.</p>
<p>It is instantiated in a similar way as a plain tooltip, inside of a TooltipBox, but you use the RichTooltip composable.</p>
<pre><code class="lang-kotlin">TooltipBox(positionProvider = tooltipPosition,
        tooltip = {
                  RichTooltip(
                      title = { Text(<span class="hljs-string">"RichTooltip"</span>) },
                      caretSize = caretSize,
                      action = {
                          TextButton(onClick = {
                              scope.launch {
                                  tooltipState.dismiss()
                                  tooltipState.onDispose()
                              }
                          }) {
                              Text(<span class="hljs-string">"Dismiss"</span>)
                          }
                      }
                  ) {
                        Text(<span class="hljs-string">"This is where a description would go."</span>)
                  }
        },
        state = tooltipState) {
        IconButton(onClick = {
            <span class="hljs-comment">/* Icon button's click event */</span>
        }) {
            Icon(imageVector = tooltipIcon,
                contentDescription = <span class="hljs-string">"Your icon's description"</span>,
                tint = iconColor)
        }
    }
</code></pre>
<p>A few things to notice about a Rich tooltip:</p>
<ol>
<li><p>A Rich tooltip has support for a caret.</p>
</li>
<li><p>You can add an action (that is, a button) to the tooltip to give users an option to find out more information.</p>
</li>
<li><p>You can add logic to dismiss the tooltip.</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727602624042/40160d88-4e8a-4487-835d-1b74a9dd7c72.png" alt="Rich tooltip without a caret" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727602651265/f3e6f7fe-c4e1-4f98-972d-b20a273900b4.png" alt="Rich tooltip with a caret" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h3 id="heading-edge-cases">Edge Cases</h3>
<p>When you choose to mark your <strong>tooltip state as persistent</strong>, it means that once the user interacts with the UI that shows your tooltip, it will stay visible until the user presses anywhere else on the screen.</p>
<p>If you looked at the example of a Rich tooltip from above, you might have noticed that we have added a button to dismiss the tooltip once it’s clicked.</p>
<p>There is a problem that happens once a user presses that button. Since the dismiss action is performed on the tooltip, if a user wants to perform another long press on the UI item that invokes this tooltip, the tooltip won’t be shown again. This means that the state of the tooltip is persistent on it being dismissed. So, how do we go about and resolve this?</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727602690256/a31b56bb-77c4-4444-bab6-7ffcca3f5207.gif" alt="Second long press does not trigger the tooltip" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>In order to “reset” the state of the tooltip, we have to call the <strong>onDispose</strong> method that is exposed through the tooltip state. Once we do that, the tooltip state is reset and the tooltip will be shown again when the user performs a long press on the UI item.</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@OptIn(ExperimentalMaterial3Api::class)</span>
<span class="hljs-meta">@Composable</span>
<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">RichTooltip</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">val</span> tooltipPosition = TooltipDefaults.rememberRichTooltipPositionProvider()
    <span class="hljs-keyword">val</span> tooltipState = rememberTooltipState(isPersistent = <span class="hljs-literal">true</span>)
    <span class="hljs-keyword">val</span> scope = rememberCoroutineScope()

    TooltipBox(positionProvider = tooltipPosition,
        tooltip = {
                  RichTooltip(
                      title = { Text(<span class="hljs-string">"RichTooltip"</span>) },
                      caretSize = TooltipDefaults.caretSize,
                      action = {
                          TextButton(onClick = {
                              scope.launch {
                                  tooltipState.dismiss()
                                  tooltipState.onDispose()  <span class="hljs-comment">/// &lt;---- HERE</span>
                              }
                          }) {
                              Text(<span class="hljs-string">"Dismiss"</span>)
                          }
                      }
                  ) {

                  }
        },
        state = tooltipState) {
        IconButton(onClick = {  }) {
            Icon(imageVector = Icons.Filled.Call, contentDescription = <span class="hljs-string">"Your icon's description"</span>)
        }
    }
}
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727602730404/60f31668-ea66-4127-b6fc-41f3aca952ae.gif" alt="onDispose solves the issue" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Another scenario where the tooltip state does not reset is if instead of calling ourselves for the dismiss method per a user’s action, the user clicks outside of the tooltip, causing it to be dismissed. This calls the dismiss method behind the scenes and the tooltip state is set to dismissed. Long pressing on the UI element to see our tooltip again will result in nothing.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727602758707/60387e08-72e6-45d4-bd47-ffb2708e0efe.gif" alt="The tooltip does not show again" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Our logic that calls the tooltip’s onDispose method does not get triggered, so how can we reset the tooltip’s state?</p>
<p>Currently, I haven’t been able to figure this out. It might be related to the tooltip’s <a target="_blank" href="https://developer.android.com/reference/kotlin/androidx/compose/foundation/MutatorMutex">MutatorMutex</a>. Maybe with upcoming releases, there will be an API for this. I did notice that if other tooltips are present on the screen and they are pressed, this resets the previously clicked upon tooltip.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727602790121/25a81994-a508-4c71-8424-c45370a7999d.gif" alt="25a81994-a508-4c71-8424-c45370a7999d" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>If you would like to see the code featured here, you can go to <a target="_blank" href="https://github.com/TomerPacific/MediumArticles/tree/master/TooltipExample">this GitHub repository</a></p>
<p>If you would like to see tooltips in an application, you can check it out <a target="_blank" href="https://play.google.com/store/apps/details?id=com.tomerpacific.laundry">here</a>.</p>
<h4 id="heading-references">References</h4>
<ul>
<li><p><a target="_blank" href="https://m3.material.io/components/tooltips/overview">Material3 Tooltip Overview</a></p>
</li>
<li><p><a target="_blank" href="https://developer.android.com/reference/kotlin/androidx/compose/material3/TooltipDefaults">Tooltip Defaults</a></p>
</li>
<li><p><a target="_blank" href="https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tooltip.kt">Tooltip Source Code</a></p>
</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Migrate from Play Core Library ]]>
                </title>
                <description>
                    <![CDATA[ You may have recently received an email from Google Play Store stating the following: Update your Play Core Maven dependency to an Android 14 compatible version! Your current Play Core library is incompatible with targetSdkVersion 34 (Android 14), w... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/migrate-from-play-core-library/</link>
                <guid isPermaLink="false">66ba5031256e9dbeab31aa84</guid>
                
                    <category>
                        <![CDATA[ Android ]]>
                    </category>
                
                    <category>
                        <![CDATA[ android app development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Tomer ]]>
                </dc:creator>
                <pubDate>Wed, 26 Jun 2024 17:53:46 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/06/ben-hershey-fnRKVPx5_xY-unsplash.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>You may have recently received an email from Google Play Store stating the following:</p>
<blockquote>
<p><em>Update your Play Core Maven dependency to an Android 14 compatible version! Your current Play Core library is incompatible with targetSdkVersion 34 (Android 14), which introduces a backwards-incompatible change to broadcast receivers to improve user security. As a reminder, from August 31, Google Play requires all new app releases to target Android 14. Update to the latest Play Core library version dependency to avoid app crashes:</em> <a target="_blank" href="https://developer.android.com/guide/playcore#playcore-migration"><em>https://developer.android.com/guide/playcore#playcore-migration</em></a>  </p>
<p><em>You may not be able to release future versions of your app with this SDK version to production or open testing.</em></p>
</blockquote>
<p>Looks frightening, doesn’t it?</p>
<p>Don’t be so worried. It is actually easier than it looks.</p>
<h2 id="heading-what-the-change-is-actually-about">What the Change is Actually About</h2>
<p>Basically, Google stopped releasing new versions of the play core library back in early 2022.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/06/1.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>The last version of play core library released</em></p>
<p>And from April 2022, they have broken down the original play core library into four separate libraries:</p>
<ul>
<li>Play Assets Delivery Library</li>
<li>Play Feature Delivery Library</li>
<li>Play In-App Reviews Library</li>
<li>Play In-App Updates Library</li>
</ul>
<p>Each library has its own functionality and responsibility.</p>
<p>Since the older core play library only supports up to a certain API level, you need to migrate your application to use the newer libraries that have support for the most recent API levels.</p>
<p>In essence, you need to figure out which functionality of the original core play library you are using and then download the correct part. For example, if you had logic to notify users when a newer version of your application was available, you need to take the Play In-App-Updates library.</p>
<p>We will be presenting two uses cases here:</p>
<ul>
<li>Native Android application</li>
<li>Flutter application</li>
</ul>
<h2 id="heading-use-case-native-android-app">Use Case – Native Android App</h2>
<p>If you have a native Android application, whether it is written in Kotlin or Java, you need to do the following:</p>
<ol>
<li>Open your application level build.gradle file</li>
<li>Most probably you will see under the dependencies block, this line:</li>
</ol>
<pre><code class="lang-groovy">implementation 'com.google.android.play:core-ktx:1.8.1'
</code></pre>
<ol start="3">
<li><p>You will need to remove it and replace it according to what you used in the previous core library</p>
</li>
<li><p>If you need to take the Play In-App-Updates library, then you need to add these to the dependencies block:</p>
</li>
</ol>
<pre><code class="lang-groovy">implementation 'com.google.android.play:app-update:2.1.0'
//Add the dependency below if you are using Kotlin in your application
implementation 'com.google.android.play:app-update-ktx:2.1.0'
</code></pre>
<ol start="5">
<li>Rebuild your application and see that everything works as it should.</li>
</ol>
<p>✋ You might also need to change import statements from <strong>import com.google.android.play.core.tasks.*;</strong> to <strong>import com.google.android.gms.tasks.*;</strong>.</p>
<h2 id="heading-use-case-flutter-application">Use Case – Flutter Application</h2>
<p>Since Flutter is a framework that caters to both Android and iOS, this scenario is a bit different from the one above. If you receive the warning to upgrade the core play library in your Flutter application, you need to have a look at the libraries you are using in your pubspec.yaml file:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">dependencies:</span>
  <span class="hljs-attr">flutter:</span>
    <span class="hljs-attr">sdk:</span> <span class="hljs-string">flutter</span>
  <span class="hljs-string">...</span>
  <span class="hljs-attr">in_app_update:</span> <span class="hljs-string">^3.0.0</span>
</code></pre>
<p>As you can see above, the application depends on the <strong>in_app_update</strong> library, which has to do with notifying users when a newer version of the application is available. When we head over to in_app_update’s pub.dev <a target="_blank" href="https://pub.dev/packages/in_app_update/changelog">changelog page</a>, we can see that:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/06/1-1.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>version 4.1.0 added the required support</em></p>
<p>So we need to update our pubspec.yaml file to use that version (at the very least).</p>
<pre><code class="lang-yaml"><span class="hljs-attr">dependencies:</span>
  <span class="hljs-attr">flutter:</span>
    <span class="hljs-attr">sdk:</span> <span class="hljs-string">flutter</span>
  <span class="hljs-string">...</span>
  <span class="hljs-attr">in_app_update:</span> <span class="hljs-string">^4.1.0</span>
</code></pre>
<p>Run Pub get and you should be good to go.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Work on a Multi-Library Project in Android – Locally and Remotely ]]>
                </title>
                <description>
                    <![CDATA[ In this article, we're going to talk about multi-library projects in Android. It's not something ordinary, but not something out of the ordinary either.  You may have come across multi-library projects in your line of work, or you may be looking into... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/working-on-a-multiple-library-project-in-android/</link>
                <guid isPermaLink="false">66ba50548e44e0cdf1281256</guid>
                
                    <category>
                        <![CDATA[ Android ]]>
                    </category>
                
                    <category>
                        <![CDATA[ android app development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Tomer ]]>
                </dc:creator>
                <pubDate>Sat, 27 Apr 2024 22:37:15 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/04/sandy-millar-5PCeHBkMCmk-unsplash.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In this article, we're going to talk about multi-library projects in Android. It's not something ordinary, but not something out of the ordinary either. </p>
<p>You may have come across multi-library projects in your line of work, or you may be looking into converting your library into sub-modules for better structure and organization. No matter the case, you should be well aware of what lies in front of you before diving in.</p>
<p>Writing your own library in Android is neat. You get a chance to write some code that can help other developers (or even yourself). </p>
<p>Since libraries can’t be a standalone project by themselves, they are usually always paired in a project with an application. This allows developing the library to be a simple process where you add a feature/fix a bug and then you can test it directly with the application you have in the project. Thus, simulating (in a local way) how a developer will integrate your library.</p>
<p>But, what if your library relies on another library you are developing?</p>
<p>If you are not aware of it, you should know that a library (read aar) cannot contain another local library within it. It can rely on libraries remotely (via dependencies), but not on something local. </p>
<p>This is not supported in Android, and while some solutions popped up during the years (<a target="_blank" href="https://github.com/kezong/fat-aar-android">FatAar</a>), these didn’t always solve the problem and are not up to date. There is even a <a target="_blank" href="https://issuetracker.google.com/issues/62121508?pli=1">Google Issue Tracker</a> requesting this feature that has been open for quite some time and is receiving plenty of attention from the community. But let’s identify which walls we can break and which we cannot.</p>
<p>Imagine your project hierarchy looks like this:</p>
<pre><code>-- App
|
 -- OuterLib
   |
    --- InnerLib
</code></pre><p>So, since InnerLib can’t be part of your original project, where can it reside? And also how would you be able to work locally while developing features inside InnerLib?</p>
<p>We are going to answer these questions in this article.</p>
<h2 id="heading-git-submodule">Git Submodule</h2>
<p>For most technical problems, there isn’t always just one solution. Usually, there are more, but each solution has its drawbacks. It's all a question of which drawbacks you are more comfortable living with at the end of the day.</p>
<p>To answer our first question, where can InnerLib reside, we have several options:</p>
<ol>
<li>Make InnerLib a submodule of our original project</li>
<li>Make InnerLib a remote dependency of its own</li>
</ol>
<p>If you are not aware of submodules in Git, <a target="_blank" href="https://git-scm.com/book/en/v2/Git-Tools-Submodules">Git’s documentation</a> is a good place to familiarize yourself with them. Quoting from it (the first paragraph):</p>
<blockquote>
<p>It often happens that while working on one project, you need to use another project from within it. 👉 Perhaps it’s a library that a third party developed or that you’re developing separately and using in multiple parent projects. 👈 A common issue arises in these scenarios: you want to be able to treat the two projects as separate yet still be able to use one from within the other.</p>
</blockquote>
<p>This paragraph shows us that this is exactly our use case. Using a submodule has its benefits. All your code is in one place, is easy to manage, and is easy to develop locally. </p>
<p>But submodules have some weaknesses. One is the fact that you must always be aware of which branch your submodule is pointing to. Imagine a scenario where you are on a release branch in your main repository and your sub-module is on a feature branch. If you don’t notice, you release a version of your code with something that is not ready for production. Whoops.</p>
<p>Now think about this within a team of developers. One careless mistake can be costly.</p>
<p>If the first option sounds problematic for you, then hosting your library in another repository is your second choice. Setting up the repository is pretty simple, but how do you work locally now?</p>
<h2 id="heading-working-locally">Working Locally</h2>
<p>Now that we've gotten our project set up properly, we will probably have a line similar to this in our OuterLib build.gradle file:</p>
<pre><code>dependencies {
  implementation <span class="hljs-string">'url_to_remote_inner_lib_repository'</span>
}
</code></pre><p>How can we make the development cycle efficient and easy to work with? If we develop some feature in InnerLib, how do we test things out in OuterLib? Or in our application?</p>
<p>One solution that might come up is to import our InnerLib locally to our OuterLib project, while having InnerLib .gitignored in our OuterLib project. You can do so easily by right clicking on the name of the project in the left hand side menu in Android Studio and going to New → Module.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/1.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>How to import a module (Step 1)</em></p>
<p>Then in the window that opens up, you can choose the Import option at the bottom left:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/1-1.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>How to import a module (Step 2)</em></p>
<p>That sounds easy and simple so far, but what’s the catch?</p>
<p>Each time you modify a file that belongs to InnerLib, the changes won’t be reflected inside InnerLib since it is ignored. So, each change you want to make has to happen inside of InnerLib and then you have to import it again inside OuterLib to see the changes.</p>
<p>This doesn’t seem right. There must be a better way of doing this.</p>
<p>With just a few lines in our <strong>settings.gradle</strong> file, we can make sure our files stay in sync when we make changes in InnerLib. </p>
<p>When we imported InnerLib into our project, Android Studio made a copy of InnerLib and cached it. That is why we needed to re-import the library for every change we made inside of it. We can tell Android Studio where to reference the files from using the <strong>projectDir</strong> attribute. </p>
<p>Our settings.gradle might look something like this:</p>
<pre><code>include <span class="hljs-string">':outerLib'</span>, <span class="hljs-string">':innerLib'</span>, <span class="hljs-string">':app'</span>
</code></pre><p>To reference our InnerLib locally, we would have to change settings.gradle into this:</p>
<pre><code>include <span class="hljs-string">':outerLib'</span>, <span class="hljs-string">':innerLib'</span>, <span class="hljs-string">':app'</span>
project(<span class="hljs-string">'innerLib'</span>).projectDir = <span class="hljs-keyword">new</span> File(<span class="hljs-string">'PATH_TO_INNER_LIB'</span>)
</code></pre><p>Using this approach, our InnerLib files will be linked to our working directory, so every change we make will be reflected immediately. </p>
<p>But, we would like flexibility when working locally on OuterLib with a remote version of InnerLib. What we wrote above inside the settings.gradle file will only allow us to work locally and surely we don’t want to commit that as it is.</p>
<h2 id="heading-maven-local">Maven Local</h2>
<p>If the approach above doesn’t sit quite right with you, there is a different one you can take. Just like you would publish your library publicly with maven, you can do the same thing locally with maven local. Maven local is a set of repositories that sit locally on your machine.</p>
<p>Below are the paths for mavenLocal depending on the operating system of your machine:</p>
<ul>
<li>Mac → /Users/YOUR_USERNAME/.m2</li>
<li>Linux → /home/YOUR_USERNAME/.m2</li>
<li>Windows → C:\Users\YOUR_USERNAME.m2</li>
</ul>
<p>In essence you can publish your library locally and then link to it in your project. Doing it this way, we can link our project to InnerLib. </p>
<p>In order to allow this configuration in our project, we need to do the following things:</p>
<ol>
<li>Add <strong><em>mavenLocal()</em></strong> as a repository inside our repositories clause. This is to allow our project the ability to search for repositories locally</li>
</ol>
<pre><code>buildscript {
    repositories {
        mavenLocal()
    }
}

...

allprojects { 
    repositories { 
        mavenLocal() 
    }
}
</code></pre><ol start="2">
<li><p>Change our implementation line inside our dependencies clause to reference our InnerLib as if it we are referencing it remotely</p>
</li>
<li><p>To publish InnerLib locally, we will create a file called publishingLocally.gradle that will contain the following:</p>
</li>
</ol>
<pre><code>apply plugin: <span class="hljs-string">'maven-publish'</span> 

project.afterEvaluate {
    publishing { 
      publications {
            library(MavenPublication) { 
                    setGroupId groupId          <span class="hljs-comment">//your library package</span>
                    setArtifactId artifactId              
                    version versionName         <span class="hljs-comment">//I.E. 1.0</span>

                    artifact bundleDebugAar

                    pom.withXml { 
                        def dependenciesNode = asNode().appendNode(<span class="hljs-string">'dependencies'</span>)
                        def dependencyNode = dependenciesNode.appendNode(<span class="hljs-string">'dependency'</span>)
                        dependencyNode.appendNode(<span class="hljs-string">'groupId'</span>, <span class="hljs-string">'your_group_id'</span>)
                        dependencyNode.appendNode(<span class="hljs-string">'artifactId'</span>, <span class="hljs-string">'your_artificat_id'</span>)
                        dependencyNode.appendNode(<span class="hljs-string">'version'</span>, <span class="hljs-string">'your_version'</span>)
                    } 
                }
            }
        }
}
</code></pre><ol start="4">
<li>Inside your application level build.gradle file, add the line:</li>
</ol>
<pre><code>apply <span class="hljs-keyword">from</span>: <span class="hljs-string">'/.publishingLocally.gradle</span>
</code></pre><p>If this option seems a bit too good to be true, <strong>it is</strong>. While on one hand, we can develop things locally seamlessly just as if we were working with a remote library. On the other, if we make any change inside InnerLib while working locally, it is required to publish it locally again. While this isn’t a costly task, it does create a need to perform tedious tasks over and over.</p>
<h2 id="heading-a-solution-for-working-locally-and-remotely">A Solution for Working Locally and Remotely</h2>
<p>We want to avoid the constant need to re-publish our InnerLib package whenever we make a change locally. We need to figure out a way to make our project be aware of those changes. </p>
<p>In the Working Locally section, we found out how to do that, but we had an issue with committing the settings.gradle file. To solve this problem so we can work both locally and remotely with our InnerLib, we will use a parameter we will define in our <strong><em>gradle.properties</em></strong> file.</p>
<p>The gradle.properties file is a place where you can store project level settings that configure your development environment. This helps make sure that all the developers on a team have a consistent development environment. </p>
<p>Some settings you might be familiar with that are found inside this file are AndroidX support (android.useAndroidX=true) or the JVM arguments (org.gradle.jvmargs=-Xmx1536m). </p>
<p>To help us solve our situation, we can add a parameter here to indicate whether we want to work locally or not. Something along the lines of:</p>
<pre><code>workingLocally = <span class="hljs-literal">false</span>
</code></pre><p>This parameter will grant us the ability to distinguish between which settings we are working with, either locally or with production code. First, let’s alter what we have in our settings.gradle file by wrapping it in a condition that checks if our parameter is true:</p>
<pre><code>include <span class="hljs-string">':outerLib'</span>, <span class="hljs-string">':innerLib'</span>, <span class="hljs-string">':app'</span>
<span class="hljs-keyword">if</span> (workingLocally.booleanValue()) {
  project(<span class="hljs-string">'innerLib'</span>).projectDir = <span class="hljs-keyword">new</span> File(<span class="hljs-string">'PATH_TO_INNER_LIB'</span>)
}
</code></pre><p>This way, we indicate to the project to get the files for our InnerLib locally from our machine. </p>
<p>Another place where we need to change our logic is in our build.gradle file. Here, instead of getting the code to our library remotely in our dependencies block, we can indicate whether we are depending on it locally or not.</p>
<pre><code>dependencies {
   <span class="hljs-keyword">if</span> (workingLocally.booleanValue()) {
      implementation <span class="hljs-string">'innerLib'</span>
   } <span class="hljs-keyword">else</span> {
     implementation <span class="hljs-string">'url_to_remote_repository'</span>
  }
}
</code></pre><blockquote>
<p><em>⚠️ Word of warning: You should never commit the gradle.properties file when working locally.</em></p>
</blockquote>
<p>The journey was long and may have seemed quite exhausting. But now we have a full-proof setup for working locally and remotely on a multiple library project.</p>
<p>If you encounter any issues or would like to give your take on this, feel free to leave a comment.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Test Proto DataStore ]]>
                </title>
                <description>
                    <![CDATA[ In a previous article, I described how you can use Proto DataStore in your application. I wrote that article as part of my experience in using Proto DataStore in one of my applications. Following that, I wanted to see what it would be like to write t... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/testing-proto-datastore/</link>
                <guid isPermaLink="false">66ba503f43a51af2a76f7570</guid>
                
                    <category>
                        <![CDATA[ Android ]]>
                    </category>
                
                    <category>
                        <![CDATA[ android app development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Tomer ]]>
                </dc:creator>
                <pubDate>Mon, 08 Apr 2024 20:54:23 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/04/alexander-schimmeck-QX0SWFpB2ho-unsplash.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p><a target="_blank" href="https://medium.com/proandroiddev/android-proto-datastore-should-you-use-it-36ae997d00f2">In a previous article</a>, I described how you can use Proto DataStore in your application. I wrote that article as part of my experience in using Proto DataStore in <a target="_blank" href="https://play.google.com/store/apps/details?id=com.tomerpacific.todo">one of my applications</a>.</p>
<p>Following that, I wanted to see what it would be like to write tests for the Proto DataStore in that application using the knowledge I gained. </p>
<p>Searching online for guidance didn’t provide much relief, so I figured I would share my knowledge for those that might be looking for it. In the worst case, it would be for my progeny.</p>
<p>In my search, I did find <a target="_blank" href="https://medium.com/androiddevelopers/datastore-and-testing-edf7ae8df3d8">this article</a>, but that focuses mostly on testing the Preferences DataStore and not the Proto DataStore. It does state that:</p>
<blockquote>
<p>“However, keep in mind you can use this material for setting up <a target="_blank" href="https://developer.android.com/codelabs/android-proto-datastore#0"><strong>Proto DataStore</strong></a> testing, as it would be very similar to Preferences.”</p>
</blockquote>
<p>But having followed it, I found out that besides the dependencies, there aren’t many similarities here and you need to introduce separate logic to test your own Proto DataStore.</p>
<h2 id="heading-setting-things-up">Setting Things Up</h2>
<p>In your application’s build.gradle file, add the following dependencies:</p>
<pre><code class="lang-groovy">dependencies {
  ///.....
  androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
  debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version"
}
</code></pre>
<p><strong><code>$compose_version</code></strong> is the variable you defined in your project level build.gradle file.</p>
<p>Then, go to your <strong><code>androidTest</code></strong> directory and create a new file. Usually, you would have a repository class that interacts with your Proto DataStore, so you can name this file as YourRepositoryClassNameTest. We will use the name <strong><code>MyRepositoryTest</code></strong>.</p>
<p>Before we delve into testing the Proto DataStore itself, we need to instantiate it. If you go online to find any documentation on this, it is kind of sparse. </p>
<p>Instantiating a Proto DataStore is used with the global Context like so (when not used in a testing scenario):</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> Context.myDataStore: DataStore&lt;MyItem&gt; <span class="hljs-keyword">by</span> dataStore(
                 fileName = DATA_STORE_FILE_NAME,
                 serializer = MyItemSerializer
 )
</code></pre>
<p>Well, you can't do this inside of a test class, because, while you can copy-paste the above code, <strong>you won’t be able to</strong> <strong>access</strong> the DataStore object. You can get the application context like this:</p>
<pre><code class="lang-kotlin">ApplicationProvider.getApplicationContext()
</code></pre>
<p>but our <code>myDataStore</code> object won’t be available through it.</p>
<p>So, what can we do?</p>
<p>In the article linked above, there is an example of how we can create a Preference DataStore using the <a target="_blank" href="https://developer.android.com/reference/kotlin/androidx/datastore/preferences/core/PreferenceDataStoreFactory#create(androidx.datastore.core.handlers.ReplaceFileCorruptionHandler,kotlin.collections.List,kotlinx.coroutines.CoroutineScope,kotlin.Function0)">PreferenceDataStoreFactory.create</a> method.</p>
<pre><code class="lang-kotlin"><span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">create</span><span class="hljs-params">(    
            corruptionHandler: <span class="hljs-type">ReplaceFileCorruptionHandler</span>&lt;<span class="hljs-type">Preferences</span>&gt;? = <span class="hljs-literal">null</span>,    
            migrations: <span class="hljs-type">List</span>&lt;<span class="hljs-type">DataMigration</span>&lt;<span class="hljs-type">Preferences</span>&gt;&gt; = listOf()</span></span>,
            scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob()),    
            produceFile: () -&gt; File): DataStore&lt;Preferences&gt;
</code></pre>
<p>But since we are not using a Preference DataStore, that won’t work for us. What will work is using the <a target="_blank" href="https://developer.android.com/reference/kotlin/androidx/datastore/core/DataStoreFactory#create(androidx.datastore.core.Serializer,androidx.datastore.core.handlers.ReplaceFileCorruptionHandler,kotlin.collections.List,kotlinx.coroutines.CoroutineScope,kotlin.Function0)">DataStoreFactory.create</a> method like this:</p>
<pre><code class="lang-kotlin"><span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-type">&lt;T : Any?&gt;</span> <span class="hljs-title">create</span><span class="hljs-params">(    
    serializer: <span class="hljs-type">Serializer</span>&lt;<span class="hljs-type">T</span>&gt;,   
    corruptionHandler: <span class="hljs-type">ReplaceFileCorruptionHandler</span>&lt;<span class="hljs-type">T</span>&gt;? = <span class="hljs-literal">null</span>,    
    migrations: <span class="hljs-type">List</span>&lt;<span class="hljs-type">DataMigration</span>&lt;<span class="hljs-type">T</span>&gt;&gt; = listOf()</span></span>,    
    scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob()),     produceFile: () -&gt; File): DataStore&lt;T&gt;
</code></pre>
<p>There are several arguments for this method (and some have default values), but we don’t need to pass all of them in. We will be passing:</p>
<ul>
<li>Our serializer class</li>
<li>A lambda method for creating the file for our Proto DataStore</li>
</ul>
<pre><code class="lang-kotlin">dataStore = DataStoreFactory.create(   
        produceFile = {                    
            testContext.dataStoreFile(TEST_DATA_STORE_FILE_NAME)                    },            
        serializer = MyItemSerializer 
      )
</code></pre>
<p>We get the <code>testContext</code> by:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> testContext: Context = ApplicationProvider.getApplicationContext()
</code></pre>
<h2 id="heading-how-to-test-the-datastore">How to Test the DataStore</h2>
<p>Having created our Proto DataStore successfully, we can move on to writing some tests for it. Keep in mind that you have a repository class that receives the instance of the Proto DataStore as a dependency, so after creating the Proto DataStore, we need to create an instance of our repository class.</p>
<pre><code class="lang-kotlin"> <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> repository = MyRepository(datastore)
</code></pre>
<p>First, let’s create a test to check our initial Proto DataStore state. The Proto DataStore itself exposes a flow which we can use.</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@OptIn(ExperimentalCoroutinesApi::class)</span>
    <span class="hljs-meta">@Test</span>
    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">repository_testFetchInitialState</span><span class="hljs-params">()</span></span> {
        runTest {
            testScope.launch {
                <span class="hljs-keyword">val</span> dataStoreObject = repository.myFlow.first()
                <span class="hljs-comment">// Insert here whatever we want to assert from our</span>
                <span class="hljs-comment">// Proto DataStore. I.E. a flag whose initial value is false</span>
                assert(dataStoreObject.myFlag == <span class="hljs-literal">false</span>)  
            }
        }
    }
</code></pre>
<p>☝️ You may have noticed this earlier, but we are using a <strong>OptIn annotation</strong> here. This is because (currently) the APIs which we are using are experimental and must be marked so when we use them.</p>
<p>Since we are accessing the flow of our <code>DataStore</code>, we need to wrap it in our <code>testScope</code>. <code>TestScope</code> is created by doing:</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@OptIn(ExperimentalCoroutinesApi::class)</span>
<span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> dispatcher = TestCoroutineDispatcher()
<span class="hljs-meta">@OptIn(ExperimentalCoroutinesApi::class)</span>
<span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> testScope = TestCoroutineScope(dispatcher)
</code></pre>
<p>Run it and enjoy your first Proto DataStore test.</p>
<p>That was fun for about two seconds.</p>
<p>Let’s do something more meaningful.</p>
<p>Imagine our Proto DataStore has a list of objects inside of it and we want to test the state of it when we add an item to it.</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@OptIn(ExperimentalCoroutinesApi::class)</span>
    <span class="hljs-meta">@Test</span>
    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">repository_testAdditionOfItem</span><span class="hljs-params">()</span></span> {
        runTest {
            testScope.launch {
              <span class="hljs-comment">//1</span>
               <span class="hljs-keyword">val</span> item: MyItem = MyItem.newBuilder().setItemId(UUID.randomUUID().toString())
                    .setItemDescription(TEST_ITEM_DESCRIPTION).build()
                <span class="hljs-comment">//2</span>
                repository.updateItem(item)

                <span class="hljs-comment">//3</span>
                <span class="hljs-keyword">val</span> items = repository.myFlow.first().itemsList
                assert(items.size == <span class="hljs-number">1</span>)

                <span class="hljs-comment">//4</span>
                assert(items[<span class="hljs-number">0</span>].itemDescription.equals(TEST_ITEM_DESCRIPTION))
            }
        }
    }
</code></pre>
<ol>
<li>We create a test item using the exposed API from the protobuff</li>
<li>We add this item to the Proto DataStore using a method we exposed on the MyRepository class</li>
<li>We grab the list of items from the flow the Proto DataStore exposes</li>
<li>We make sure that the item found in the Proto DataStore matches the item we created earlier</li>
</ol>
<h2 id="heading-your-datastore-has-a-leak-in-it">Your DataStore Has a Leak in it</h2>
<p>If you try to run the tests above in one go, you will soon receive an error during runtime:</p>
<blockquote>
<p>There are multiple DataStores active for the same file: /data/user/0/com.example.app/files/datastore/dataStore_filename.pb. You should either maintain your DataStore as a singleton or confirm that there is no two DataStore’s active on the same file (by confirming that the scope is cancelled).</p>
</blockquote>
<p>Well, that is problematic. We only created one DataStore instance in our test class.</p>
<p>What is going on here?</p>
<p>Because we are not using <a target="_blank" href="https://developer.android.com/topic/libraries/architecture/datastore#preferences-create">the property delegate</a> to create our DataStore (meaning Context.datastore), it isn’t ensured that our DataStore object is a singleton whenever we access it.</p>
<p>To circumvent this scenario, I have found out that one approach is to delete and recreate the DataStore for each test case. To delete the DataStore, we can do this:</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@After</span>
<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">cleanup</span><span class="hljs-params">()</span></span> {
  File(testContext.filesDir, <span class="hljs-string">"datastore"</span>).deleteRecursively()
}
</code></pre>
<p>and before every test, we recreate it:</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@Before</span>
 <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">setup</span><span class="hljs-params">()</span></span> {
    dataStore = DataStoreFactory.create(
        produceFile = {
            testContext.dataStoreFile(TEST_DATA_STORE_FILE_NAME)
        },
        serializer = MyItemSerializer
    )
 }
</code></pre>
<p>To see a full example, you can go <a target="_blank" href="https://github.com/TomerPacific/Todo/blob/master/app/src/androidTest/java/com/tomerpacific/todo/TodoItesmRepositoryTest.kt">here</a>.</p>
<p>In this article, I wanted to show the outline of a how a Proto DataStore can be tested. </p>
<p>While I went over two test cases, depending on your DataStore and the types you configured there, there could be more test cases and scenarios to write. The building blocks are there, you just have to adapt it to your needs.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Flutter Tutorial – How to Migrate to V2 Embedding ]]>
                </title>
                <description>
                    <![CDATA[ If you hopped on the Flutter bandwagon in it’s early days, most likely you have a project or two that were created prior to version 1.12 of Flutter. If that is the case, you may have seen this message whenever you run Pub get in one of your projects:... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/flutter-migrating-to-v2-embedding/</link>
                <guid isPermaLink="false">66ba4ff68e44e0cdf1281250</guid>
                
                    <category>
                        <![CDATA[ Android ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Flutter ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Tomer ]]>
                </dc:creator>
                <pubDate>Mon, 29 Jan 2024 18:35:05 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/01/nick-fewings-J54DjpXYJuE-unsplash.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>If you hopped on the Flutter bandwagon in it’s early days, most likely you have a project or two that were created prior to version 1.12 of Flutter. If that is the case, you may have seen this message whenever you run Pub get in one of your projects:</p>
<blockquote>
<p>This app is using a deprecated version of the Android embedding.<br>To avoid unexpected runtime failures, or future build failures, try to migrate this app to the V2 embedding.  </p>
<p>Take a look at the docs for migrating an app: <a target="_blank" href="https://github.com/flutter/flutter/wiki/Upgrading-pre-1.12-Android-projects">https://github.com/flutter/flutter/wiki/Upgrading-pre-1.12-Android-projects</a></p>
</blockquote>
<p>Now, the document itself has the steps you need to follow to make this warning disappear, but it does not always clarify what to change and where. </p>
<p>This article will give you a step by step walkthrough on migrating your Flutter application to V2 Embedding so you can make that warning go away for good.</p>
<h2 id="heading-automatic-migration-the-easy-way-out">Automatic Migration – The Easy Way Out</h2>
<p>It needs to be said that you can forego this process of migration if your application can be easily recreated. So what does that mean? </p>
<p>Well, if the code in your application is not complex, then you can just save the files in your lib folder and create a new project using <strong><code>flutter create</code></strong>. That way, you will have a project that is already migrated to V2 Embedding and will just need to copy paste the code you have in your lib folder.</p>
<p>But, if your project is more complex – let’s say it's a package that has platform specific code – you will probably be better off by migrating it manually.</p>
<h2 id="heading-manual-migration-follow-these-steps">Manual Migration – Follow These Steps</h2>
<ol>
<li>Open the <strong>MainActivity</strong>.kt (or .java) file in your application</li>
<li>You need to remove any content this file has and just leave it bare with a class declaration (unless you have specific logic there).</li>
<li>Remove all of the imports and make sure to have one import that is this:</li>
</ol>
<pre><code class="lang-java"><span class="hljs-keyword">import</span> io.flutter.embedding.android.FlutterActivity;
</code></pre>
<p>The end result should be as follows:</p>
<pre><code class="lang-java"><span class="hljs-keyword">import</span> io.flutter.embedding.android.FlutterActivity;
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MainActivity</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">FlutterActivity</span> </span>{   
    <span class="hljs-comment">// Nothing should be here</span>
}
</code></pre>
<ol start="4">
<li>Open the AndroidManifest.xml file and change the name attribute under the application tag to <strong><code>${applicationName}</code></strong> – so it looks like this: </li>
</ol>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">application</span>      
    <span class="hljs-attr">android:name</span>=<span class="hljs-string">"${applicationName}"</span>&gt;</span> 
     .... 
<span class="hljs-tag">&lt;/<span class="hljs-name">application</span>&gt;</span>
</code></pre>
<ol start="5">
<li>You need to add the following meta data inside your application tags:</li>
</ol>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">meta-data</span>           
     <span class="hljs-attr">android:name</span>=<span class="hljs-string">"flutterEmbedding"</span>       
     <span class="hljs-attr">android:value</span>=<span class="hljs-string">"2"</span> /&gt;</span>
</code></pre>
<ol start="6">
<li>If you want a specific Splash screen behavior, you will need to remove the Splash screen meta tag:</li>
</ol>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">meta-data</span>                     <span class="hljs-attr">android:name</span>=<span class="hljs-string">"io.flutter.app.android.SplashScreenUntilFirstFrame"</span>                <span class="hljs-attr">android:value</span>=<span class="hljs-string">"true"</span> /&gt;</span>
</code></pre>
<ol start="7">
<li>Then head to your styles.xml file and configure the LaunchTheme there with the drawable of your liking:</li>
</ol>
<pre><code class="lang-xml"><span class="hljs-meta">&lt;?xml version="1.0" encoding="utf-8"?&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">resources</span>&gt;</span>    
     <span class="hljs-tag">&lt;<span class="hljs-name">style</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"LaunchTheme"</span> <span class="hljs-attr">parent</span>=<span class="hljs-string">"@android:style/Theme.Black.NoTitleBar"</span>&gt;</span><span class="xml">        <span class="hljs-tag">&lt;<span class="hljs-name">item</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"android:windowBackground"</span>&gt;</span>@drawable/launch_background
        <span class="hljs-tag">&lt;/<span class="hljs-name">item</span>&gt;</span>   
    </span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">resources</span>&gt;</span>
</code></pre>
<p>Your AndroidManifest.xml will look something like this after all the changes above:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">manifest</span> <span class="hljs-attr">xmlns:android</span>=<span class="hljs-string">"http://schemas.android.com/apk/res/android"</span>    <span class="hljs-attr">package</span>=<span class="hljs-string">"PACKAGE_NAME"</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">application</span>        
    <span class="hljs-attr">android:name</span>=<span class="hljs-string">"${applicationName}"</span>    
    <span class="hljs-attr">android:label</span>=<span class="hljs-string">"APPLICATION_LABEL"</span>      
    <span class="hljs-attr">android:icon</span>=<span class="hljs-string">"@mipmap/ic_launcher"</span>&gt;</span>     
        <span class="hljs-tag">&lt;<span class="hljs-name">activity</span>           
            <span class="hljs-attr">android:name</span>=<span class="hljs-string">".MainActivity"</span>
            <span class="hljs-attr">android:exported</span>=<span class="hljs-string">"true"</span>   
            <span class="hljs-attr">android:launchMode</span>=<span class="hljs-string">"singleTop"</span>     <span class="hljs-attr">android:theme</span>=<span class="hljs-string">"@style/LaunchTheme"</span>                <span class="hljs-attr">android:configChanges</span>=<span class="hljs-string">"orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"</span>            <span class="hljs-attr">android:hardwareAccelerated</span>=<span class="hljs-string">"true"</span>            <span class="hljs-attr">android:windowSoftInputMode</span>=<span class="hljs-string">"adjustResize"</span>&gt;</span>     
                <span class="hljs-tag">&lt;<span class="hljs-name">intent-filter</span>&gt;</span>    
                    <span class="hljs-tag">&lt;<span class="hljs-name">action</span> <span class="hljs-attr">android:name</span>=<span class="hljs-string">"android.intent.action.MAIN"</span>/&gt;</span>                                <span class="hljs-tag">&lt;<span class="hljs-name">category</span> <span class="hljs-attr">android:name</span>=<span class="hljs-string">"android.intent.category.LAUNCHER"</span>/&gt;</span>  
                    <span class="hljs-tag">&lt;/<span class="hljs-name">intent-filter</span>&gt;</span>     
       <span class="hljs-tag">&lt;/<span class="hljs-name">activity</span>&gt;</span>     
       <span class="hljs-tag">&lt;<span class="hljs-name">meta-data</span>       
               <span class="hljs-attr">android:name</span>=<span class="hljs-string">"flutterEmbedding"</span>         
            <span class="hljs-attr">android:value</span>=<span class="hljs-string">"2"</span> /&gt;</span> 
 <span class="hljs-tag">&lt;/<span class="hljs-name">application</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">manifest</span>&gt;</span>
</code></pre>
<h2 id="heading-androidx-support">AndroidX Support</h2>
<p>Your project might also be needed to be migrated to use AndroidX libraries instead of the older support libraries. You will be alerted for this when you build and run your application:</p>
<blockquote>
<p>Your app isn’t using AndroidX. To avoid potential build failures, you can quickly migrate your app by following the steps on <a target="_blank" href="https://goo.gl/CP92wY">https://goo.gl/CP92wY</a>.</p>
</blockquote>
<p>Fixing this is rather simple, as Android Studio has built in support for migrating to AndroidX.</p>
<p>Start by opening the Android folder of your Flutter application as a standalone project</p>
<p>Click on Refactor → Migrate to AndroidX:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/migration.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Dropdown menu to Migrate to AndroidX</em></p>
<p>You will then be prompted to save a copy of your project and past that, the migration process will take place.</p>
<h3 id="heading-errors-you-might-see">Errors You Might See</h3>
<p>During this migration process, you might encounter several errors when building your application. The most prominent ones are:</p>
<ul>
<li><em>Unable to get mutable Windows environment variable map</em></li>
<li><em>cvc-complex-type.2.4.a: Invalid content was found starting with element ‘base-extension’. One of ‘{layoutlib}’ is expected</em></li>
<li><em>Warning: This version only understands SDK XML versions up to 2 but an SDK XML file of version 3 was encountered. This can happen if you use versions of Android Studio and the command-line tools that were released at different times</em></li>
</ul>
<p>The first two errors are related to each other and both stem from the same root cause. It is because your project was set up with an old Gradle version and it is needed to upgrade it. </p>
<p>To do so, follow these steps:</p>
<ol>
<li>Open the Android folder in your Flutter application as a standalone project</li>
<li>Click on File → Project Structure:</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/migration-1.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Dropdown menu to select Project Structure</em></p>
<ol start="3">
<li>Change the Gradle version to something more recent and that matches the current Android Studio version you are using</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/migration-2.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>AGP and Gradle settings screen</em></p>
<p>You could also use the AGP Upgrade Assistant to do this as well by going to Tools →AGP Upgrade Assistant:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/migration-3.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Dropdown menu to upgrade AGP using the AGP Upgrade Assistant</em></p>
<p>The third issue, which is a warning, might be caused by having an old version of the Android SDK Tools. To learn how to do that, you can go <a target="_blank" href="https://developer.android.com/studio/intro/update#sdk-manager">here</a>.</p>
<p>Your project should be now fully migrated, compiling and running smoothly. </p>
<p>If you want to read other articles I have written, you can check them out here:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/TomerPacific/MediumArticles">https://github.com/TomerPacific/MediumArticles</a></div>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Android Proto DataStore – Should You Use It? ]]>
                </title>
                <description>
                    <![CDATA[ A few years back, Google announced the DataStore, which is a replacement for the tried and true SharedPreferences.  If you use or have used SharedPreferences in your applications, you might be thinking of making the switch. But as with everything, th... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/android-proto-datastore-should-you-use-it/</link>
                <guid isPermaLink="false">66ba4fdccabb4cb0aaa837f4</guid>
                
                    <category>
                        <![CDATA[ Android ]]>
                    </category>
                
                    <category>
                        <![CDATA[ protocol-buffers ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Tomer ]]>
                </dc:creator>
                <pubDate>Tue, 02 Jan 2024 19:52:43 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/12/niklas-ohlrogge-j-0olYcaihg-unsplash.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>A few years back, Google announced the DataStore, which is a replacement for the tried and true SharedPreferences. </p>
<p>If you use or have used SharedPreferences in your applications, you might be thinking of making the switch. But as with everything, the main question here is: what is going to be the cost in development?</p>
<p><a target="_blank" href="https://developer.android.com/codelabs/android-proto-datastore#3">There are benefits</a> for using DataStore, but only the <strong>Proto DataStore</strong> allows you to save objects while providing type safety. </p>
<p>If you look at the <a target="_blank" href="https://developer.android.com/topic/libraries/architecture/datastore#proto-datastore">documentation</a> for Proto DataStore, you will find that it is a bit outdated and missing some crucial steps when working with it. So that is why, in this article, we are going to go over how to integrate Proto DataStore into your application and show that it’s not that big of a hassle to use it.</p>
<h2 id="heading-what-is-datastore">What is DataStore?</h2>
<p>Jetpack DataStore has two variants:</p>
<ul>
<li>Preferences DataStore</li>
<li>Proto DataStore</li>
</ul>
<p>We won’t be discussing the first, due to it’s similarity to SharedPreferences and also the fact that is has been covered widely. So now let’s understand what the Proto in Proto DataStore means.</p>
<p>Proto is the name Google chose to represent <a target="_blank" href="https://protobuf.dev/">Protocol Buffers</a>. These are (Google’s) mechanism that help you serialize structured data. They are not coding language-specific and in general, you define the type of data that you wish to work with and then code is generated that helps you read and write your data.</p>
<p>✋ We will be using the Proto 3 version in this article.</p>
<p>How does that definition look like?</p>
<pre><code class="lang-proto">message MyItem {
    string itemName = 1;
    int32 itemId = 2;
}
</code></pre>
<p>First, you define an object with the message keyword. Inside it, you list the fields associated with that object. The numbers at the end of each field are used to identify the field itself and <strong>cannot be changed once being set and the object is in use</strong>.</p>
<p>But what if we wanted to have multiple objects in our .proto file? Assuming the objects are related to one another, you can do this simply by adding more message objects:</p>
<pre><code class="lang-proto">message MyItem {
    string itemName = 1;
    int32 itemId = 2;
}

message MyListOfItems {
   repeated MyItem items = 1;
}
</code></pre>
<p>Notice that above we have added another message object that relies on the MyItem object defined above. If you want to define a list of objects, you need to use the <strong>repeated</strong> keyword.</p>
<h2 id="heading-how-to-set-up-proto-datastore">How to Set Up Proto DataStore</h2>
<p>To get started, you'll need to add the following dependencies to your application level build.gradle:</p>
<pre><code class="lang-groovy"> implementation "androidx.datastore:datastore-preferences:1.0.0"
 implementation  "com.google.protobuf:protobuf-javalite:3.18.0"
</code></pre>
<p>Then, you will need to create a proto directory inside your project. This directory needs to be a sibling of the Java folder in your project structure. </p>
<p>Inside of the proto directory, you will be creating a .proto file. This file is responsible for generating the data types you wish to store in Proto DataStore.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/12/1.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Inside the proto directory, create a file with the .proto extension. Our .proto file will hold objects representing a Todo list (what else?). So we will call our file <strong>todo.proto</strong> and it will look like this:</p>
<pre><code class="lang-proto">syntax = "proto3";

option java_package = "com.yourPackageName.todo";
option java_multiple_files = true;

message TodoItem {
  string itemId = 1;
  string itemDescription = 2;
}

message TodoItems {
  repeated TodoItem items = 1;
}
</code></pre>
<p>Notice how we defined two message objects:</p>
<ol>
<li>TodoItem – that defines a todo item</li>
<li>TodoItems – that defines a list of TodoItem objects</li>
</ol>
<p>Next, build the project so that classes will be generated for TodoItem and TodoItems.</p>
<p>After our data objects have been defined, we need to create a class to serialize them. This class will tell the DataStore how to read/write our objects.</p>
<pre><code class="lang-kotlin"><span class="hljs-comment">// 1</span>
<span class="hljs-keyword">object</span> TodoItemSerializer: Serializer&lt;TodoItems&gt; {
   <span class="hljs-comment">// 2</span>
    <span class="hljs-keyword">override</span> <span class="hljs-keyword">val</span> defaultValue: TodoItems = TodoItems.getDefaultInstance()
    <span class="hljs-comment">// 3</span>
    <span class="hljs-keyword">override</span> <span class="hljs-keyword">suspend</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">readFrom</span><span class="hljs-params">(input: <span class="hljs-type">InputStream</span>)</span></span>: TodoItems {
        <span class="hljs-keyword">try</span> {
            <span class="hljs-keyword">return</span> TodoItems.parseFrom(input)
        } <span class="hljs-keyword">catch</span> (exception: InvalidProtocolBufferException) {
            <span class="hljs-keyword">throw</span> CorruptionException(<span class="hljs-string">"Cannot read proto."</span>, exception)
        }
    }
    <span class="hljs-comment">// 3</span>
    <span class="hljs-keyword">override</span> <span class="hljs-keyword">suspend</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">writeTo</span><span class="hljs-params">(
        t: <span class="hljs-type">TodoItems</span>,
        output: <span class="hljs-type">OutputStream</span>
    )</span></span> = t.writeTo(output)
}
</code></pre>
<p>Let’s review what we have in this class:</p>
<ol>
<li>When we declare the class, we need to implement the <strong>Serializer</strong> interface with our object as the type (T)</li>
<li>We define a default value for the serializer in case the file is not created</li>
<li>We override the readFrom/writeTo methods and we make sure to have our object as the data type there</li>
</ol>
<p>We have our .proto file with our data types and our serializer, so the next step is to instantiate the DataStore. We do this by using the property delegate created by dataStore, which requires giving a filename where our data will be saved and our serializer class (which we defined above).</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">private</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">val</span> DATA_STORE_FILE_NAME = <span class="hljs-string">"todo.pb"</span>

<span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> Context.todoItemDatastore: DataStore&lt;TodoItems&gt; <span class="hljs-keyword">by</span> dataStore(
    fileName = DATA_STORE_FILE_NAME,
    serializer = TodoItemSerializer,
)
</code></pre>
<p>This piece of code needs to reside at the top of a class of your choosing above the definition of the class itself. That is:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">private</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">val</span> DATA_STORE_FILE_NAME = <span class="hljs-string">"todo.pb"</span>

<span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> Context.todoItemDatastore: DataStore&lt;TodoItems&gt; <span class="hljs-keyword">by</span> dataStore(
    fileName = DATA_STORE_FILE_NAME,
    serializer = TodoItemSerializer,
)

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">YourClassName</span> </span>{

}
</code></pre>
<p>To access this object in the rest of our application, we will need to use a context. An example is to use the application context in your viewmodel class:</p>
<pre><code class="lang-kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyViewModel</span></span>(application: Application): AndroidViewModel(application) {

   <span class="hljs-keyword">val</span> todoDataStore = application.todoItemDataStore
   <span class="hljs-comment">//...</span>
}
</code></pre>
<h2 id="heading-how-to-use-kotlin-flow">How to Use Kotlin Flow</h2>
<p>Now that we have gone through setting up everything we need for our DataStore, we'll discuss how we are actually going to interact with it. We'll want to read and write data to/from it. But the way we can do so is different from what you may be familiar with from SharedPreferences.</p>
<p>The DataStore we defined above has a data field that exposes a Flow for the properties we defined in our DataStore.</p>
<p>🚰 If you are not familiar with flows, <a target="_blank" href="https://developer.android.com/kotlin/flow">this</a> is a good place to start.</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> todoItemFlow: Flow&lt;TodoItems&gt; = todoItemDataStore.<span class="hljs-keyword">data</span>
        .<span class="hljs-keyword">catch</span> { exception -&gt;
            <span class="hljs-keyword">if</span> (exception <span class="hljs-keyword">is</span> IOException) {
                emit(TodoItems.getDefaultInstance())
            } <span class="hljs-keyword">else</span> {
                <span class="hljs-keyword">throw</span> exception
            }
        }
</code></pre>
<p>The code above shows how you can define a Flow that collects data from the Proto DataStore. A catch block was added in case an exception occurs. You can place this logic in the class where you defined your DataStore and use it like so in your viewmodel:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> todoItemsFlow: LiveData&lt;TodoItems&gt; = todoItemsRepository.todoItemFlow.asLiveData()
</code></pre>
<p>Notice how we converted our Flow to LiveData. We did this for two reasons:</p>
<ol>
<li>Flows can stay active regardless of the activity/fragment that uses them</li>
<li>LiveData is something familiar to many developers, and I wanted to make this example as approachable as possible</li>
</ol>
<p>To be able to do this, you need to add the following dependency to your build.gradle file:</p>
<pre><code class="lang-groovy">implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.6.2"
</code></pre>
<p>In your activity/fragment class, you can observe this live data like so:</p>
<pre><code class="lang-kotlin">myViewModel.todoItemFlow.observe(LocalLifecycleOwner.current) { todoItems -&gt;
                <span class="hljs-comment">// Logic to access data from DataStore</span>
            }
</code></pre>
<h2 id="heading-why-and-when-to-use-datastore">Why and When to Use DataStore</h2>
<p>After everything we reviewed, it’s time to talk about the elephant in the room. Should you go ahead and use DataStore (either Preferences or Proto) in your existing or next project?</p>
<p>In my opinion, the answer should be <strong>Yes</strong>. Besides the fact that Google is moving away from SharedPreferences, DataStore offers plenty of benefits to help you focus on your application and not the persistence of your data. </p>
<p>It’s safe to interact with the DataStore from the UI thread (as it moves work to I/O automatically), and it forces you to use Flow (if you haven’t still) and enjoy all the benefits within. There is also an option to migrate easily from SharedPreferences to Preferences DataStore.</p>
<p>If you are contemplating using Room instead of Proto DataStore, well that depends on your use case. If the amount of data you are going to save (or persist) is rather small and won’t require partial updating, the Proto DataStore is the way to go. If you have a larger data set or one that may be complex, you should opt for using Room instead.</p>
<p>If you want to see how all this code looks like in an application, you can see it here:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/TomerPacific/Todo">https://github.com/TomerPacific/Todo</a></div>
<p>If you want to read other articles I have written, you can see them here:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/TomerPacific/MediumArticles">https://github.com/TomerPacific/MediumArticles</a></div>
<p>Thanks for reading!</p>
<p>References:</p>
<ul>
<li><a target="_blank" href="https://protobuf.dev/programming-guides/proto3/">Protocol Buffers Documentation (proto 3)</a></li>
<li><a target="_blank" href="https://developer.android.com/codelabs/android-proto-datastore#0">Working With Proto DataStore Codelab</a></li>
<li><a target="_blank" href="https://developer.android.com/topic/libraries/architecture/datastore">DataStore Documentation</a></li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
