<?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 app development - 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 app development - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Sat, 23 May 2026 08:50:30 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/android-app-development/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[ 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="1472" height="704" 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="1167" height="281" 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 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[ How to Build and Release a Flutter App in Google Play ]]>
                </title>
                <description>
                    <![CDATA[ In the rapidly evolving world of mobile app development, Flutter has gained immense popularity for its ability to create stunning and high-performance apps across multiple platforms.  Flutter's framework allows developers to build beautiful user inte... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/build-and-release-flutter-app-in-google-play/</link>
                <guid isPermaLink="false">66ba109a9065919bb4e84cac</guid>
                
                    <category>
                        <![CDATA[ android app development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Flutter ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Arunachalam B ]]>
                </dc:creator>
                <pubDate>Wed, 30 Aug 2023 14:23:42 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/08/Build-and-Release-the-Flutter-App-in-Google-Play.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In the rapidly evolving world of mobile app development, Flutter has gained immense popularity for its ability to create stunning and high-performance apps across multiple platforms. </p>
<p>Flutter's framework allows developers to build beautiful user interfaces, deliver native performance, and streamline development. </p>
<p>In this tutorial, I will walk you through building and releasing a Flutter app on the Android platform, from setting up your development environment to distributing your app on the Google Play Store.</p>
<h2 id="heading-prerequisites">Prerequisites:</h2>
<ol>
<li><strong>Install Flutter and Dart:</strong> Make sure you have Flutter and Dart installed on your machine. Follow the <a target="_blank" href="https://docs.flutter.dev/get-started/install">official Flutter installation guide</a> for your platform.</li>
<li><strong>Android Studio:</strong> Download and install Android Studio, which comes with useful tools for Android app development.</li>
<li><strong>Google Play Developer Account:</strong> Create an account on the Google Play Console to publish your app on the Play Store.</li>
</ol>
<h2 id="heading-how-to-develop-your-app">How to Develop your app</h2>
<p>In this guide, I'll be building and releasing a <a target="_blank" href="https://www.freecodecamp.org/news/learn-state-management-in-flutter/">simple todo app</a> I built in one of my earlier blogs.</p>
<p>However, you can follow along with this tutorial and release your own app based on what you build. </p>
<p>As a learner, and if you want to experience the app releasing process, this will be good practice for you.</p>
<p>Run the following command to clone my repo:</p>
<pre><code>git clone https:<span class="hljs-comment">//github.com/5minslearn/Flutter-Todo-App</span>
</code></pre><p>Before proceeding with the app-building process, it's crucial to ensure that our app runs smoothly on a simulator or a physical device, without encountering any errors. </p>
<p>Open the repo in VS Code and press F5 to run the app on your phone / emulator. </p>
<h2 id="heading-how-to-customize-the-default-launcher-icon-in-flutter">How to Customize the Default Launcher Icon in Flutter</h2>
<p>When a new Flutter app is created, it has a default launcher icon. </p>
<p>To customize this icon we need to follow the steps below: </p>
<ol>
<li>You can create your own icon by following this <a target="_blank" href="https://m3.material.io/styles/icons">guideline</a>.</li>
<li>In the <code>[project]/android/app/src/main/res/</code> directory, place your icon files in folders. The default <code>mipmap-</code> folders demonstrate the correct naming convention.</li>
<li>In <code>AndroidManifest.xml, update the application tags</code> <code>android:icon</code> attribute to reference icons from the previous step (for example, <code>&lt;application android:icon="@mipmap/custom_icon" ...</code>).</li>
</ol>
<p>I created an icon named <code>custom_icon.xml</code> with multiple resolutions (<code>mdpi</code> , <code>hdpi</code>, <code>xhdpi</code>, <code>xxhdpi</code>, <code>xxxhdpi</code>):</p>
<pre><code><span class="hljs-number">48</span> × <span class="hljs-number">48</span> (mdpi)
<span class="hljs-number">72</span> × <span class="hljs-number">72</span> (hdpi)
<span class="hljs-number">96</span> × <span class="hljs-number">96</span> (xhdpi)
<span class="hljs-number">144</span> × <span class="hljs-number">144</span> (xxhdpi)
<span class="hljs-number">192</span> × <span class="hljs-number">192</span> (xxxhdpi)
</code></pre><p>I also placed each icon in the respective <code>mipmap-</code> folder. And finally, I mentioned them in the <code>AndroidManifest.xml</code>:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/image-169.png" alt="Image" width="600" height="400" loading="lazy">
_Updating custom<em>icon in AndroidManifest.xml</em></p>
<pre><code>&lt;manifest xmlns:android=<span class="hljs-string">"http://schemas.android.com/apk/res/android"</span> package=<span class="hljs-string">"com.example.todo"</span> &gt;
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">application</span>
        <span class="hljs-attr">android:label</span>=<span class="hljs-string">"todo"</span>
        <span class="hljs-attr">android:name</span>=<span class="hljs-string">"${applicationName}"</span>
        <span class="hljs-attr">android:icon</span>=<span class="hljs-string">"@mipmap/custom_icon"</span>&gt;</span>
        .....
        .....
    <span class="hljs-tag">&lt;/<span class="hljs-name">application</span>&gt;</span></span>
&lt;/manifest&gt;
</code></pre><p>I am utilizing <code>ColorControlNormal</code> in my custom icon file (<code>custom_icon.xml</code>):</p>
<pre><code>&lt;vector xmlns:android=<span class="hljs-string">"http://schemas.android.com/apk/res/android"</span>
    <span class="hljs-attr">android</span>:width=<span class="hljs-string">"48dp"</span>
    <span class="hljs-attr">android</span>:height=<span class="hljs-string">"48dp"</span>
    <span class="hljs-attr">android</span>:viewportWidth=<span class="hljs-string">"960"</span>
    <span class="hljs-attr">android</span>:viewportHeight=<span class="hljs-string">"960"</span>
    <span class="hljs-attr">android</span>:tint=<span class="hljs-string">"?attr/colorControlNormal"</span>&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">path</span>
      <span class="hljs-attr">android:fillColor</span>=<span class="hljs-string">"@android:color/white"</span>
      <span class="hljs-attr">android:pathData</span>=<span class="hljs-string">"M380,....,453.5Q592,463 613,483Z"</span>/&gt;</span></span>
&lt;/vector&gt;
</code></pre><p><strong><code>colorControlNormal</code></strong> is a value that refers to a color attribute that is resolved at runtime, allowing the drawable to match the color theme of the app.</p>
<p>As I'm using an older API version, this function isn't accessible within the older API. </p>
<p>As a solution, I've included the <code>appcompat</code> library in my <code>app/build.gradle</code> file: </p>
<pre><code>...
dependencies {
    ....
    implementation <span class="hljs-string">'androidx.appcompat:appcompat:1.3.1'</span>
    ....
}
....
</code></pre><p><img src="https://www.freecodecamp.org/news/content/images/2023/08/image-146.png" alt="Image" width="600" height="400" loading="lazy">
<em>Implement appcombat library in <code>build.gradle</code></em></p>
<p>That's it. Our configurations are done. Let's try to run the app and look at the icon in the device.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/image-168.png" alt="Image" width="600" height="400" loading="lazy">
<em>App Icon Changed for Todo App</em></p>
<p>The one with the rabbit's face is the one I created. It has been successfully applied to our app. </p>
<p>Without any delay, let's get into building our Android application.</p>
<h2 id="heading-what-is-a-keystore-and-why-do-you-need-it-in-flutter">What is a Keystore and Why DO You Need it in Flutter?</h2>
<p>A keystore in Flutter, specifically for Android, is a secure container used to store cryptographic keys and certificates. </p>
<p>It's important to maintain the security of your app, especially when dealing with sensitive information such as API keys, authentication tokens, and encryption keys. </p>
<p>The keystore ensures that these sensitive assets are stored in a way that makes them difficult to extract from the device.</p>
<h3 id="heading-security">Security</h3>
<p>Storing sensitive information in a Keystore ensures that it is protected from unauthorized access, even if the device is compromised.</p>
<h3 id="heading-compliance">Compliance</h3>
<p>Many regulations require apps to protect sensitive data. A keystore helps you meet compliance standards.</p>
<h3 id="heading-encryption">Encryption</h3>
<p>If your app uses encryption, the keystore provides a secure location for storing encryption keys.</p>
<h3 id="heading-credentials">Credentials</h3>
<p>If your app communicates with APIs or services that require credentials, using a keystore can prevent those credentials from being easily extracted.</p>
<h2 id="heading-how-to-create-a-keystore-in-flutter">How to Create a Keystore in Flutter</h2>
<p>To create a Keystore, you typically use the keytool utility that comes with the Java Development Kit (JDK):</p>
<pre><code>keytool -genkey -v -keystore my-release-key.jks -keyalg RSA -keysize <span class="hljs-number">2048</span> -validity <span class="hljs-number">10000</span> -alias my-key
</code></pre><p><img src="https://www.freecodecamp.org/news/content/images/2023/08/image-170.png" alt="Image" width="600" height="400" loading="lazy">
<em>Sample output for generate a keystore file</em></p>
<h2 id="heading-how-to-generate-apk-or-aab-in-flutter"><strong>How to Generate APK or AAB in Flutter</strong></h2>
<p>APK (Android Package) and AAB (Android App Bundle) are distribution formats used to package and distribute Android applications. </p>
<p>It is recommended to build an app as AAB over APK due to the following benefits:</p>
<ul>
<li>Smaller downloads and faster installations due to optimized APKs</li>
<li>Improved efficiency in resource usage and reduced storage consumption</li>
<li>Dynamic feature delivery support for delivering features on-demand</li>
<li>Enhanced optimization for specific device configurations</li>
</ul>
<pre><code>flutter build appbundle
or
flutter build apk
</code></pre><p><img src="https://www.freecodecamp.org/news/content/images/2023/08/image-171.png" alt="Image" width="600" height="400" loading="lazy">
<em>Sample output to build abb</em></p>
<p>We cannot directly install an Android App Bundle (AAB) file on a mobile device.</p>
<p>An AAB is not an executable package like an APK (Android Package) file that can be directly installed on a device. </p>
<p>Instead, the AAB is a publishing format designed for optimized delivery on the Google Play Store.</p>
<p>When you publish your app on the Google Play Store using an AAB, the Play Store uses it to generate APKs that are tailored to each user's device configuration. </p>
<p>This dynamic generation allows the Play Store to deliver only the necessary resources and code for a specific device, reducing the app's size and improving performance during installation.</p>
<h2 id="heading-how-to-release-a-flutter-app-on-google-play-store">How to Release a Flutter App on Google Play Store</h2>
<p>To release our app on the Google Play Store, we'll require a Google Play developer account. We can create an account by visiting this <a target="_blank" href="https://play.google.com/console">link</a>. </p>
<p>Please be aware that there is a registration fee of at least $25 associated with the process.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/image-150.png" alt="Image" width="600" height="400" loading="lazy">
<em>Google play dashboard to create a new App</em></p>
<p>Click on the Create app button in the <code>All apps</code> section. Enter the app details in the "Create app" form.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/image-149.png" alt="Image" width="600" height="400" loading="lazy">
<em>Creating an app in Google play for release</em></p>
<p>After creating the app, we have to go through multiple tasks to release the application. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/image-151.png" alt="Image" width="600" height="400" loading="lazy">
<em>Steps to testing the app in Goole Play Console</em></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/image-152.png" alt="Image" width="600" height="400" loading="lazy">
<em>Steps to setting up the app in Goole Play Console</em></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/image-153.png" alt="Image" width="600" height="400" loading="lazy">
<em>Steps to release the app in Goole Play Console</em></p>
<p>However, all these steps will be self-explanatory. </p>
<p>At each step, you'll be prompted for information about your application, advertisements, privacy policy, and more. </p>
<p>It's important to note that you can proceed to publish only after completing these steps. </p>
<p>Make sure to mention everything clearly. Especially the questions about data collection. Because, at any time if Google found that your prescribed information is wrong, your app will not be published or will be taken out, if it's published. </p>
<p>After completing these steps, we have to send the app for review. Our app will be published once the review is completed from Google Play team. </p>
<p>It's important to note that there's a higher chance of the app getting rejected if it has security or advertising-related issues. </p>
<p>Therefore, make sure to thoroughly review your app to ensure that it doesn't have any potential problems in data collection. This will contribute to a smoother and more successful app release experience.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Throughout this tutorial, we've covered the process of changing the app icon and building a Flutter app for Android. </p>
<p>While this example provides a foundational understanding of Flutter app development, it's important to note that real-world app complexities may require additional custom configurations in Android. </p>
<p>For further insights, I encourage you to explore the official <a target="_blank" href="https://docs.flutter.dev/deployment/android">documentation</a>, which offers comprehensive guidance on more advanced aspects.</p>
<p>I hope this tutorial has provided you with valuable insights in your Flutter journey.</p>
<p>Thank you for investing your time, and best of luck in your app development endeavors! </p>
<p>If you wish to learn more about Flutter, subscribe to my article by visiting my <a target="_blank" href="https://5minslearn.gogosoon.com/?ref=fcc_flutter_publish_app">site</a> which has a consolidated list of all my blogs.</p>
<p>Cheers! </p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Detecting ANRs in Your Application ]]>
                </title>
                <description>
                    <![CDATA[ If there was a way to make my applications "Application not responding" error proof, I would do it.  ANRs always seem to sneak up on you when you're not expecting them. And the problem is that there's nothing you can do about them.  You might see the... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/detect-application-not-responding-errors-in-your-application/</link>
                <guid isPermaLink="false">66ba4feb8e44e0cdf128124c</guid>
                
                    <category>
                        <![CDATA[ android app development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ error handling ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Tomer ]]>
                </dc:creator>
                <pubDate>Wed, 11 May 2022 15:48:14 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/05/mediamodifier-yx17UuZw1Ck-unsplash.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>If there was a way to make my applications "Application not responding" error proof, I would do it. </p>
<p>ANRs always seem to sneak up on you when you're not expecting them. And the problem is that there's nothing you can do about them. </p>
<p>You might see them pop up when seeing the overview of your application in Google Play Console. But there won’t be a lot of information there to understand how the ANR happened and what you could've done to fix the situation. </p>
<p>And beyond that, if a user happens to experience an ANR, you have to rely on their willingness to invest time and effort to tell you about it. We've all been on the other side, too. If you're using an application that gets stuck, the one thing you do is go straight to uninstalling it.</p>
<p>So, how can we as developers do everything in our power to protect our app's users from experiencing ANRs?</p>
<p>Let’s find out.</p>
<h2 id="heading-how-to-prevent-application-not-responding-errors-on-android">How to Prevent "Application Not Responding" Errors on Android</h2>
<h3 id="heading-be-proactive">Be Proactive</h3>
<p>Before we delve into solutions that can help us detect ANRs, let’s understand what we can do to avoid getting them in the first place (or minimizing the chance they can happen). </p>
<p>These points might sound obvious, but in a large enough application, it might be easy to overlook things:</p>
<p>First, check to see if you have places in your code that are doing extensive work on the UI thread. The work done on the UI thread should be short and relating to something with the UI of your application. </p>
<p>If you are doing any other logic there or perhaps even asynchronous work, delegate it to a background thread or a service</p>
<p>Second, if by any chance you have threads that hold locks or certain blocks of code that need to be synchronized, make sure you are not creating a deadlock or a certain state of your application reaches it</p>
<p>And third, if your application deals with broadcast receivers, you must verify that the execution of the <strong>onReceive</strong> method is short and ends in a timely fashion. If there is work there that can take some time, delegate it to a background thread</p>
<p>Another way that you can detect places that might cause ANRs is by using <a target="_blank" href="https://developer.android.com/reference/android/os/StrictMode"><strong>StrictMode</strong></a>. You can use it while developing your application as it catches accidental disk or network usage on the main thread.</p>
<h3 id="heading-be-smart">Be Smart</h3>
<p>You have gone over your application, and you think it isn’t at risk for any ANRs. So you release it for public consumption. </p>
<p>Lo and behold a couple months go by and then you start seeing reports of ANRs. What could you have done differently? As we discussed earlier, those crash reports hardly provide any information regarding the ANR.</p>
<p>To help your application to give you the highest level of detail that it can when an ANR occurs, I will detail two options:</p>
<ol>
<li>Run a thread which polls the UI thread to see if it is stuck</li>
<li>On API level ≥ 30, you can use <a target="_blank" href="https://developer.android.com/reference/kotlin/android/app/ActivityManager#gethistoricalprocessexitreasons"><strong>getHistoricalProcessExitReasons</strong></a></li>
</ol>
<p>Let's look at each one in detail now:</p>
<h4 id="heading-run-a-thread-that-polls-the-ui-thread">Run a thread that polls the UI thread</h4>
<p>There is already a library called <a target="_blank" href="https://github.com/SalomonBrys/ANR-WatchDog">ANR-Watchdog</a> which takes care of detecting ANRs and provides you with all the details. In case you don’t want to use it or want to have something of your own, here is a rough outline of what it does:</p>
<ul>
<li>Create a thread which runs on the main thread (it doesn’t have to do any actual work)</li>
<li>See if the thread’s execution completes after a few seconds</li>
<li>If it did, then no ANR took place and you run the thread again</li>
<li>If it did not, some other thread is blocking the main thread and causing an ANR</li>
</ul>
<p>Below is a rough outline of such a class:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">package</span> com.tomerpacific.anrdetection

<span class="hljs-keyword">import</span> android.os.Handler
<span class="hljs-keyword">import</span> android.os.Looper
<span class="hljs-keyword">import</span> java.lang.Exception

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

    <span class="hljs-keyword">val</span> TIMEOUT: <span class="hljs-built_in">Long</span> = <span class="hljs-number">5000L</span>
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> handler: Handler = Handler(Looper.getMainLooper())
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> worker : Runnable = Runnable {  }

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">run</span><span class="hljs-params">()</span></span> {
        <span class="hljs-keyword">while</span> (!isInterrupted) {
            handler.postAtFrontOfQueue(worker)

            <span class="hljs-keyword">try</span> {
                sleep(TIMEOUT)
            } <span class="hljs-keyword">catch</span> (exception: Exception) {
                exception.printStackTrace()
            }

            <span class="hljs-keyword">if</span> (handler.hasMessages(<span class="hljs-number">0</span>)) {
                <span class="hljs-comment">//worker has not finished running so the UI thread is being held</span>
                <span class="hljs-keyword">val</span> stackTrace: Array&lt;StackTraceElement&gt; = currentThread().stackTrace
                <span class="hljs-keyword">var</span> output: String = <span class="hljs-string">""</span>
                <span class="hljs-keyword">for</span> (element <span class="hljs-keyword">in</span> stackTrace) {
                    output += element.className + <span class="hljs-string">" "</span> + element.methodName + <span class="hljs-string">" "</span> + element.lineNumber
                }

                print(output)
            }
        }
    }
}
</code></pre>
<p>⚠️ The execution of the runnable is always on the main thread, but since it doesn’t do any work, it is not supposed to impact your application’s performance. You could also decide to run it every desired time interval.</p>
<h4 id="heading-use-gethistoricalprocessexitreasonshttpsdeveloperandroidcomreferencekotlinandroidappactivitymanagergethistoricalprocessexitreasons">use <a target="_blank" href="https://developer.android.com/reference/kotlin/android/app/ActivityManager#gethistoricalprocessexitreasons">getHistoricalProcessExitReasons</a></h4>
<p>Option #2 can make your life simpler, as its API gives you a lot of information.</p>
<p>Introduced in Android 11 (API level 30), getHistoricalProcessExitReasons does exactly what you think it does. It returns a list of recorded objects that account for the most recent application terminations. </p>
<p>This method is called on the ActivityManager and accepts three arguments:</p>
<ol>
<li>The package name – of String type (can be null)</li>
<li>The process’s id that belonged to the package – of int type</li>
<li>The maximum number of reasons you want to get back – of int type</li>
</ol>
<p>It’s important to note that all of these arguments can be substituted with default values. That is, you can pass null as the package name and get the entire exit reasons for the caller’s UID</p>
<p>So what do these recorded objects contain? Well, these objects are of <a target="_blank" href="https://developer.android.com/reference/kotlin/android/app/ApplicationExitInfo"><strong>ApplicationExitInfo</strong></a> type and they can provide you with a lot of useful information. </p>
<p>For starters, you could call the <strong>getReason</strong> method to find out why the process terminated. This method returns an integer marking the code for the exit reason. If the value returned is <strong>6</strong>, that means the application was terminated because it was unresponsive due to the fact that an <a target="_blank" href="https://developer.android.com/reference/kotlin/android/app/ApplicationExitInfo#reason_anr">ANR</a> happened.</p>
<p><div class="embed-wrapper"><iframe src="https://giphy.com/embed/917Ve5cLpoB3Nhd1xh" width="480" height="400" class="giphy-embed" title="Embedded content" loading="lazy"></iframe></div></p><p><a href="https://giphy.com/gifs/theoffice-nbc-the-office-tv-917Ve5cLpoB3Nhd1xh">via GIPHY</a></p><p></p>
<p>That’s neat, but how can we see where the ANR happened? For that we can use <a target="_blank" href="https://developer.android.com/reference/kotlin/android/app/ApplicationExitInfo#gettraceinputstream"><strong>getTraceInputStream</strong></a>. Like the name implies, the returned value is an InputStream of bytes which needs to be read like any other InputStream. </p>
<p>An example output looks like the following:</p>
<pre><code class="lang-log">I/System.out: ----- pid 2738 at 2022-04-26 17:48:12 -----
    Cmd line: com.tomerpacific.anrdetection
    Build fingerprint: 'Android/sdk_phone_x86/generic_x86:11/RSR1.210210.001.A1/7193139:userdebug/dev-keys'
    ABI: 'x86'
    Build type: optimized
I/System.out: Zygote loaded classes=15746 post zygote classes=728
    Dumping registered class loaders
    #0 dalvik.system.PathClassLoader: [], parent #1
    #1 java.lang.BootClassLoader: [], no parent
I/System.out: #2 dalvik.system.PathClassLoader: [/data/app/~~C_0mqw-g_9cjPjIR_kpRIg==/com.tomerpacific.anrdetection-3-t-I6JR9Q3QA6UY7L8iPA==/base.apk], parent #1
    Done dumping class loaders
    Classes initialized: 302 in 19.361ms
    Intern table: 31490 strong; 543 weak
    JNI: CheckJNI is on; globals=637 (plus 31 weak)
I/System.out: Libraries: libandroid.so libaudioeffect_jni.so libcompiler_rt.so libicu_jni.so libjavacore.so libjavacrypto.so libjnigraphics.so libmedia_jni.so libopenjdk.so librs_jni.so libsfplugin_ccodec.so libsoundpool.so libstats_jni.so libwebviewchromium_loader.so (14)
    Heap: 91% free, 2330KB/26MB; 67022 objects
    Dumping cumulative Gc timings
I/System.out: Average major GC reclaim bytes ratio inf over 0 GC cycles
    Average major GC copied live bytes ratio 0.738176 over 4 major GCs
    Cumulative bytes moved 11482280
    Cumulative objects moved 217937
    Peak regions allocated 28 (7168KB) / 768 (192MB)
I/System.out: Start Dumping histograms for 1 iterations for young concurrent copying
    ProcessMarkStack:    Sum: 26.311ms 99% C.I. 26.311ms-26.311ms Avg: 26.311ms Max: 26.311ms
    ScanImmuneSpaces:    Sum: 5.625ms 99% C.I. 5.625ms-5.625ms Avg: 5.625ms Max: 5.625ms
    VisitConcurrentRoots:    Sum: 1.121ms 99% C.I. 1.121ms-1.121ms Avg: 1.121ms Max: 1.121ms
I/System.out: (Paused)ClearCards:    Sum: 375us 99% C.I. 7us-235us Avg: 28.846us Max: 235us
    GrayAllDirtyImmuneObjects:    Sum: 329us 99% C.I. 329us-329us Avg: 329us Max: 329us
    VisitNonThreadRoots:    Sum: 327us 99% C.I. 327us-327us Avg: 327us Max: 327us
I/System.out: InitializePhase:    Sum: 306us 99% C.I. 306us-306us Avg: 306us Max: 306us
    (Paused)GrayAllNewlyDirtyImmuneObjects:    Sum: 164us 99% C.I. 164us-164us Avg: 164us Max: 164us
    (Paused)FlipCallback:    Sum: 144us 99% C.I. 144us-144us Avg: 144us Max: 144us
    SweepSystemWeaks:    Sum: 142us 99% C.I. 142us-142us Avg: 142us Max: 142us
I/System.out: ScanCardsForSpace:    Sum: 125us 99% C.I. 125us-125us Avg: 125us Max: 125us
    ThreadListFlip:    Sum: 96us 99% C.I. 96us-96us Avg: 96us Max: 96us
    ClearFromSpace:    Sum: 78us 99% C.I. 78us-78us Avg: 78us Max: 78us
    CopyingPhase:    Sum: 76us 99% C.I. 76us-76us Avg: 76us Max: 76us
I/System.out: FlipOtherThreads:    Sum: 58us 99% C.I. 58us-58us Avg: 58us Max: 58us
    ProcessReferences:    Sum: 54us 99% C.I. 19us-35us Avg: 27us Max: 35us
    SweepArray:    Sum: 53us 99% C.I. 53us-53us Avg: 53us Max: 53us
I/System.out: EnqueueFinalizerReferences:    Sum: 38us 99% C.I. 38us-38us Avg: 38us Max: 38us
    RecordFree:    Sum: 37us 99% C.I. 14us-23us Avg: 18.500us Max: 23us
    ForwardSoftReferences:    Sum: 25us 99% C.I. 25us-25us Avg: 25us Max: 25us
    FlipThreadRoots:    Sum: 21us 99% C.I. 21us-21us Avg: 21us Max: 21us
I/System.out: (Paused)SetFromSpace:    Sum: 19us 99% C.I. 19us-19us Avg: 19us Max: 19us
    ResumeRunnableThreads:    Sum: 12us 99% C.I. 12us-12us Avg: 12us Max: 12us
    EmptyRBMarkBitStack:    Sum: 8us 99% C.I. 8us-8us Avg: 8us Max: 8us
    SwapBitmaps:    Sum: 7us 99% C.I. 7us-7us Avg: 7us Max: 7us
    Done Dumping histograms
    young concurrent copying paused:    Sum: 750us 99% C.I. 750us-750us Avg: 750us Max: 750us
I/System.out: young concurrent copying freed-bytes: Avg: 1052KB Max: 1052KB Min: 1052KB
    Freed-bytes histogram: 960:1
    young concurrent copying total time: 35.641ms mean time: 35.641ms
    young concurrent copying freed: 8956 objects with total size 1052KB
I/System.out: young concurrent copying throughput: 255886/s / 29MB/s  per cpu-time: 179578666/s / 171MB/s
    Average minor GC reclaim bytes ratio 0.742269 over 1 GC cycles
    Average minor GC copied live bytes ratio 0.276211 over 2 minor GCs
    Cumulative bytes moved 1410368
    Cumulative objects moved 26626
I/System.out: Peak regions allocated 28 (7168KB) / 768 (192MB)
    Total time spent in GC: 35.641ms
    Mean GC size throughput: 28MB/s per cpu-time: 169MB/s
    Mean GC object throughput: 251284 objects/s
    Total number of allocations 75978
    Total bytes allocated 3382KB
    Total bytes freed 1052KB
I/System.out: Free memory 23MB
    Free memory until GC 23MB
    Free memory until OOME 189MB
    Total memory 26MB
    Max memory 192MB
    Zygote space size 3040KB
    Total mutator paused time: 750us
I/System.out: Total time waiting for GC to complete: 80.600us
    Total GC count: 1
    Total GC time: 35.641ms
    Total blocking GC count: 0
    Total blocking GC time: 0
    Histogram of GC count per 10000 ms: 0:1
    Histogram of blocking GC count per 10000 ms: 0:1
    Native bytes total: 15621964 registered: 98204
I/System.out: Total native bytes at last GC: 15537168
    /system/framework/oat/x86/android.hidl.manager-V1.0-java.odex: quicken
    /system/framework/oat/x86/android.test.base.odex: quicken
    /system/framework/oat/x86/android.hidl.base-V1.0-java.odex: quicken
I/System.out: Current JIT code cache size (used / resident): 0KB / 32KB
    Current JIT data cache size (used / resident): 4KB / 32KB
    Zygote JIT code cache size (at point of fork): 45KB / 48KB
    Zygote JIT data cache size (at point of fork): 33KB / 36KB
    Current JIT mini-debug-info size: 26KB
I/System.out: Current JIT capacity: 64KB
    Current number of JIT JNI stub entries: 0
    Current number of JIT code cache entries: 48
    Total number of JIT compilations: 6
    Total number of JIT compilations for on stack replacement: 1
I/System.out: Total number of JIT code cache collections: 0
    Memory used for stack maps: Avg: 35B Max: 52B Min: 28B
    Memory used for compiled code: Avg: 125B Max: 257B Min: 69B
    Memory used for profiling info: Avg: 70B Max: 188B Min: 20B
    Start Dumping histograms for 48 iterations for JIT timings
    Compiling:    Sum: 385.780ms 99% C.I. 0.556ms-25.610ms Avg: 8.037ms Max: 25.610ms
I/System.out: TrimMaps:    Sum: 44.431ms 99% C.I. 2.400us-5148us Avg: 925.645us Max: 5643us
    Done Dumping histograms
    Memory used for compilation: Avg: 83KB Max: 322KB Min: 8560B
    ProfileSaver total_bytes_written=0
    ProfileSaver total_number_of_writes=0
    ProfileSaver total_number_of_code_cache_queries=0
I/System.out: ProfileSaver total_number_of_skipped_writes=0
    ProfileSaver total_number_of_failed_writes=0
    ProfileSaver total_ms_of_sleep=5000
    ProfileSaver total_ms_of_work=0
I/System.out: ProfileSaver total_number_of_hot_spikes=5
    ProfileSaver total_number_of_wake_ups=0
I/System.out: suspend all histogram:    Sum: 11.468ms 99% C.I. 0.018ms-10.658ms Avg: 1.042ms Max: 11.094ms
    DALVIK THREADS (15):
    "main" prio=5 tid=1 Runnable
      | group="main" sCount=0 dsCount=0 flags=0 obj=0x72107008 self=0xe7d05410
      | sysTid=2738 nice=-10 cgrp=top-app sched=0/0 handle=0xf6267478
I/System.out:   | state=R schedstat=( 5812106631 1041760011 2536 ) utm=535 stm=45 core=0 HZ=100
      | stack=0xff7cb000-0xff7cd000 stackSize=8192KB
      | held mutexes= "mutator lock"(shared held)
        at com.tomerpacific.anrdetection.MainActivity$onCreate$1$1.onClick(MainActivity.kt:18)
        at android.view.View.performClick(View.java:7448)
        at android.view.View.performClickInternal(View.java:7425)
I/System.out:     at android.view.View.access$3600(View.java:810)
        at android.view.View$PerformClick.run(View.java:28305)
I/System.out:     at android.os.Handler.handleCallback(Handler.java:938)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:223)
        at android.app.ActivityThread.main(ActivityThread.java:7656)
I/System.out:     at java.lang.reflect.Method.invoke(Native method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
</code></pre>
<p>This is only a partial snippet of all the output, but you can see that it provides a ton of information, including:</p>
<ul>
<li>Free memory/Total memory/Max memory</li>
<li>Heap diagnostic (percentage free, size and amount of objects allocated)</li>
<li>The stacktrace of the main thread</li>
</ul>
<p>Other useful methods include:</p>
<ul>
<li>getTimestamp – the timestamp of when the process terminated</li>
<li>getDescription – a system description of why the process terminated</li>
</ul>
<h3 id="heading-be-resourceful">Be Resourceful</h3>
<p>If your application does suffer from ANRs, solving them can be quite tricky. This can be because you're not getting a complete stacktrace (or not having one at all), you're not able to reproduce it, or it's happening on some esoteric device. Well, what can you do?</p>
<p>In Android Studio version ≥ 3.2, you have a utility called <a target="_blank" href="https://developer.android.com/studio/profile/cpu-profiler">CPU Profiler</a>. This tool lets you inspect your thread activity during the runtime of your application. With it, you might find out which threads are running, for how long, and where they are running.</p>
<p>To use it, inside Android Studio, go to View → Tool Window → Profiler:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/05/1_RklpDywn-s_a7seW8_wcaw.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>A window will open at the bottom of the screen. Once you attach a process to it, you will see three timelines:</p>
<ol>
<li>Event timeline</li>
<li>CPU timeline</li>
<li>Thread timeline</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/05/1__aG-Tlaaa7ZWpLbuQnLlqQ.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>You want to focus on the Thread timeline to see if anything is out of the ordinary there. Each thread’s activity can be identified by three colors:</p>
<ul>
<li>Green – indicates that the thread is running or in a runnable state</li>
<li>Yellow – indicates that the thread is waiting for the execution of some I/O operation</li>
<li>Gray – indicates the thread is sleeping</li>
</ul>
<h2 id="heading-wrapping-up">Wrapping Up</h2>
<p>Hopefully you have gained some confidence in making your applications as ANR-proof as you can. Using the tools and techniques listed above may help prevent your application’s next ANR.</p>
<p>You are welcome to check out some of my other articles on GitHub:</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[ How to Add Swipe Animations to a CardView in an Android App ]]>
                </title>
                <description>
                    <![CDATA[ By Gourav Khunger If you're building an Android app, you should consider adding animations. They can improve your app's user experience and increase retention.  These days, if you see an app that has no animation, it can feel odd and out-dated. And s... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/add-swipe-animations-to-a-card-view-in-android-app/</link>
                <guid isPermaLink="false">66d45edb052ad259f07e4ad2</guid>
                
                    <category>
                        <![CDATA[ android app development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ androiddev ]]>
                    </category>
                
                    <category>
                        <![CDATA[ animation ]]>
                    </category>
                
                    <category>
                        <![CDATA[ animations ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Kotlin ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Mon, 15 Nov 2021 16:09:53 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2021/11/Swiping-Animation-Android-Views.gif" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Gourav Khunger</p>
<p>If you're building an Android app, you should consider adding animations. They can improve your app's user experience and increase retention. </p>
<p>These days, if you see an app that has no animation, it can feel odd and out-dated. And since interactive experiences are kind of the new norm, you'll want to figure out ways to set your app apart.</p>
<h2 id="heading-what-well-build-here">What We'll Build Here</h2>
<p>Now, it might seem difficult to make your app stand out if you just have something basic like a quote sharing app (which is what we are going to work on here). It can be hard to hook the user and keep them interested.</p>
<p>Of course, you could just add two simple buttons to load the next/previous quote and call it a day. But that's pretty basic and any app could do that! Even if you're just building a simple side-project, there's no trade-off for good UX :)</p>
<p>So what we'll do in this tutorial is drop the buttons, and instead have logic where a user can swipe the card to the left. When they've swiped far enough, the app will load a new card with a new quote.</p>
<p>By the end of this post, you will learn how to make a really smooth animated card which a user can swipe that can perform whatever action you choose. Here's a demo of how it works:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/11/iHxFjvI4x.gif" alt="Animation showing smooth slide to refresh swiping animation on a android cardview." width="600" height="400" loading="lazy"></p>
<p>Amazing, right? Let's get into it!</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>For this tutorial, we will use Kotlin as the programming language for our app – but you can easily translate the code to Java and it would work the same.</p>
<p>For reference, this is the quote card that we wish to enable the swipe feature on.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/11/9CVHyoJfV.png" alt="Android card view with a quote." width="600" height="400" loading="lazy"></p>
<p>It is an androidX <code>CardView</code> with a bunch of <code>TextView</code>s and an <code>ImageView</code>. There's also a <code>ProgressBar</code> that gets shown while loading a new quote.</p>
<p>We won't be making the XML code for the user interface. You can <a target="_blank" href="https://github.com/gouravkhunger/QuotesApp/blob/main/app/src/main/res/layout/fragment_quote.xml">get the layout</a> I used here from the GitHub repository, or build your own.</p>
<p><a target="_blank" href="https://github.com/gouravkhunger/QuotesApp">Here's the complete code</a> for our Quotes app, if you wish to check it out. It uses the MVVM design pattern, but this article doesn't rely on what pattern you use for the business logic of your app, as we'll just be working on the UI part.</p>
<p>Now, we're ready to make that awesome swipe interface!</p>
<h2 id="heading-how-to-handle-swipes-in-our-app">How to Handle Swipes in Our App</h2>
<p>To handle swipes, we first need to set a touch listener on the card. Each time an action is performed on the card, the touch listener is called. Within the listener, we will add the logic to do the math and perform the animations.</p>
<p>Here is the blueprint of the touch listener we will be using:</p>
<pre><code class="lang-kotlin">quoteCard.setOnTouchListener(
    View.OnTouchListener { view, event -&gt;
        <span class="hljs-keyword">when</span> (event.action) {
            MotionEvent.ACTION_MOVE -&gt; {
                <span class="hljs-comment">// <span class="hljs-doctag">TODO:</span> Handle ACTION_MOVE</span>
            }
            MotionEvent.ACTION_UP -&gt; {
                <span class="hljs-comment">// <span class="hljs-doctag">TODO:</span> Handle ACTION_UP</span>
            }
        }

        <span class="hljs-comment">// required to by-pass lint warning</span>
        view.performClick()
        <span class="hljs-keyword">return</span><span class="hljs-symbol">@OnTouchListener</span> <span class="hljs-literal">true</span>
    }
)
</code></pre>
<p>Here, we are specifically listening for 2 actions on the card – the <code>ACTION_MOVE</code> and the <code>ACTION_UP</code>.</p>
<ul>
<li>The <code>ACTION_MOVE</code> event is called when a user starts swiping the card, that is, moving it.</li>
<li>The <code>ACTION_UP</code> is called when a user lifts their finger from the card, basically, when they release it.</li>
</ul>
<p>There are many other action events that we can override, such as <code>ACTION_DOWN</code> that's called when a person gets hold of the view, but we don't need them for this feature.</p>
<p>The basic setup for the card is done, so let's figure out the swiping logic.</p>
<h3 id="heading-the-math-behind-the-swipe-action">The math behind the swipe action</h3>
<p>First, let's re-think <strong>what we want to achieve</strong>. Implementing functionality is easier when you know exactly what you wish to have. Your code will also make more sense when your requirements are clear.</p>
<p>Here, we have a quote card. We want users to be able to swipe it only to the left, and if the minimum threshold to load a new quote is reached, it should move back to its original position and load a new quote.</p>
<p>Now, to achieve this, let's think of it in terms of the card. Let's define the mean position as the center of the card.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/11/dEnpWr7e4.png" alt="Mean Position of Quote Card" width="600" height="400" loading="lazy">
<em>Mean Position of the Card</em></p>
<p>We want the card to swipe if and only if the user swipes it to the left of the mean position.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/11/7epeWn53S.gif" alt="Animation illustrating user swipes to the left of the Quote Card" width="600" height="400" loading="lazy">
<em>Swipe only if moved towards the left of mean position</em></p>
<p>So how can we make this happen? </p>
<p>You guessed it – we will calculate the mean position and on the <code>ACTION_MOVE</code> event, we will check if the user swiped to the left and move the card accordingly.</p>
<h3 id="heading-how-to-implement-the-swipe-logic">How to implement the swipe logic</h3>
<p>To implement the logic, we first need to have the starting position of the card, which is fairly easy to calculate. We will just make sure that it is calculated with respect to the full-screen width, not just the card's width.</p>
<p>Place these lines of code before the <code>when(event.action)</code> statement:</p>
<pre><code class="lang-kotlin">quoteCard.setOnTouchListener(
    View.OnTouchListener { view, event -&gt;

        <span class="hljs-comment">// variables to store current configuration of quote card.</span>
        <span class="hljs-keyword">val</span> displayMetrics = resources.displayMetrics
        <span class="hljs-keyword">val</span> cardWidth = quoteCard.width
        <span class="hljs-keyword">val</span> cardStart = (displayMetrics.widthPixels.toFloat() / <span class="hljs-number">2</span>) - (cardWidth / <span class="hljs-number">2</span>)

        <span class="hljs-keyword">when</span> (event.action) {
            ...
        }
        ...
    }
)
</code></pre>
<p>First we get the <code>displayMetrics</code> from resources, which will give us the width of the screen using <code>displayMetrics.widthPixels.toFloat()</code>.</p>
<p>Then we get the <code>cardWidth</code> using the <code>width</code> property of the <code>quoteCard</code>.</p>
<p>Finally, we calculate the starting position of the card using the formula <code>(width of screen/2) - (cardWidth/2)</code>. Essentially, this gives us the x-coordinate of this position of the card:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/11/NiH3lsseM.gif" alt="Animation highlighting starting position of Quote Card" width="600" height="400" loading="lazy">
<em>Starting position of card.</em></p>
<p>Now, let's implement the code for the <code>ACTION_MOVE</code> event.</p>
<h3 id="heading-how-to-handle-the-actionmove-event">How to handle the <code>ACTION_MOVE</code> event</h3>
<p>Inside the <code>ACTION_MOVE</code> block, we first initialise the <code>newX</code> variable that holds the new x-coordinate that the card has been swiped to.</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> newX = event.rawX
</code></pre>
<p><code>event.rawX</code> gives us the absolute value of the new coordinate with respect to the screen width.</p>
<p><code>newX</code> will contain the x-coordinate where the user's finger is, at any given moment. The value <code>0.0</code> for <code>newX</code> means that the user swiped to the left-most part of the screen. And for my emulator, <code>1080.0</code> represents the right-most edge of the screen.</p>
<p>Since, we want the card to swipe only if <code>newX</code> is less than the mean position of the card, we will place an if-condition here to verify that this is the case.</p>
<p>Think of this with simple values. Let's suppose that the mean position of the card is at x-coordinate <code>540.0</code> (small x-coordinate) and the user swipes to <code>710.0</code> (bigger x-coordinate). But we don't want them to be able to swipe to the right. And if the user swipes to <code>320.0</code> (smaller x-coordinate), then we need to carry out the swipe and move the card to the new position.</p>
<p>Here's the code to implement the above logic:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">if</span> (newX - cardWidth &lt; cardStart) { <span class="hljs-comment">// or newX &lt; cardStart + cardWidth</span>
    quoteCard.animate().x(
        min(cardStart, newX - (cardWidth / <span class="hljs-number">2</span>))
    )
    .setDuration(<span class="hljs-number">0</span>)
    .start()
}
</code></pre>
<p>We subtract <code>cardWidth</code> from <code>newX</code> because <code>newX</code> is an absolute value which is not relative to the card. It has a higher value because <code>cardStart</code> is towards the start of the screen, and <code>newX</code> is initially somewhere in the middle (a user would generally swipe from the middle).</p>
<p>We want to compare the value of <strong>shift</strong> in the x-coordinate and median to the value of <code>cardStart</code>, not the value of <strong><code>newX</code></strong>, so we take this into account by subtracting <code>cardWidth</code>.</p>
<p>Then, we carry out the animation using <code>quoteCard.animate()</code> and we change its x coordinate using the <code>x()</code> function.</p>
<p>Now, why do we do <code>min(cardStart, newX - (cardWidth/2))</code>?</p>
<p>This is very interesting and intuitive to understand. From the beginning, we are emphasizing that the card should move only to the left and not to the right. </p>
<p><code>newX - (cardWidth/2))</code> is nothing but the swiped distance towards the left (so subtraction is involved – for the right side, it should be added).</p>
<p>The <code>min()</code> function here returns the minimum of the two values provided. If the swiped distance is less than the <code>cardStart</code>, it is returned, otherwise <code>cardStart</code> is used. This is the condition we want to meet and <code>min()</code> makes it really easy to handle.</p>
<p><code>setDuration(0)</code> ensures that the animation is carried instantaneously (which keeps swiping from feeling laggy). <code>start()</code> actually starts the animation with the given properties.</p>
<p>This animation will clear any doubt your have on how this works:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/11/other.gif" alt="Image" width="600" height="400" loading="lazy">
<em>Visualisation of the aforementioned concept</em></p>
<p>(I don't have expertise on making animations, this was the best I could come up with.)</p>
<p>Here is the final code for the <code>ACTION_MOVE</code> event:</p>
<pre><code class="lang-kotlin">MotionEvent.ACTION_MOVE -&gt; {
    <span class="hljs-comment">// get the new coordinate of the event on X-axis</span>
    <span class="hljs-keyword">val</span> newX = event.rawX

    <span class="hljs-comment">// carry out swipe only if newX - cardWidth &lt; cardStart, that is</span>
    <span class="hljs-comment">// the card is swiped to the left side, not to the right</span>
    <span class="hljs-keyword">if</span> (newX - cardWidth &lt; cardStart) {
        quoteCard.animate()
            .x(
                min(cardStart, newX - (cardWidth / <span class="hljs-number">2</span>))
            )
        .setDuration(<span class="hljs-number">0</span>)
        .start()
    }
}
</code></pre>
<p>You can also include a <code>TextView</code> to the UI that reflects when the user should release the card. Place this code inside the above <code>if</code> statement too:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">if</span> (quoteCard.x &lt; MIN_SWIPE_DISTANCE) textView.text = getString(R.string.releaseCard)
<span class="hljs-keyword">else</span> textView.text = getString(R.string.infoText)
</code></pre>
<p>where <code>MIN_SWIPE_DISTANCE</code> is <code>-250</code>:</p>
<pre><code class="lang-kotlin"><span class="hljs-comment">// -250 produces best result, feel free to change to your liking</span>
<span class="hljs-keyword">const</span> <span class="hljs-keyword">val</span> MIN_SWIPE_DISTANCE = -<span class="hljs-number">250</span> <span class="hljs-comment">// User should move alteast -250 from mean position to load new quote</span>
</code></pre>
<p>Now, the <code>ACTION_MOVE</code> event is handled properly. Let's write the code to handle the <code>ACTION_UP</code> event, that is, when the card is released.</p>
<h3 id="heading-how-to-handle-the-actionup-event">How to handle the <code>ACTION_UP</code> event</h3>
<p>For the <code>ACTION_UP</code> event, we want the card to come back to its original position, wait for about <code>100</code> milliseconds, then load a new quote.</p>
<p>The logic to animate the card is similar, but this time we will make its animation duration about <code>150</code> millisecond to make it look smooth.</p>
<p>First, create a variable <code>currentX</code> that holds the current value of the x coordinate of the quote card. We'll use this variable later.</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">var</span> currentX = quoteCard.x
</code></pre>
<p>Then, start the animation on the card. Pass the <code>cardStart</code> variable to the <code>x()</code> function to make it return to its original position and set the duration to <code>150</code>.</p>
<pre><code class="lang-kotlin">quoteCard.animate()
    .x(cardStart)
    .setDuration(<span class="hljs-number">150</span>)
<span class="hljs-comment">// continued below</span>
</code></pre>
<p>This time, we set a listener on the animation. A listener is something that keeps an eye on the animation. By using it, we can perform actions on various animation events such as start, end, resume, and more.</p>
<pre><code class="lang-kotlin"><span class="hljs-comment">// continuation</span>
.setListener(
    <span class="hljs-keyword">object</span> : AnimatorListenerAdapter() {
        <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onAnimationEnd</span><span class="hljs-params">(animation: <span class="hljs-type">Animator</span>)</span></span> {
            viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Default) {
                delay(<span class="hljs-number">100</span>)
                <span class="hljs-comment">// check if the swipe distance was more than</span>
                <span class="hljs-comment">// minimum swipe required to load a new quote</span>
                <span class="hljs-keyword">if</span> (currentX &lt; MIN_SWIPE_DISTANCE) {
                    <span class="hljs-comment">// Add logic to load a new quote if swiped adequately</span>
                    viewModel.getRandomQuote()
                    currentX = <span class="hljs-number">0f</span>
                }
            }
        }
    }
)
.start()
</code></pre>
<p>We set a listener to look for the ending of the animation by overriding the <code>onAnimationEnd()</code> function. </p>
<p>As soon as the animation ends, we launch a coroutine (similar to Threads in Java but much more efficient) with a delay of 100 milliseconds. It then checks if the user had swiped further than the <code>MIN_SWIPE_DISTANCE</code> needed to load a new quote. The variable <code>currentX</code> is used for the comparison here.</p>
<p>If the user actually swipes passing the minimum distance, the coroutine is delayed for <code>100</code> milliseconds. Then the view model loads a new random quote from the API, also resetting the <code>currentX</code> variable to <code>0f</code>.</p>
<p>The final code for the <code>ACTION_UP</code> event looks like this:</p>
<pre><code class="lang-kotlin">MotionEvent.ACTION_UP -&gt; {
    <span class="hljs-keyword">var</span> currentX = quoteCard.x
    quoteCard.animate()
        .x(cardStart)
        .setDuration(<span class="hljs-number">150</span>)
        .setListener(<span class="hljs-keyword">object</span> : AnimatorListenerAdapter() {
            <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onAnimationEnd</span><span class="hljs-params">(animation: <span class="hljs-type">Animator</span>)</span></span> {
                viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Default) {
                    delay(<span class="hljs-number">100</span>)
                    <span class="hljs-comment">// check if the swipe distance was more than</span>
                    <span class="hljs-comment">// minimum swipe required to load a new quote</span>
                    <span class="hljs-keyword">if</span> (currentX &lt; MIN_SWIPE_DISTANCE) {
                        <span class="hljs-comment">// Add logic to load a new quote if swiped adequately</span>
                        viewModel.getRandomQuote()
                        currentX = <span class="hljs-number">0f</span>
                    }
                }
            }
        })
        .start()
    textView.text = getString(R.string.infoText)
}
</code></pre>
<h2 id="heading-final-code">Final Code</h2>
<p>This is the final code for the complete <code>onTouchListener()</code>:</p>
<pre><code class="lang-kotlin">quoteCard.setOnTouchListener(
    View.OnTouchListener { v, event -&gt;

        <span class="hljs-comment">// variables to store current configuration of quote card.</span>
        <span class="hljs-keyword">val</span> displayMetrics = resources.displayMetrics
        <span class="hljs-keyword">val</span> cardWidth = quoteCard.width
        <span class="hljs-keyword">val</span> cardStart = (displayMetrics.widthPixels.toFloat() / <span class="hljs-number">2</span>) - (cardWidth / <span class="hljs-number">2</span>)

        <span class="hljs-keyword">when</span> (event.action) {
            MotionEvent.ACTION_UP -&gt; {
                <span class="hljs-keyword">var</span> currentX = quoteCard.x
                quoteCard.animate()
                    .x(cardStart)
                    .setDuration(<span class="hljs-number">150</span>)
                    .setListener(
                        <span class="hljs-keyword">object</span> : AnimatorListenerAdapter() {
                            <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onAnimationEnd</span><span class="hljs-params">(animation: <span class="hljs-type">Animator</span>)</span></span> {
                                viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Default) {
                                    delay(<span class="hljs-number">100</span>)

                                    <span class="hljs-comment">// check if the swipe distance was more than</span>
                                    <span class="hljs-comment">// minimum swipe required to load a new quote</span>
                                    <span class="hljs-keyword">if</span> (currentX &lt; MIN_SWIPE_DISTANCE) {
                                        <span class="hljs-comment">// Add logic to load a new quote if swiped adequately</span>
                                        viewModel.getRandomQuote()
                                        currentX = <span class="hljs-number">0f</span>
                                    }
                                }
                            }
                        }
                    )
                    .start()
                textView.text = getString(R.string.infoText)
            }
            MotionEvent.ACTION_MOVE -&gt; {
                <span class="hljs-comment">// get the new co-ordinate of X-axis</span>
                <span class="hljs-keyword">val</span> newX = event.rawX

                <span class="hljs-comment">// carry out swipe only if newX &lt; cardStart, that is,</span>
                <span class="hljs-comment">// the card is swiped to the left side, not to the right</span>
                <span class="hljs-keyword">if</span> (newX - cardWidth &lt; cardStart) {
                    quoteCard.animate()
                        .x(
                            min(cardStart, newX - (cardWidth / <span class="hljs-number">2</span>))
                        )
                        .setDuration(<span class="hljs-number">0</span>)
                        .start()
                    <span class="hljs-keyword">if</span> (quoteCard.x &lt; MIN_SWIPE_DISTANCE) 
                        textView.text = getString(R.string.releaseCard)
                    <span class="hljs-keyword">else</span> textView.text = getString(R.string.infoText)
                }
            }
        }

        <span class="hljs-comment">// required to by-pass lint warning</span>
        v.performClick()
        <span class="hljs-keyword">return</span><span class="hljs-symbol">@OnTouchListener</span> <span class="hljs-literal">true</span>
    }
}
</code></pre>
<p>Congrats! In this tutorial, we've implemented animation that lets a user swipe a card containing a quote to get a new quote.</p>
<p>Don't forget to download the app and test it out yourself. Stars and contributions on the <a target="_blank" href="https://github.com/gouravkhunger/QuotesApp">GitHub repository</a> are welcomed!</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Now you have learned how to animate a card and handle animation listeners on it. This helps create better UX that makes your app stand out.</p>
<p>Using the knowledge you gained in this post, you can now create most of the following animations for views in Android:</p>
<ul>
<li><strong>Programmatically create sliding animations for Android views.</strong></li>
</ul>
<p>Just as we did in this tutorial.</p>
<ul>
<li><strong>Left to right animation</strong></li>
</ul>
<p>This is fairly simple, just turn the subtraction in the variables to addition and <code>&lt;</code> signs in the <code>if</code> statements to <code>&gt;</code> signs. With these few tweaks here and there, the right to left animations in card view can be turned into left to right ones!</p>
<ul>
<li><strong>You can also show and hide views using animations.</strong></li>
</ul>
<p>For this, you have to keep track of the start position and end position then animate them with <code>alpha()</code> from <code>0</code> to <code>1</code>. For an example, you can refer to my library <a target="_blank" href="https://github.com/gouravkhunger/AccoLib">Accolib</a> to create animated FAQ accordions.</p>
<ul>
<li><strong>Basic animated layout changes can be achieved with view animations.</strong></li>
</ul>
<p>Thanks a lot for reading so far, I hope this post added some value. Subscribe to my newsletter at <a target="_blank" href="https://genicsblog.com">Genics Blog</a> to stay updated with my future articles!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Set Up Laravel 8 on Your Android Phone ]]>
                </title>
                <description>
                    <![CDATA[ By Precious Oladele Hey, how are you doing? In this article, I'm going to show you how you can install Laravel 8 on your phone.  To get the most out of this guide, you should have some knowledge of PHP and you should know what Laravel is. But if ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-setup-laravel-8-on-android-phone/</link>
                <guid isPermaLink="false">66d4608b55db48792eed3f99</guid>
                
                    <category>
                        <![CDATA[ Android ]]>
                    </category>
                
                    <category>
                        <![CDATA[ android app development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Laravel ]]>
                    </category>
                
                    <category>
                        <![CDATA[ mobile app development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ PHP ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Thu, 11 Nov 2021 16:34:04 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2021/11/pexels-lisa-1092644--1-.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Precious Oladele</p>
<p>Hey, how are you doing? In this article, I'm going to show you how you can install Laravel 8 on your phone. </p>
<p>To get the most out of this guide, you should have some knowledge of PHP and you should know what Laravel is. But if you don't, don't worry – I will explain the basics so you can get started.</p>
<h2 id="heading-what-is-laravel">What Is Laravel?</h2>
<p>Laravel is a web application framework with expressive, elegant syntax. It's built on PHP, which means that Laravel is PHP but it makes things easier to work with.</p>
<p>It comes with lots of packages for various features, like authentication, so we don't need to write authentication ourselves. To learn more about what Laravel can do, you can visit the site at <a target="_blank" href="https://www.freecodecamp.org/news/p/8cc4755b-3b0b-4751-991c-a7b4997c0335/laravel.com">laravel.com</a>.</p>
<h2 id="heading-why-i-wrote-this-tutorial">Why I wrote this tutorial</h2>
<p>I created this tutorial because I want people interested in programming who don't have a laptop or pc to be able to build things on their phones. </p>
<p>My <a target="_blank" href="https://www.freecodecamp.org/news/how-to-code-on-your-phone-python-pydroid-android-app-tutorial/">last post on freeCodeCamp</a> made me realize that people are interested in learning how the tech works, so that's why I'm making more guides like this.</p>
<p>So let's dive into it. In this tutorial I am going to show you how you can install composer.php and use it to set up Laravel 8 on your phone 🔥🔥. </p>
<p>I am Precious Oladele, and I'm almost 19 this month 🥴. I'm from Nigeria and I will be taking you through this process. And if you're wondering how I know so much about this, it's because I also don't have a laptop so I explore with my phone instead 😎.</p>
<h2 id="heading-requirements">Requirements</h2>
<p>To go through this tutorial, you'll need an Android phone with V6.0+.</p>
<h2 id="heading-set-up">Set up</h2>
<p>We need to head over to the Play Store and download <strong>Termux:</strong></p>
<p><img src="https://lh6.googleusercontent.com/cQlgYSj1hH3487jHevQn4eKEjC51xAn5vm0fo6tL8yfNV-nwTeLFW2kv480G5i-OjxtaKXCNQ2c5q36ywH5nqjitwKBjieL7NC4ZXmw7K3WbsuxrsKgAaQBslE9uFg_xnSOFdXc" alt="Image" width="720" height="1600" loading="lazy"></p>
<p><strong>Termux</strong> is a Linux-based system which we can use on our phones. It's as simple as using your regular Linux – you can install anything, even Kali, Ubuntu, or whatever you want. But for this tutorial we will be using it to set up Laravel 8 on our mobile phone.</p>
<h2 id="heading-download-composer">Download composer</h2>
<p>Before we download composer, we need to open up our <strong>Termux</strong> app and type in this command:</p>
<pre><code>termux-setup-storage
</code></pre><p>It'll ask you for storage permissions, so go ahead and click accept. Once you're done head over to <a target="_blank" href="https://getcomposer.org/download/">https://getcomposer.org/download/</a>.</p>
<p><img src="https://lh4.googleusercontent.com/6DrBqRU9swO3NdA0iEKhzhvK7bQtH3dLdKdx69YrCQboxPLIi-seYv84u_5I4dTAcAqAayNRv6wxlMxxLkivcLYi0N5-iLLYAwB9nzVKvxdMFRaY44ECp5duQLcX7j685RyK-K0" alt="Image" width="720" height="1600" loading="lazy"></p>
<p>We need to grab everything there. But before that we need to install <strong>PHP</strong> so we can use it in our app. To do that in your <strong>Termux</strong>, type in the following command:</p>
<pre><code>apt install php
</code></pre><p>and click enter. You should see this:</p>
<p><img src="https://lh6.googleusercontent.com/jamAoOq-pWlvWz9FfouVQVagFh1stoz-qvrCf4k_Aywd9pabMiDj8ygNR9lqCiXDmwF8M2nzHyrJoDlvrBoPAUSO4w7WY40vQlQqTWzdkDAvK8dlR9XMn19qVhLAu1iwv0E7R24" alt="Image" width="720" height="1600" loading="lazy"></p>
<p>Once that is done head over to the composer page and grab the code. We need to do this because Termux is Linux-based. If it was Windows there would be a simple button to download composer.exe there. </p>
<p>Copy the whole code and head over to Termux where you can paste it in. Then click enter.</p>
<p>When composer is installed you should see something like this:</p>
<p><img src="https://lh3.googleusercontent.com/Ou_2eDdSX0ZuA0KW29MF2xNMi0YHh3f159-w7ujzGeQtIUEtEZ4mFaq4WYJLFxeGeox7lHmxMswxJcRm1fY6a2C4fZSd0329DnjPcHJvaiUs-vKerof13s2qYMCEi650q-X_qqU" alt="Image" width="720" height="1600" loading="lazy"></p>
<h2 id="heading-how-to-install-laravel-8">How to Install Laravel 8</h2>
<p>Before we install Laravel 8, let's check to see if we have a <strong>composer.phar</strong> file. In your <strong>Termux</strong> type this command:</p>
<pre><code class="lang-php">ls
</code></pre>
<p>and hit enter. You will see the available files there.</p>
<p><img src="https://lh4.googleusercontent.com/wNOLq4c37i9ccupbMQm64jvOibFEEN2ZuPi_Lez_HO6PZZalo0eI1Lp_XWwvpZK8fWgRedfNsUqo2tYsC4lN54w7TDjjFB4C3k0yR2NVcXiKRY4sFOk6tpCGjVk8X3GaIuMIJEw" alt="Image" width="720" height="1600" loading="lazy"></p>
<p>You can see the <strong>composer.phar</strong> file and a <strong>storage</strong> folder. The storage folder grants access to your file manager. Remember the <strong>termux-setup-storage</strong> command you wrote first.</p>
<p>Now let's install Laravel 8. To do so we can either create a project or just install it globally. But it's a bit of a long process when installing it globally on your phone because you need to set a path and so on, and it can be pretty confusing. So in this guide we will create a project instead. </p>
<p>In your <strong>Termux</strong> go ahead and type this:</p>
<pre><code class="lang-php">php composer.phar create-project laravel/laravel myapp
</code></pre>
<p><code>myapp</code> is just the project name – you can change it to whatever you want. Then hit enter and wait for the magic to happen.</p>
<p>When you see the below, it means that Laravel has been installed:</p>
<p><img src="https://lh5.googleusercontent.com/e1T64FywzWaz7amrgF0E5aXE6YqJdjMqCm2ZbAMeVyq2GBJ3wPXpAIxevqulsoPa2LOyqfWTtb6PjbqK48-IcWAssx6Hk3cPHyd5-N6Lw2fFWFdWEM0qhbA6ivKHjt5GH_ED5aI" alt="Image" width="720" height="1600" loading="lazy"></p>
<p>Easy as pie. Now to test it, you can cd into <code>myapp</code> by typing <strong><code>cd myapp</code></strong>. Then you can run the Laravel server with <strong><code>php artisan serve</code>.</strong></p>
<p>Voilà – development has started 🔥</p>
<p><img src="https://lh5.googleusercontent.com/OmM_BEGUNTploSBEIItrnkn6S_yotTKJPo_q60PP7mx93Uo9V4Tb9p_ucHIiaHAOPbFGL36CWT-zWcGwc-a2FsiNbfyFpbnWu6IjT-MsS2X5TQhI1vAbhU3bGMgbM_tLC_nu3oU" alt="Image" width="720" height="1600" loading="lazy"></p>
<p>Now you can open <a target="_blank">http://127.0</a>.0.1:8000 in your browser and see that Laravel is running:</p>
<p><img src="https://lh5.googleusercontent.com/iG_uPbC4lzleqSPETYM6pRFsZ1BAj5PbAeTwSDNVWL2_r6V-AxpWk5iYpS-Gs2iBQUmvPZMnVoCjIk9ZtcOJj6QeCOi32dFJ-2zVJC420MIiFyN9pSKUb5sUGI2iSaJ0ITf9Wr4" alt="Image" width="720" height="1600" loading="lazy"></p>
<p> Also make sure you do this so your <strong>Termux</strong> app won't force close when you are coding: 😎</p>
<p><img src="https://lh5.googleusercontent.com/P6s9dGlmoHGeo2_vAmImYFSXrFgRZTUUlunlOwegzVw8QdLGKoigMhbm5lPlxsE-K5PraWkGlN6VYwzwk16FLi_A4GOGRJdCkPWB3rlc6bbZuJQN7d7s0WKkJTSu1QFTeGXABDM" alt="Image" width="720" height="1026" loading="lazy"></p>
<h2 id="heading-thats-it">That's it!</h2>
<p>Thanks for reading. I hope you learned something from this tutorial. You should now be able to install Laravel on your Android phone and start using it to build apps.</p>
<p>If you want more content from me, you can subscribe to my YouTube channel 🙏😁<br><a target="_blank" href="https://youtube.com/channel/UCLcHGKxbEO1XGVETXqzYXLA">DevStack</a></p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/VAh6A1SpZfo" 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 GitHub Actions to Automate Android App Development ]]>
                </title>
                <description>
                    <![CDATA[ There are many repetitive tasks that we have to do every day. And they can be a bit boring, difficult, and monotonous.  But instead of laboring away at those daily tasks, you can delegate them so someone or something else does them for you. That way,... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/use-github-actions-to-automate-android-development/</link>
                <guid isPermaLink="false">66ba5041f8a814ef73b78bce</guid>
                
                    <category>
                        <![CDATA[ Android ]]>
                    </category>
                
                    <category>
                        <![CDATA[ android app development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ automation ]]>
                    </category>
                
                    <category>
                        <![CDATA[ GitHub Actions ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Tomer ]]>
                </dc:creator>
                <pubDate>Mon, 16 Aug 2021 20:18:40 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2021/08/moises-de-paula-HPZZHJ-LuDI-unsplash.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>There are many repetitive tasks that we have to do every day. And they can be a bit boring, difficult, and monotonous. </p>
<p>But instead of laboring away at those daily tasks, you can delegate them so someone or something else does them for you. That way, you can have more time to do things you want to do. You can have time to relax.</p>
<p>If you've ever developed an Android application, you know how tedious some tasks can get:</p>
<ul>
<li>Running tests</li>
<li>Making sure the application compiles when merging new code</li>
<li>Building and publishing the application.</li>
</ul>
<p>So to whom should we pass along these tasks? Another coworker? They can just pass it along to someone else and it won’t free up anyone’s time. Plus, we don’t want to bum out our colleagues. The solution?</p>
<p>Say hello to GitHub Actions. 👐</p>
<h2 id="heading-what-are-github-actions">What Are GitHub Actions?</h2>
<p>GitHub Actions are commands we can trigger when something happens in our repository. At its core, an action is a configuration file that has a list of commands that describe:</p>
<ul>
<li>What needs to happen</li>
<li>When it should happen</li>
</ul>
<p>This configuration file is in YAML format (.yml) and an example looks like this:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">My</span> <span class="hljs-string">GitHub</span> <span class="hljs-string">Action</span>

<span class="hljs-attr">on:</span> <span class="hljs-string">pull_request</span>

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">build:</span>

    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>

    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v1</span>
</code></pre>
<p>Let’s break down the example above:</p>
<ol>
<li>We give a name to our action (My GitHub Action) [<strong>Optional]</strong></li>
<li>We signify when this action should run (when a pull request is opened)</li>
<li>We start a list of tasks (jobs) that should happen once this action is triggered</li>
<li>The first one is a <strong>build</strong> action</li>
<li>The <strong>runs-on</strong> command tells GitHub which runner will execute this job (this is a virtual server and you can choose between Windows/Mac/Linux)</li>
<li>Each job can have many phases which are grouped together by the <strong>steps</strong> keyword</li>
<li>The <strong>uses</strong> keyword tells the script what action to enact</li>
</ol>
<p>This is a very short example which does not showcase all of the features of GitHub Actions, but it provides a peek into the structure of the configuration file. </p>
<p>In the next sections, we will create actions that will help keep our development cycle efficient and effective.</p>
<p>Note that all GitHub Actions files need to reside under your project’s main folder in the path .github/workflows:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/08/1_5t9-LbXINncXYPUtYgY0TA.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-how-to-create-a-github-action-for-pull-requests">How to Create a GitHub Action For Pull Requests</h2>
<p>Whether you are working on a project alone or part of a team, making sure that your application is stable is crucial. So it makes total sense to ensure that your application is compiling properly and all tests are passing whenever you consider merging a pull request. </p>
<p>We’ve already shown in our example how we can checkout the code in our repository. In this action, we will include the following steps:</p>
<ol>
<li>Setting up the JDK version</li>
<li>Changing permissions for the virtual environment</li>
<li>Running tests (if we have any)</li>
<li>Building the application</li>
</ol>
<pre><code class="lang-yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">Android</span> <span class="hljs-string">Build</span>

<span class="hljs-attr">on:</span> <span class="hljs-string">pull_request</span>

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">build:</span>

    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>

    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v1</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Set</span> <span class="hljs-string">Up</span> <span class="hljs-string">JDK</span>              <span class="hljs-string">//</span> <span class="hljs-number">1</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/setup-java@v1</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">java-version:</span> <span class="hljs-number">1.8</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Change</span> <span class="hljs-string">wrapper</span> <span class="hljs-string">permissions</span>  <span class="hljs-string">//</span> <span class="hljs-number">2</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">chmod</span> <span class="hljs-string">+x</span> <span class="hljs-string">./gradlew</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Run</span> <span class="hljs-string">Tests</span>                   <span class="hljs-string">//</span> <span class="hljs-number">3</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">./gradlew</span> <span class="hljs-string">test</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Build</span> <span class="hljs-string">Project</span>               <span class="hljs-string">//</span> <span class="hljs-number">4</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">./gradlew</span> <span class="hljs-string">assemble</span>
</code></pre>
<p>You can see that above, each step has its own properties and attributes that are specific to it. </p>
<p>I won’t go into each one of them, since you can do that on your own through the documentation. What is common for most of the steps is the <strong>run</strong> keyword. This attribute states what command to execute.</p>
<p>✋ We need the second step so that the virtual environment can run the gradle commands. Without it, it won’t be able to.</p>
<h2 id="heading-how-to-make-a-github-action-for-publishing-an-application">How to make a GitHub Action For Publishing an Application</h2>
<p>Once you have published your application for the first time, republishing it becomes sort of like a chore. </p>
<p>You have to make sure the version is upgraded, build the APK, submit it via the Google Play Console and other tedious tasks. </p>
<p>We can automate this process with another GitHub action. This action is a little more complicated than the previous once since it requires the use of <a target="_blank" href="https://docs.github.com/en/actions/reference/encrypted-secrets">GitHub Secrets</a>. </p>
<p>In a nutshell, GitHub Secrets are a way to store sensitive information as environment variables of your repository. We will be needing to use them because:</p>
<ol>
<li>We will need to sign our application</li>
<li>We are going to give this action permission to submit our built application to the Google Play Store</li>
</ol>
<p>Let’s find out how we can create GitHub Secrets first.</p>
<ul>
<li>Inside the main page of your repository, click on the <strong>Settings</strong> tab</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/08/1_JVm-YqUz-grjtXl6v1ySsg.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<ul>
<li>On the left hand side menu, there will be an option titled <strong>Secrets</strong></li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/08/1_lAAL2-4XiOyLoaJYutJ4EA.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<ul>
<li>To create a secret, press the <strong>New repository secret</strong> button</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/08/1_MiV5nk7LtM56v6bYcPH_2Q.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Now that we got that out of the way, let’s look at the script for publishing an application:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">Android</span> <span class="hljs-string">Publish</span>

<span class="hljs-attr">on:</span>
  <span class="hljs-attr">workflow_dispatch:</span>

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">build:</span>

    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>

    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v1</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Set</span> <span class="hljs-string">Up</span> <span class="hljs-string">JDK</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/setup-java@v1</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">java-version:</span> <span class="hljs-number">1.8</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Change</span> <span class="hljs-string">wrapper</span> <span class="hljs-string">permissions</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">chmod</span> <span class="hljs-string">+x</span> <span class="hljs-string">./gradlew</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Run</span> <span class="hljs-string">Tests</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">./gradlew</span> <span class="hljs-string">test</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Build</span> <span class="hljs-string">Project</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">./gradlew</span> <span class="hljs-string">build</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Build</span> <span class="hljs-string">Release</span> <span class="hljs-string">AAB</span>      <span class="hljs-string">//</span> <span class="hljs-number">1</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">./gradlew</span> <span class="hljs-string">bundleRelease</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Sign</span> <span class="hljs-string">AAB</span>               <span class="hljs-string">//</span> <span class="hljs-number">2</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">r0adkll/sign-android-release@v1</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">releaseDirectory:</span> <span class="hljs-string">app/build/outputs/bundle/release</span>
          <span class="hljs-attr">signingKeyBase64:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.SIGN_KEY</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">alias:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.ALIAS</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">keyStorePassword:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.STORE_KEY_PASSWORD</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">keyPassword:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.KEY_PASSWORD</span> <span class="hljs-string">}}</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Deploy</span> <span class="hljs-string">to</span> <span class="hljs-string">Play</span> <span class="hljs-string">Store</span>   <span class="hljs-string">//</span> <span class="hljs-number">3</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">r0adkll/upload-google-play@v1</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">serviceAccountJsonPlainText:</span> <span class="hljs-string">${{secrets.SERVICE_ACCOUNT}}</span>
          <span class="hljs-attr">packageName:</span> <span class="hljs-string">com.tomerpacific.laundry</span>
          <span class="hljs-attr">releaseFiles:</span> <span class="hljs-string">app/build/outputs/bundle/release/app-release.aab</span>
          <span class="hljs-attr">track:</span> <span class="hljs-string">production</span>
</code></pre>
<p>You may have noticed that this action will run <strong>on workflow_dispatch</strong>. What does that mean? Basically it allows this action to be triggered manually from GitHub itself. </p>
<p>You can of course decide you would rather run this action when a push happens on the main branch (for example).</p>
<p>The step marked with 1 in the snippet above triggers building an .aab of our application. Then, like we would do if we were building it inside Android Studio, we have to sign this .aab file. </p>
<p>This is the first time GitHub Secrets come into play. We need to create secrets for:</p>
<ul>
<li>The Signing Key (secrets.SIGN_KEY)</li>
<li>The Key Alias (secrets.ALIAS)</li>
<li>The Store Key Password (secrets.STORE_KEY_PASSWORD)</li>
<li>The Key Password (secrets.KEY_PASSWORD)</li>
</ul>
<p>Once we have signed the .aab file we can deploy it to the Google Play Store. At this step there is a little bit more work to do since we need to allow this GitHub Action the permission to deploy applications for us on Google Play. But, wait, how do we do that? We use a <a target="_blank" href="https://cloud.google.com/compute/docs/access/service-accounts">Service Account</a>.</p>
<h3 id="heading-how-to-create-a-service-account">How to Create a Service Account</h3>
<blockquote>
<p>A service account is an entity that you create that tells services or applications it interacts with that it is operating on your behalf.</p>
</blockquote>
<p>In our case, our GitHub Action is going to interact with the Google Play Store so it can upload a new version of our application. </p>
<p>To create a service account, go to the Google Cloud Console. If you have no account there, make sure to create one. Then, on the main page, in the left hand side menu, there will be a list item titled Service Accounts.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/08/1_5MKbbfXd8BzFywlBzsWytw.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Once you've clicked it, on the right hand side of the window you will see any service accounts you already have. </p>
<p>We want to create a new one and in the top part of the window there is a button to do just that.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/08/1_em9j6HfzBMlG603v0O6SzA.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>In the window that opens, you will have to enter in the service’s name and you can also enter a description.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/08/1_E1b3hZk9hS7PX4mM9LhJJA.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>The name given here will be the unique identifier of this service account. </p>
<p>In the second step you will be asked to give this service account a role. From the <strong>Select A Role</strong> dropdown, choose Basic → Editor.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/08/1_GPvZUINPACbTwH5jWNh-4w.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Finally, in the third step, fill out your email in both places under the "Grant users access to this service account" section:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/08/1_ARhcPbB3VgC-_-e-AQrCTg.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>After pressing the done button, you will need to create a key for this service account. The action will use this key to be identified by Google Play. </p>
<p>To create the key, click the three horizontal dots under the Actions label in the main service account screen. In the menu that appears, select <strong>Manage keys</strong>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/08/1_n9gKn2b4xP3_SnOcVxsYhQ.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>In this window, we will create a key by selecting the <strong>New Key</strong> button and choosing "Create new key" from the menu that appears.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/08/1_zkDo3a1b8Rw487u1SemAWQ.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Now we have the option of choosing the format of our new key – the default is JSON and we will leave it selected. Click create.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/08/1_17x3W1mL8nFC8I8WsPci0A.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Once you have done so, a file will be downloaded to your computer. Make sure to keep this file as it has all the data relevant for your service account and you won’t be able to download it again. </p>
<p>We will take the contents of this file and then create a GitHub secret with it (<strong>secrets.SERVICE_ACCOUNT</strong>).</p>
<p>Last but not least, we need to make Google Play aware of this service account. Doing so requires us to login to our Google Play Console account and heading over to <strong>Setup →API Access</strong>. </p>
<p>If you scroll down the page, you will see a section titled Service Accounts. You should be able to see the service account you created previously. Click the Grant Access link</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/08/1_bONaGlfRFYGPi2i3vJUntg.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>In the settings that open, head over to App permissions. Here you will choose which application this service account interacts with. </p>
<p>Under Account permissions, everything under the <strong>releases</strong> section should be checked. I highly advise you to look at all the other settings and decide for yourself what you want to leave checked or what you want to check off. </p>
<p>Once you are done, click the <strong>Invite user</strong> button located in the bottom right corner.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/08/1_0ez0QuP59YwonfFePbjvzQ.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>After the invitation is sent, we can run the publishing to store action.</p>
<h2 id="heading-how-to-monitor-our-actions-in-github">How to Monitor Our Actions in GitHub</h2>
<p>To see what actions are defined for your repository, click on the Actions tab. This tab showcases all workflows defined and those that have already run.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/08/Workflows.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>On the left hand side you can see all the actions that have been defined and on the right hand side you can see all the actions that have been run. If you want to look at a specific action, you can click on it.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/08/workflow.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>If the action is defined to run on <strong>workflow_dispatch</strong>, you will see a button enabling you to run it (like in the picture above). </p>
<p>If you want to see a specific run of a workflow, you can also do that from the main Workflows page by clicking on one of the runs. If one of the actions fails to run, this would be the place to investigate and see what went wrong. </p>
<p>Our first action is supposed to be triggered when a pull request is opened. If it works correctly, you should be seeing this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/08/pr.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>And there you have it!</p>
<h2 id="heading-wrapping-up">Wrapping Up</h2>
<p>It’s been a long read up to this point, but we have gone through everything that you need to get started creating a Continuous Integration and Continuous Deployment pipeline for your applications. </p>
<p>If you are interested in seeing how GitHub Actions are set up, you can check them out in one of my repositories 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/LaundrySymbols/actions">https://github.com/TomerPacific/LaundrySymbols/actions</a></div>
<p>To read more about GitHub Actions, head over 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://docs.github.com/en/actions">https://docs.github.com/en/actions</a></div>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Automatically Upload an Android App Bundle to the Play Store ]]>
                </title>
                <description>
                    <![CDATA[ By Zaid Humayun In this article, I'm going to explain how to automatically upload an Android App Bundle (.aab file) to the Play Store's beta track. We'll use Android Studio and AWS as a cloud infrastructure provider.  Once we've uploaded the app bund... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/automatically-upload-an-android-app-bundle-to-the-play-store/</link>
                <guid isPermaLink="false">66d461c8a326133d12440aa4</guid>
                
                    <category>
                        <![CDATA[ Android ]]>
                    </category>
                
                    <category>
                        <![CDATA[ android app development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ automation ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Tue, 06 Jul 2021 21:08:39 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2021/07/pexels-lisa-1092644.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Zaid Humayun</p>
<p>In this article, I'm going to explain how to automatically upload an Android App Bundle (.aab file) to the Play Store's beta track. We'll use Android Studio and AWS as a cloud infrastructure provider. </p>
<p>Once we've uploaded the app bundle, we'll trigger a Slack notification.</p>
<p>This is a valuable use of your time for a number of reasons, like creating observability and prioritizing processes.</p>
<h2 id="heading-tech-well-use">Tech We'll Use</h2>
<p>Here are the resources we are going to be using for this tutorial:</p>
<ol>
<li>Android Studio</li>
<li>AWS CodeBuild</li>
<li>AWS Lambda</li>
<li>S3</li>
<li>Slack</li>
</ol>
<h2 id="heading-high-level-overview-of-the-project">High Level Overview of the Project</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/07/beta_track_upload_flow-1.jpg" alt="beta_track_upload_flow-1" width="600" height="400" loading="lazy"></p>
<p>The above image shows you a general overview of how we'll structure the whole thing.</p>
<p>Essentially, there needs to be a Code Pipeline set up on AWS for your Android repository. This Code Pipeline will have Code Build as one of its stages.</p>
<p>Pushing to the master branch of your Android app repository is going to trigger Code Build. The Code Build project will sign the Android app from the command line and upload the artifact to an S3 bucket.</p>
<p>Uploading the bundle to S3 will trigger a Lambda, which will download the bundle and upload it to the Play Store using the Google Publishing API. Once it gets a 200 response, the Lambda will then trigger a Slack notification.</p>
<h2 id="heading-how-to-get-your-google-play-service-account-key">How to Get Your Google Play Service Account Key</h2>
<p>To be able to use the Google Play Publisher API, you will need a Google Play Service Account key.</p>
<p>A service account is an account that can act on your behalf when servers are communicating with each other. You can read more about how Google uses OAuth2.0 for server to server communication <a target="_blank" href="https://developers.google.com/identity/protocols/oauth2/service-account">here</a>.</p>
<p>To see how to create a service account and give it access to the Google Play Publisher API, look <a target="_blank" href="https://developers.google.com/identity/protocols/oauth2/service-account">here</a>.</p>
<p>Once you've created your service account and given it the appropriate permissions, make sure to download the service account key and keep it safe. You'll be uploading this to an S3 bucket soon.</p>
<h2 id="heading-how-to-sign-the-android-bundle">How to Sign the Android Bundle</h2>
<p>The main thing to figure out is how to sign the Android App Bundle. Google has fairly decent documentation on it that you can find <a target="_blank" href="https://developer.android.com/studio/build/building-cmdline#sign_cmdline">here</a>.</p>
<p>I'll summarize the links below.</p>
<p>Generate a private key using <code>keytool</code> like this:</p>
<pre><code class="lang-bash">keytool -genkey -v -keystore my-release-key.jks -keyalg RSA -keysize 2048 -validity 10000 -<span class="hljs-built_in">alias</span> my-alias
</code></pre>
<p>You can call your key whatever you want. Here, I called it <code>my-release-key.jks</code>. You can also choose whatever alias you want. Throughout this tutorial make sure to use the correct name and alias for your key.</p>
<p>Open <code>build.gradle</code> within your <code>app</code> directory in Android Studio and add the following code block to it:</p>
<pre><code class="lang-bash">android {
    ...
    defaultConfig { ... }
    signingConfigs {
        release {
            // You need to specify either an absolute path or include the
            // keystore file <span class="hljs-keyword">in</span> the same directory as the build.gradle file.
            storeFile file(<span class="hljs-string">"my-release-key.jks"</span>)
            storePassword <span class="hljs-string">"password"</span>
            keyAlias <span class="hljs-string">"my-alias"</span>
            keyPassword <span class="hljs-string">"password"</span>
        }
    }
    buildTypes {
        release {
            signingConfig signingConfigs.release
            ...
        }
    }
}
</code></pre>
<p>If you changed the name of your release key to something other than the default, make sure to specify the new name. Same thing for the alias.</p>
<p>Your store password will be whatever password you generated when you first uploaded your app to the Play Store.</p>
<p>Now, when you run the command <code>./gradlew :app:bundleRelease</code> from the command line in Android Studio, you'll notice that it generates a signed App Bundle.</p>
<h2 id="heading-how-to-scrub-signing-information">How to Scrub Signing Information</h2>
<p>Committing code with the signing information available as plain text in the <code>build.gradle</code> file is a security risk and could be an attack vector.</p>
<p>Google has documentation around this that you can find <a target="_blank" href="https://developer.android.com/studio/publish/app-signing#secure-shared-keystore">here</a>.</p>
<p>First, create a <code>keystore.properties</code> file in the root of your project directory.</p>
<p>The contents of the file should be as below:</p>
<pre><code class="lang-text">storePassword=myStorePassword
keyPassword=myKeyPassword
keyAlias=myKeyAlias
storeFile=myStoreFileLocation
</code></pre>
<p>Your store password and key password will be the password you used when you uploaded your app bundle to the App Store the first time.</p>
<p>Your <code>keyAlias</code> and <code>storeFile</code> will be the alias you assigned when creating your private key and the location of the private key you created, respectively.</p>
<p>Now, we need to load this file into <code>build.gradle</code>. This came as a surprise to me initially, but Gradle actually <a target="_blank" href="https://docs.gradle.org/current/dsl/index.html">works as a DSL</a>. So, it makes it easier to write configuration using Gradle.</p>
<pre><code class="lang-gradle">//  Load properties from keystore.properties
def keystorePropertiesFile = rootProject.file("keystore.properties")

//  Creating a new Properties() object
def keystoreProperties = new Properties()

//  If keystorePropertiesFile exists, read from that, else set from build environment
if (keystorePropertiesFile.exists()) {
    //  Loading the keystoreProperties file
    keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
} else {
    //  Read all environment variables from the build environment
    keystoreProperties.setProperty("storeFile", "${System.getenv('STORE_FILE')}")
    keystoreProperties.setProperty("keyAlias", "${System.getenv('KEY_ALIAS')}")
    keystoreProperties.setProperty("keyPassword", "${System.getenv('KEY_PASSWORD')}")
    keystoreProperties.setProperty("storePassword", "${System.getenv('STORE_PASSWORD')}")
}
</code></pre>
<p>You'll notice the if condition in there – don't worry about it for now. It's there specifically to account for Code Build later.</p>
<p>Once you do this, change your <code>signingConfigs</code> section in <code>build.gradle</code> to look like this:</p>
<pre><code class="lang-gradle">signingConfigs {
        release {
            storeFile file(keystoreProperties['storeFile'])
            keyAlias keystoreProperties['keyAlias']
            keyPassword keystoreProperties['keyPassword']
            storePassword keystoreProperties['storePassword']
        }
    }
</code></pre>
<h2 id="heading-how-to-set-up-aws-code-pipeline">How to Set Up AWS Code Pipeline</h2>
<p>I'm not going to go into too much detail on this one since its relatively straightforward.</p>
<p>Set up an AWS Code Pipeline with the following three stages:</p>
<ol>
<li>Source stage connected to your GitHub repository's <code>master</code> branch</li>
<li>Build stage connected to AWS Code Build</li>
<li>Deploy stage which will deploy to an S3 bucket.</li>
</ol>
<p>You can find more documentation about setting up a Code Pipeline <a target="_blank" href="https://docs.aws.amazon.com/codebuild/latest/userguide/how-to-create-pipeline.html">here</a>.</p>
<h2 id="heading-how-to-set-up-aws-s3">How to Set Up AWS S3</h2>
<p>First, make sure you have a Code Pipeline set up with Code Build as one of the stages. Next, set up two S3 buckets:</p>
<ol>
<li>A bucket to store your release key in. I'm calling this bucket <code>release-key.jks</code></li>
<li>A bucket in which you will store your Google Play Service Account private key. (You should have downloaded this key while creating your service account.)</li>
</ol>
<p>You will need to allow access to these buckets from your Code Build service role. Your Code Build service role should have been created when you set up your Code Pipeline.</p>
<p>Head over to the IAM console and find your Code Build service role and grab the ARN.</p>
<p>Next, use the console to get to the Permissions tab for the bucket <code>release-key.jks</code> and add the following policy there:</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"Version"</span>: <span class="hljs-string">"2012-10-17"</span>,
    <span class="hljs-attr">"Statement"</span>: [
        {
            <span class="hljs-attr">"Effect"</span>: <span class="hljs-string">"Allow"</span>,
            <span class="hljs-attr">"Principal"</span>: {
                <span class="hljs-attr">"AWS"</span>: [
                    <span class="hljs-string">"arn:aws:iam::123456789:role/service-role/codebuild-service-role-dummy"</span>,
                ]
            },
            <span class="hljs-attr">"Action"</span>: <span class="hljs-string">"s3:GetObject"</span>,
            <span class="hljs-attr">"Resource"</span>: <span class="hljs-string">"arn:aws:s3:::release-key-bucket/*"</span>
        }
    ]
}
</code></pre>
<p>This policy will allow access to the S3 bucket from the machine where your CodeBuild project will execute.</p>
<p>You will need to replace the ARNs mentioned above with the ARNs for your account. Make sure to specify the correct ARN for the Code Build service role when you're updating the policy.</p>
<p>You don't need to change the permissions policy for the second bucket. We'll add the relevant permissions to the AWS Lambda role to allow it to access the bucket.</p>
<h2 id="heading-how-to-set-up-aws-codebuild">How to Set Up AWS CodeBuild</h2>
<p>Next, create a <code>buildspec.yml</code> file in your project root folder.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">version:</span> <span class="hljs-number">0.2</span>

<span class="hljs-attr">phases:</span>
  <span class="hljs-attr">build:</span>
    <span class="hljs-attr">commands:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">aws</span> <span class="hljs-string">s3api</span> <span class="hljs-string">get-object</span> <span class="hljs-string">--bucket</span> <span class="hljs-string">release-key.jks</span> <span class="hljs-string">--key</span> <span class="hljs-string">release-key.jks</span> <span class="hljs-string">./releaseKey.jks</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">cp</span> <span class="hljs-string">./releaseKey.jks</span> <span class="hljs-string">${CODEBUILD_SRC_DIR}/app/releaseKey.jks</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">export</span> <span class="hljs-string">STORE_FILE=releaseKey.jks</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">export</span> <span class="hljs-string">KEY_ALIAS=$keyAlias</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">export</span> <span class="hljs-string">KEY_PASSWORD=$keyPassword</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">export</span> <span class="hljs-string">STORE_PASSWORD=$storePassword</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./gradlew</span> <span class="hljs-string">:app:bundleRelease</span>

<span class="hljs-attr">artifacts:</span>
  <span class="hljs-attr">files:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">app/build/outputs/bundle/release/app-release.aab</span>
</code></pre>
<p>This file is pretty simple. It fetches the release key from the bucket specified and saves it into a local file on the Code Build server into the location specified.</p>
<p>Next, export all the variables required for the <code>build.gradle</code> configuration to work correctly. Finally, run Gradle's release command from the command line.</p>
<p>Before you can run this script in Code Build, you'll need to add the variables to the Code Build environment. To do this, first go to the AWS Code Build console and pick your build project for your Android app.</p>
<p>Next, select Edit &gt; Environment like in the screenshot below:</p>
<p><img src="https://www.freecodecamp.org/news/assets/img/aws_code_build_menu_screenshot.png" alt="aws_code_build_menu_screenshot" width="600" height="400" loading="lazy"></p>
<p>On the screen that pops up once you do this, select the Additional Configuration dropdown. There you'll see an option to add environment variables through key value pairs.</p>
<p>Now when Code Build runs the <code>buildspec.yml</code> file, it will be able to export the specified variables.</p>
<p>As things stand right now, when your pipeline runs, Code Build will be able to download the private key to sign and build your Android app and upload the signed bundle to an S3 bucket.</p>
<h2 id="heading-how-to-set-up-the-slack-app">How to Set Up the Slack App</h2>
<p>Observability is a hallmark of automation. You want to know when your automation runs, whether it succeeds or fails, and if it fails, the reason for failure.</p>
<p>The way AWS typically handles observability is through CloudWatch. But I think a Slack integration serves the purpose just as well.</p>
<p>The easiest way to integrate Slack into your automation workflows is to set up a Slack app and send a notification to that app from your automation workflow.</p>
<p>To learn how to set up a Slack app, view the documentation <a target="_blank" href="https://api.slack.com/start/overview">here</a>. The process is super easy and you should have an app up and running in a few minutes. </p>
<p>Once you've created the app, you will get a WebHook URL you can use to call the app to post into the relevant channel. Keep track of this WebHook URL because we'll be using this with the AWS Lambda function.</p>
<h2 id="heading-how-to-set-up-aws-lambda">How to Set Up AWS Lambda</h2>
<p>So far, we have an Android App Bundle being signed, built, and uploaded to an S3 bucket. Next, we need to figure out how to upload the bundle to the beta track on the Play Store.</p>
<p>The way to do this is to set up an AWS Lambda which will be triggered when the bundle is uploaded to the S3 bucket. When this trigger occurs, the Lambda will run, download the bundle, grab the service account key, and upload the bundle to the Play Store beta track.</p>
<p>Once you've created a Lambda and added a trigger to run it when a file is uploaded to the bucket, look at the code below:</p>
<pre><code class="lang-python"><span class="hljs-string">"""This Python3 script is used to upload a new .aab bundle to the play store. The execution of this Python script
    occurs through an AWS Lambda which is invoked when a new file is uploaded to the relevant S3 buckets"""</span>

<span class="hljs-keyword">import</span> json
<span class="hljs-keyword">import</span> boto3
<span class="hljs-keyword">import</span> os
<span class="hljs-keyword">from</span> urllib <span class="hljs-keyword">import</span> request, parse
<span class="hljs-keyword">from</span> google.oauth2 <span class="hljs-keyword">import</span> service_account
<span class="hljs-keyword">import</span> googleapiclient.discovery

<span class="hljs-comment">#   Defining the scope of the authorization request</span>
SCOPES = [<span class="hljs-string">'https://www.googleapis.com/auth/androidpublisher'</span>]

<span class="hljs-comment">#   Package name for app</span>
package_name = <span class="hljs-string">'com.app.name'</span>

<span class="hljs-comment">#   Define the slack webhook url</span>
slack_webhook_url = os.environ[<span class="hljs-string">'SLACK_WEBHOOK_URL'</span>]

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">send_slack_message</span>(<span class="hljs-params">message</span>):</span>
    data = json.dumps({ <span class="hljs-string">'text'</span>: message })
    post_data = data.encode(<span class="hljs-string">'utf-8'</span>)
    req = request.Request(slack_webhook_url, data=post_data, headers={ <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'application/json'</span> })
    request.urlopen(req)

<span class="hljs-comment">#   This is the main handler function</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">lambda_handler</span>(<span class="hljs-params">event, context</span>):</span>
    <span class="hljs-comment">#   Create a new client S3 client and download the correct file from the bucket</span>
    s3 = boto3.client(<span class="hljs-string">'s3'</span>)
    s3.download_file(<span class="hljs-string">'service-account-bucket-key'</span>, <span class="hljs-string">'service-account-bucket-key.json'</span>, <span class="hljs-string">'/tmp/service-account-key.json'</span>)
    SERVICE_ACCOUNT_FILE = <span class="hljs-string">'/tmp/service-account-key.json'</span>

    <span class="hljs-comment">#   Download the app-release.aab file that triggered the Lambda</span>
    bucket_name = event[<span class="hljs-string">'Records'</span>][<span class="hljs-number">0</span>][<span class="hljs-string">'s3'</span>][<span class="hljs-string">'bucket'</span>][<span class="hljs-string">'name'</span>]
    file_key = event[<span class="hljs-string">'Records'</span>][<span class="hljs-number">0</span>][<span class="hljs-string">'s3'</span>][<span class="hljs-string">'object'</span>][<span class="hljs-string">'key'</span>]
    s3.download_file(bucket_name, file_key, <span class="hljs-string">'/tmp/app-release.aab'</span>)
    APP_BUNDLE = <span class="hljs-string">'/tmp/app-release.aab'</span>

    print(<span class="hljs-string">f"A bundle uploaded to <span class="hljs-subst">{bucket_name}</span> has triggered the Lambda"</span>)

    <span class="hljs-comment">#   Create a credentials object and create a service object using the credentials object</span>
    credentials = service_account.Credentials.from_service_account_file(
        SERVICE_ACCOUNT_FILE, scopes=SCOPES
    )
    service = googleapiclient.discovery.build(<span class="hljs-string">'androidpublisher'</span>, <span class="hljs-string">'v3'</span>, credentials=credentials, cache_discovery=<span class="hljs-literal">False</span>)

    <span class="hljs-comment">#   Create an edit request using the service object and get the editId</span>
    edit_request = service.edits().insert(body={}, packageName=package_name)
    result = edit_request.execute()
    edit_id = result[<span class="hljs-string">'id'</span>]

    <span class="hljs-comment">#   Create a request to upload the app bundle</span>
    <span class="hljs-keyword">try</span>:
        bundle_response = service.edits().bundles().upload(
            editId=edit_id,
            packageName=package_name,
            media_body=APP_BUNDLE,
            media_mime_type=<span class="hljs-string">"application/octet-stream"</span>
        ).execute()
    <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> err:
        message = <span class="hljs-string">f"There was an error while uploading a new version of <span class="hljs-subst">{package_name}</span>"</span>
        send_slack_message(message)
        <span class="hljs-keyword">raise</span> err

    print(<span class="hljs-string">f"Version code <span class="hljs-subst">{bundle_response[<span class="hljs-string">'versionCode'</span>]}</span> has been uploaded"</span>)

    <span class="hljs-comment">#   Create a track request to upload the bundle to the beta track</span>
    track_response = service.edits().tracks().update(
        editId=edit_id,
        track=<span class="hljs-string">'beta'</span>,
        packageName=package_name,
        body={<span class="hljs-string">u'releases'</span>: [{
            <span class="hljs-string">u'versionCodes'</span>: [str(bundle_response[<span class="hljs-string">'versionCode'</span>])],
            <span class="hljs-string">u'status'</span>: <span class="hljs-string">u'completed'</span>,
        }]}
    ).execute()

    print(<span class="hljs-string">"The bundle has been committed to the beta track"</span>)

    <span class="hljs-comment">#   Create a commit request to commit the edit to BETA track</span>
    commit_request = service.edits().commit(
        editId=edit_id,
        packageName=package_name
    ).execute()

    print(<span class="hljs-string">f"Edit <span class="hljs-subst">{commit_request[<span class="hljs-string">'id'</span>]}</span> has been committed"</span>)

    message = <span class="hljs-string">f"Version code <span class="hljs-subst">{bundle_response[<span class="hljs-string">'versionCode'</span>]}</span> has been uploaded from the bucket <span class="hljs-subst">{bucket_name}</span>.\nEdit <span class="hljs-subst">{commit_request[<span class="hljs-string">'id'</span>]}</span> has been committed"</span>
    send_slack_message(message)

    <span class="hljs-keyword">return</span> {
        <span class="hljs-string">'statusCode'</span>: <span class="hljs-number">200</span>,
        <span class="hljs-string">'body'</span>: json.dumps(<span class="hljs-string">'Successfully executed the app bundle release to beta'</span>)
    }
</code></pre>
<p>The Lambda above will use the <code>googleapiclient</code> library and its discovery module to build the URL for Google Play's Publishing API. </p>
<p>Next, the Lambda will download the service account key from the bucket you set up earlier. You'll have to make sure you specify the correct bucket names.</p>
<p>Depending on whether the upload succeeds or fails, we want a Slack message to go out. Add the Slack WebHook URL from the previous section into the environment variables for the Lambda. The function above uses Python's <code>os</code> module to get access to the environment variable and post the message to Slack.</p>
<p>If your Lambda fails, it might be because your Lambda does not have permissions to access the S3 bucket where the key for your Google Play service account is stored. In that case, you will see an error message indicating this. </p>
<p>To fix this, you simply need to add the relevant permissions to your Lambda role.</p>
<p>Here is the policy you will need to add:</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"Version"</span>: <span class="hljs-string">"2012-10-07"</span>,
    <span class="hljs-attr">"Statement"</span>: [
        {
            <span class="hljs-attr">"Effect"</span>: <span class="hljs-string">"Allow"</span>,
            <span class="hljs-attr">"Action"</span>: [
                <span class="hljs-string">"s3:GetObjectVersion"</span>,
                <span class="hljs-string">"s3:GetBucketVersioning"</span>,
                <span class="hljs-string">"s3:GetBucketAcl"</span>,
                <span class="hljs-string">"s3:GetObject"</span>,
                <span class="hljs-string">"s3:GetBucketTagging"</span>,
                <span class="hljs-string">"s3:GetBucketLocation"</span>,
                <span class="hljs-string">"s3:GetObjectVersionAcl"</span>
            ],
            <span class="hljs-attr">"Resource"</span>: [
                <span class="hljs-string">"arn:aws:s3:::arn:aws:s3:::your-bucket-name-with-service-account-key"</span>
            ]
        }
    ]
}
</code></pre>
<p>Replace the ARN for the bucket with the relevant one for your account and you should be good to go.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>So, there you have it. It definitely wasn't easy and there's a lot of moving parts, but this is an automation that will save you a lot of time and effort. </p>
<p>If you're part of a team that is frequently releasing new app updates, you don't want to be hindered by the absence of one person whose job it is to release the update.</p>
<p>Building this sort of automation makes your CI/CD workflow a lot smoother and more robust.</p>
<p>If you're interested in blogs like this, you can read more at https://redixhumayun.github.io or follow me on Twitter.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Secure Your Android App – Four Security Best Practices Every Android Dev Should Know ]]>
                </title>
                <description>
                    <![CDATA[ By Andrej Kovacevic If you've been following the news lately, you may have noticed a worrisome tech trend. The frequency and severity of cybersecurity attacks are exploding.  We've seen news of a sprawling hack involving the SolarWinds management pla... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-secure-your-android-app/</link>
                <guid isPermaLink="false">66d45da5c7632f8bfbf1e3dd</guid>
                
                    <category>
                        <![CDATA[ android app development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Application Security ]]>
                    </category>
                
                    <category>
                        <![CDATA[ cybersecurity ]]>
                    </category>
                
                    <category>
                        <![CDATA[ information security ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Fri, 28 May 2021 16:01:05 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2021/05/android-app-coding-security.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Andrej Kovacevic</p>
<p>If you've been following the news lately, you may have noticed a worrisome tech trend. The frequency and severity of cybersecurity attacks are exploding. </p>
<p>We've seen news of a sprawling hack involving the SolarWinds management platform. That attack may have compromised the systems of <a target="_blank" href="https://www.npr.org/2021/04/16/985439655/a-worst-nightmare-cyberattack-the-untold-story-of-the-solarwinds-hack">over 18,000 SolarWinds customers</a>. </p>
<p>Then, we found out that a zero-day flaw in Microsoft's ubiquitous Exchange email server caused affected systems to get hacked <a target="_blank" href="https://www.zdnet.com/article/microsoft-exchange-server-attacks-theyre-being-hacked-faster-than-we-can-count-says-security-company/">faster than experts could count</a>.</p>
<p>For the average software developer (like me), that's downright scary. After all, if big multibillion-dollar companies like those aren't immune to disastrous hacks, what chance does my code have to stay safe?</p>
<p>Well, it turns out that there's some good news and bad news on that front. </p>
<p>The good news is that the types of large-scale attacks we've seen lately are just that – large scale. In other words, the kinds of groups (or nation-states) that can carry out attacks like those don't care all that much about the kinds of small software projects I work on.</p>
<p>But the bad news is this: there are plenty of small-scale hackers who are more than happy to make your life as a developer miserable. And they will do it if you let them. </p>
<p>But you can do something about it. You can make it your mission to put as many roadblocks in their way as possible. By covering the most likely angles of attack on your software, you can increase the odds that a would-be attacker will pass your code by and look for an easier target.</p>
<p>Now, I've already written extensively about a variety of software security topics. But because I happen to be working on an Android application at the moment – and <a target="_blank" href="https://thehackernews.com/2020/05/stranhogg-android-vulnerability.html">because Android is a favorite target of hackers</a> – I've decided to share four of the most important Android app security principles you can include in your apps on the platform. I make them a focus of my security efforts, and you should too. </p>
<h2 id="heading-1-protect-your-apps-transport-layer">1. Protect Your App's Transport Layer</h2>
<p>One of the first things an attacker will look for when targeting an Android app is to see if they can intercept any of the data passing between it and your server's backend. </p>
<p>By eavesdropping on those communications, they can tell an awful lot about your app. And if you're really unlucky, they might even be able to use the data to figure out how to impersonate your app and gain inappropriate access to server-side data.</p>
<p>So, step one in your effort to secure an Android app is simple: protect its data transfer layer by employing strong encryption. </p>
<p>You can do this by making use of protocols like <a target="_blank" href="https://www.freecodecamp.org/news/openssl-command-cheatsheet-b441be1e8c4a/">SSL</a> and <a target="_blank" href="https://www.freecodecamp.org/news/what-is-tls-transport-layer-security-encryption-explained-in-plain-english/">TLS</a>, which are simple to add to your code and are very difficult to compromise. And once you have a solution in place, take the time to do some <a target="_blank" href="https://www.freecodecamp.org/news/threat-modeling-goran-aviani/">threat modeling</a> to decide if you've done enough to protect your app's traffic.</p>
<p>If you are dealing with particularly sensitive data, you may even want to go a little further and build a VPN-type solution right into your app. If you've never implemented a VPN in an Android app before, you can read up on the basics of adding one <a target="_blank" href="https://developer.android.com/guide/topics/connectivity/vpn">here in the Android developer guide</a>. </p>
<p>If you go this route, you can even advertise the feature to your customer (or end-users if you're building something public). Android VPN apps are so commonplace these days that the mere mention of the feature will let users know that you're serious about their data's security – and that's always a nice PR bonus.</p>
<h2 id="heading-2-make-authentication-bulletproof">2. Make Authentication Bulletproof</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/05/app-authorization-bulletproof-2fa.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Image: thodonal / Adobe Stock</em></p>
<p>Besides your app's data streams, the next most common attack vector to eliminate is any weakness in its authentication methods. </p>
<p>The trouble with doing that is your users. By that, I mean that you need to make your app's authentication process as secure as you can without making it so onerous that your users will revolt (and if I had a dollar for every time a client asked me if 2FA was <em>really</em> necessary, I'd be retired by now).</p>
<p>Regardless of how many times you have to answer the question, though, 2FA is both necessary and worth implementing. </p>
<p>On top of that, you also need to pay attention to how you handle things like key exchanges. At a minimum, you should be using AES encryption to keep those transactions secure.</p>
<p>And last but not least, you should make certain to <a target="_blank" href="https://auth0.com/learn/token-based-authentication-made-easy/">use token-based security</a> to authenticate legitimate requests from your app to its backend. That makes making such requests difficult enough that even if an attacker finds a way to view a live data stream, they'll have no way to use that information to launch an attack.</p>
<h2 id="heading-3-guard-against-code-injection">3. Guard Against Code Injection</h2>
<p>The next thing you should worry about is the public-facing parts of your app. Because most apps are interactive, they will provide users the ability to input data in one form or another. This can be through text-input fields like forms, or through direct data uploads for exchanging things like documents and pictures. </p>
<p>And every time you add a user input feature, you should take great pains to make sure that nobody can use them against you.</p>
<p>The first way to address this is to use proper input validation. If your app expects specific text in a field, make sure it won't accept anything else. You can do this by adding a <a target="_blank" href="https://www.simplifiedcoding.net/android-form-validation-tutorial/">pre-built text validation module</a> or by <a target="_blank" href="https://www.freecodecamp.org/news/form-validation-with-html5-and-javascript/">building your own</a> (if you have the time and inclination). </p>
<p>If you plan to let a user upload pictures or other specific files, you must include an ability for the app to scan the uploaded file to make sure it is what it claims to be. </p>
<p>Again, this is possible using any number of pre-built modules. I tend to favor <a target="_blank" href="https://github.com/arimus/jmimemagic">JMimeMagic</a> because it can handle a variety of MIME types, but you can use whatever solution works best in the context of your project.</p>
<h2 id="heading-4-minimize-and-secure-client-side-storage">4. Minimize and Secure Client-Side Storage</h2>
<p>Last but not least, you should strive to build your app with the smallest local data footprint that's feasible to get the job done. </p>
<p>That's because any data you store on a client device is outside of your control and is therefore vulnerable to external threats. </p>
<p>If a user's device gets compromised by any attack that yields access to the data stored on it, you may inadvertently give the attacker a roadmap to steal information from your app.</p>
<p>Now, storing no client-side data is near-impossible – especially if you need your app to retain some or all of its functionality offline. So, at the very least, make sure any and all client-side data you store remains encrypted at all times. </p>
<p>And, try to eliminate storage of any data that could pose a problem if an attacker got their hands on it. Things like contact lists, message longs, or any kind of usage history come to mind.</p>
<p>And beyond storage, you'll want to test your app to see that it isn't vulnerable to memory leaks that might expose critical data. </p>
<p>To do that, you should get familiar with tools like the <a target="_blank" href="https://www.zaproxy.org/">OWASP Zed Attack Proxy (ZAP)</a>. It will help you to find any vulnerabilities in your app's memory usage before attackers can use them against you. </p>
<p>It can be a bit complex to get the hang of, but there's plenty of good documentation and a fantastic user community that will help you along.</p>
<h2 id="heading-locked-up-tight">Locked Up Tight</h2>
<p>I wish I could tell you that any app built using these four principles will be invulnerable to every threat. But attackers work to find new flaws every minute of every day, and unless you make countering them your job too, you're unlikely to find a perfect solution. </p>
<p>But by covering these major attack vectors, you will at least make their work hard enough that it won't be worth it to try (unless your app contains something of immense value locked up inside). </p>
<p>And at the end of the day, that's all anyone can ask of you as a developer unless they're willing to pay you handsomely to guard against every possible threat – and hey, wouldn't that be nice?</p>
<p><em>Featured image: Arthur Shevtsov / Adobe Stock</em></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Style and Theme an App With Jetpack Compose ]]>
                </title>
                <description>
                    <![CDATA[ In this article, we will learn how to style and theme an application in Jetpack Compose. Compose is a new UI framework for Android (though Desktop and Web support is being developed), which replaces the old XML-based View system. While still in beta ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-style-and-theme-an-app-with-jetpack-compose/</link>
                <guid isPermaLink="false">66d460cb47a8245f78752abd</guid>
                
                    <category>
                        <![CDATA[ Android ]]>
                    </category>
                
                    <category>
                        <![CDATA[ android app development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Kotlin ]]>
                    </category>
                
                    <category>
                        <![CDATA[ UI Design ]]>
                    </category>
                
                    <category>
                        <![CDATA[ User Interface ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Ryan Michael Kay ]]>
                </dc:creator>
                <pubDate>Mon, 22 Mar 2021 13:48:12 +0000</pubDate>
                <media:content url="https://cdn-media-2.freecodecamp.org/w1280/6054c45f687d62084bf67e41.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In this article, we will learn how to style and theme an application in Jetpack Compose.</p>
<p>Compose is a new UI framework for Android (though Desktop and Web support is being developed), which replaces the old XML-based View system.</p>
<p>While still in beta release as of writing this article, I do not expect this particular part of the library to change drastically for the stable release.</p>
<p>Topics include:</p>
<ul>
<li><p>A brief recap of the XML approach</p>
</li>
<li><p>How to migrate from the XML-based colors, themes, and typography (font) system</p>
</li>
<li><p>How to set up light and dark themes for your apps in only a few lines of code</p>
</li>
<li><p>How to use your new Kotlin-based style information in your composables</p>
</li>
<li><p>How to style Text Composables specifically</p>
</li>
</ul>
<p>Before proceeding, it is important that you understand what a composable is. I will not be stopping to explain that concept here, as I already have in <a target="_blank" href="https://www.freecodecamp.org/news/jetpack-compose-beginner-tutorial-composables-recomposition/">this article</a>.</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/81r-vwPxlaw" 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>
<p> </p>
<h2 id="heading-how-we-used-to-style-android-apps-using-xml-resources">How We Used to Style Android Apps Using XML Resources</h2>
<p>As usual, I like to share with you the motivations behind, and a bit of history on, these topics. In case you do not care, feel free to skip to the next section where we get into the practical stuff.</p>
<h3 id="heading-android-resources">Android Resources</h3>
<p>The Android app resources system is something which the Android team deserves a high five for, at least in my opinion. But like every design decision, a feature in one situation becomes a flaw in another situation.</p>
<p>To be specific, one of the greatest challenges for both platform and application developers alike is to create what I will call <strong>localized resources</strong>. I am referring to the challenge of building apps which:</p>
<ul>
<li><p>Display text and graphics in a variety of different languages and alphabets</p>
</li>
<li><p>Look and feel proportionate to a wide variety of form factors (dimensions, densities, and so on.)</p>
</li>
</ul>
<p>Those are just two common examples – there are plenty more. The resource system gives us a place where app developers can provide localized resources which the platform can select for at compile time. This saves us having to write that boilerplate code ourselves.</p>
<h3 id="heading-feature-or-flaw">Feature or Flaw?</h3>
<p>While I would never want to manage the boilerplate code necessary for localized string resources myself, that does not mean I enjoy writing XML.</p>
<p>In fact, <strong>there are very few things I would prefer to do in XML</strong> over a modern, idiomatic, and elegant language such as Kotlin or Swift. Personal preference aside, there is a more technical reason why XML resources are not always ideal.</p>
<p>Please note that this is not meant as a criticism of the platform developers/engineers. It is merely an observation of how design decisions always have benefits and costs.</p>
<p>In order to integrate our XML-based resources into our JVM-based application code, we must necessarily have <strong>layers of translation</strong> (compilation) and <strong>platform bridges</strong> (APIs). This can present difficulties for both platform and application developers.</p>
<p>Two common problems I ran into were:</p>
<ul>
<li><p>I want access to a resource in a place where I do not want tight coupling to the platform APIs which provide the resource</p>
</li>
<li><p>I have to write some ridiculous boilerplate code just to change the look of a View (that is, override something defined within resource styles and themes)</p>
</li>
</ul>
<p>The <strong>root problem</strong> for everyone involved is <strong>tight coupling</strong> to the View system and the Android resource system (which are themselves tightly coupled together).</p>
<p>For the platform developers, this means they have to build on top of, or work around gigantic and old codebases. Add that they must also try to have new features work on older Android OS versions, and that becomes a very thankless job.</p>
<p>The result for us application developers is most often a lot of <strong>boilerplate code,</strong> some <strong>hacky workarounds</strong> for things which intuitively seem like they should be one-liners. Not to mention the main API for getting these resources is <code>Context</code>, which is a class you really do not want to leak in memory.</p>
<p><strong>Enter Jetpack Compose.</strong></p>
<h2 id="heading-how-to-set-up-themes-colors-and-fonts-with-jetpack-compose">How to Set Up Themes, Colors, and Fonts with Jetpack Compose</h2>
<p>With our review of the old system out of the way, let's explore a much prettier and simpler way to style and theme an Android application. I said I would keep this practical, but allow one point.</p>
<p>Since we will be doing that work in Kotlin, it means one very important thing: Both we and the platform developers are much less bound by translation (compilation) and API bridges (Android's <code>R</code> class and <code>Context</code>) between XML and the JVM.</p>
<p>In simple terms, this means <strong>much less boilerplate code</strong>, and <strong>much more control at runtime</strong>.</p>
<p>For the practical part of this article, my suggestion to you is to follow this process in the order I explain it. I have structured it in the order I follow when writing this code in a new App.</p>
<h3 id="heading-how-to-replace-colorsxml-resources-with-kotlin-compose">How to Replace Colors.xml Resources with Kotlin Compose</h3>
<p>If you have not already decided upon a color scheme for your application, I suggest that you use the various resources available on the official Material Design website. Try out:</p>
<ul>
<li><p>The <a target="_blank" href="https://material.io/design/color/the-color-system.html#tools-for-picking-colors">color palettes</a></p>
</li>
<li><p>The <a target="_blank" href="https://material.io/resources/color/">color tool</a></p>
</li>
</ul>
<p>If you plan to support light and dark app themes (explained shortly), try to select a color scheme which supports white text and a color scheme which supports black text.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/color_text_palettes.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Example of light and dark color schemes.</em></p>
<p>Create a file called something like <a target="_blank" href="https://github.com/BracketCove/GraphSudokuOpen/blob/master/app/src/main/java/com/bracketcove/graphsudoku/ui/Color.kt">Color.kt</a> (the name does not matter) and fill it with immutable <strong>val</strong>ues:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">import</span> androidx.compose.ui.graphics.Color

<span class="hljs-keyword">val</span> primaryGreen = Color(<span class="hljs-number">0XFF00bc00</span>)
<span class="hljs-keyword">val</span> primaryCharcoal = Color(<span class="hljs-number">0xFF2b2b2b</span>)
<span class="hljs-keyword">val</span> accentAmber = Color(<span class="hljs-number">0xFFffe400</span>)

<span class="hljs-keyword">val</span> textColorLight = Color(<span class="hljs-number">0xDCFFFFFF</span>)
<span class="hljs-keyword">val</span> textColorDark = Color(<span class="hljs-number">0xFFf3f3f3</span>)
<span class="hljs-keyword">val</span> gridLineColorLight = Color.Black
<span class="hljs-comment">//...</span>
</code></pre>
<p>You can either use a predefined value like <code>Color.Black</code> or supply your own ARGB Hex value.</p>
<p>Since ARGB Hex is just a bunch of jargon to describe what the heck "<code>0XFF00bc00</code>" means, let me translate:</p>
<ul>
<li><p>First two characters <code>0x</code> tell the compiler that this is a hexadecimal number</p>
</li>
<li><p>Second two characters , "<code>FF</code>" or "<code>DC</code>", represent Transparency/Opaqueness/<strong>A</strong>lpha in Hex</p>
</li>
<li><p>The remaining six character pairs represent <strong>R</strong>ed, <strong>G</strong>reen, and <strong>B</strong>lue</p>
</li>
</ul>
<h3 id="heading-how-to-add-fonts-and-replace-the-fontfamily-attribute">How to Add Fonts and Replace the <code>fontFamily</code> Attribute</h3>
<p>Typography (fonts) is also very easy to manage. This is the kind of thing where Kotlin's <a target="_blank" href="https://kotlinlang.org/docs/functions.html#default-arguments">default arguments</a> are very useful.</p>
<p>Create a file called something like <a target="_blank" href="https://github.com/BracketCove/GraphSudokuOpen/blob/master/app/src/main/java/com/bracketcove/graphsudoku/ui/Type.kt">Type.kt</a> (the name still does not matter) and create <code>Typography</code> class...:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> typography = Typography(
    body1 = TextStyle(
        fontFamily = FontFamily.Default,
        fontWeight = FontWeight.Normal,
        fontSize = <span class="hljs-number">16</span>.sp
    ),

    button = TextStyle(
        fontFamily = FontFamily.Default,
        fontWeight = FontWeight.Bold,
        fontSize = <span class="hljs-number">32</span>.sp
    ),

    caption = TextStyle(
        fontFamily = FontFamily.Default,
        fontWeight = FontWeight.Normal,
        fontSize = <span class="hljs-number">12</span>.sp
    )
)
<span class="hljs-comment">//...</span>
</code></pre>
<p>...and some <code>TextStyle</code> classes:</p>
<pre><code class="lang-kotlin"><span class="hljs-comment">//...</span>
<span class="hljs-keyword">val</span> mainTitle = TextStyle(
    fontFamily = FontFamily.Default,
    fontWeight = FontWeight.Light,
    fontSize = <span class="hljs-number">48</span>.sp,
    textAlign = TextAlign.Center
)

<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">dropdownText</span><span class="hljs-params">(color: <span class="hljs-type">Color</span>)</span></span> = TextStyle(
    fontFamily = FontFamily.Default,
    fontWeight = FontWeight.Normal,
    fontSize = <span class="hljs-number">32</span>.sp,
    textAlign = TextAlign.Center,
    color = color
)
<span class="hljs-comment">//...</span>
</code></pre>
<p>Whether you provide public functions or values (I advise against using <code>**var**</code> here) is up to your individual preference and current requirements.</p>
<h3 id="heading-how-to-create-an-app-theme-in-jetpack-compose">How to Create an App Theme in Jetpack Compose</h3>
<p>The last thing you need to configure before using your theme in your composables is a <code>MaterialTheme @Composable</code>. I have mine, and along with my light and dark color palettes in a file called <a target="_blank" href="https://github.com/BracketCove/GraphSudokuOpen/blob/master/app/src/main/java/com/bracketcove/graphsudoku/ui/GraphSudokuTheme.kt">GraphSudokuTheme</a>:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">import</span> androidx.compose.foundation.isSystemInDarkTheme
<span class="hljs-keyword">import</span> androidx.compose.material.MaterialTheme
<span class="hljs-keyword">import</span> androidx.compose.material.darkColors
<span class="hljs-keyword">import</span> androidx.compose.material.lightColors
<span class="hljs-keyword">import</span> androidx.compose.runtime.Composable
<span class="hljs-keyword">import</span> androidx.compose.ui.graphics.Color

<span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> LightColorPalette = lightColors(
    primary = primaryGreen,
    secondary = textColorLight,
    surface = lightGrey,
    primaryVariant = gridLineColorLight,
    onPrimary = accentAmber,
    onSurface = accentAmber
)

<span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> DarkColorPalette = darkColors(
    <span class="hljs-comment">//main background color</span>
    primary = primaryCharcoal,
    <span class="hljs-comment">//used for text color</span>
    secondary = textColorDark,
    <span class="hljs-comment">//background of sudoku board</span>
    surface = lightGreyAlpha,
    <span class="hljs-comment">//grid lines of sudoku board</span>
    primaryVariant = gridLineColorLight,
    onPrimary = accentAmber,

    onSurface = accentAmber

)

<span class="hljs-meta">@Composable</span>
<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">GraphSudokuTheme</span><span class="hljs-params">(
    darkTheme: <span class="hljs-type">Boolean</span> = isSystemInDarkTheme()</span></span>,
    content: <span class="hljs-meta">@Composable</span> () -&gt; <span class="hljs-built_in">Unit</span>
) {

    MaterialTheme(
        colors = <span class="hljs-keyword">if</span> (darkTheme) DarkColorPalette <span class="hljs-keyword">else</span> LightColorPalette,
        typography = typography,
        shapes = shapes,
        content = content
    )
}
</code></pre>
<p>Since you should already be familiar with what a composable is (I gave you fair warning), the only new thing here is <code>darkTheme: Boolean = isSystemInDarkTheme()</code>.</p>
<p>To give a simplified explanation, <code>isSystemInDarkTheme()</code> is a call which asks any compatible Android device for the user's preference of a light or dark theme.</p>
<p>It <strong>returns a boolean value</strong> which we can use in a Ternary (Conditional) Assignment expression such as <code>colors = if (darkTheme) DarkColorPalette else LightColorPalette</code>.</p>
<p>That is actually it. Colors, Fonts, and two Themes defined in a few minutes.</p>
<h2 id="heading-how-to-use-a-theme-in-compose">How to Use a Theme in Compose</h2>
<p>It is now time to use this Theme in your app. In this app, which only has two primary screens, I just use an <a target="_blank" href="https://github.com/BracketCove/GraphSudokuOpen/blob/master/app/src/main/java/com/bracketcove/graphsudoku/ui/activegame/ActiveGameActivity.kt">Activity</a> as a <strong>container</strong> for my composables:</p>
<pre><code class="lang-pgsql"><span class="hljs-keyword">class</span> NewGameActivity : AppCompatActivity(), NewGameContainer {
    //...
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //...

        setContent {
            GraphSudokuTheme {
                NewGameScreen(
                    onEventHandler = logic::onEvent,
                    viewModel
                )
            }
        }
        //...
    }
</code></pre>
<p>Wherever you find yourself calling <code>setContent {}</code>, my suggestion for beginners is to immediately place your Theme composable inside of it. Doing so will cause the style information to <strong>cascade/inherit to each nested composable</strong>.</p>
<p>You are done! Almost.</p>
<h2 id="heading-how-to-override-styles-and-themes">How to Override Styles and Themes</h2>
<p>If you can help it, try to include any colors you will want in your light and dark palettes. This way, when you call <code>MaterialTheme.colors.&lt;Color&gt;</code>, the system will handle the conditional logic necessary to pick the appropriate palette:</p>
<pre><code class="lang-pgsql">@Composable
fun NewGameContent(
    onEventHandler: (NewGameEvent) -&gt; Unit,
    viewModel: NewGameViewModel
) {
    Surface(
        Modifier
            .wrapContentHeight()
            .fillMaxWidth()
    ) {
        ConstraintLayout(Modifier.background(MaterialTheme.colors.<span class="hljs-keyword">primary</span>)) { 
            //...
        }
        //...
      }
}
</code></pre>
<p>However, sometimes it is more suitable to write your own conditional logic...or I just got lazy. Fortunately Compose makes many such configurations available as properties:</p>
<pre><code class="lang-pgsql">@Composable
fun DoneIcon(onEventHandler: (NewGameEvent) -&gt; Unit) {
    Icon(
        imageVector = Icons.Filled.Done,
        tint = <span class="hljs-keyword">if</span> (MaterialTheme.colors.isLight) textColorLight 
        <span class="hljs-keyword">else</span> textColorDark,
        contentDescription = <span class="hljs-keyword">null</span>,
        modifier = Modifier
            .clickable(
            //...
            )
    )
}
</code></pre>
<p><code>MaterialTheme.Colors.isLight</code> returns a boolean indicating what mode they are in, then we can use another Ternary Assignment from there.</p>
<h3 id="heading-how-to-style-a-text-composable">How to Style a Text Composable</h3>
<p>Just set the <code>style</code> argument equal to one of your text styles (whether it comes from <code>MaterialTheme</code> or one of the styles within <code>Type.kt</code>):</p>
<pre><code class="lang-kotlin">Text(
    text = stat.toTime(),
    style = statsLabel.copy(
        color = <span class="hljs-keyword">if</span> (isZero) Color.White
        <span class="hljs-keyword">else</span> MaterialTheme.colors.onPrimary,
    fontWeight = FontWeight.Normal
    ),
    modifier = Modifier
        .wrapContentSize()
        .padding(end = <span class="hljs-number">2</span>.dp, bottom = <span class="hljs-number">4</span>.dp),
        textAlign = TextAlign.End
)
</code></pre>
<p><code>TextStyle</code> has its own <code>copy</code> function ready to go should you need to overwrite anything.</p>
<p>And that's it! You now know how to style and theme an application using Jetpack Compose. Thanks for reading :)</p>
<h3 id="heading-social"><strong>Social</strong></h3>
<p>You can find me on <a target="_blank" href="https://www.instagram.com/rkay301/">Instagram here</a> and on <a target="_blank" href="https://twitter.com/wiseAss301">Twitter here</a>.</p>
<h3 id="heading-here-are-some-of-my-tutorials-amp-courses"><strong>Here are some of my tutorials &amp; courses</strong></h3>
<p><a target="_blank" href="https://www.youtube.com/channel/UCSwuCetC3YlO1Y7bqVW5GHg">https://youtube.com/wiseass</a> <a target="_blank" href="https://www.freecodecamp.org/news/author/ryan-michael-kay/">https://www.freecodecamp.org/news/author/ryan-michael-kay/</a> <a target="_blank" href="https://skl.sh/35IdKsj">https://skl.sh/35IdKsj</a> (introduction to Android with Android Studio)</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Web App on Your Phone – Python & Pydroid Android App Tutorial ]]>
                </title>
                <description>
                    <![CDATA[ By Precious Oladele Hey there, how are you? I'm an 18 year old a backend developer and an aspiring Machine Learning Engineer. And in this article, I'm going to be writing about how to build a web app on your phone using Python 😁. Let's dive into it.... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-code-on-your-phone-python-pydroid-android-app-tutorial/</link>
                <guid isPermaLink="false">66d46089c7632f8bfbf1e467</guid>
                
                    <category>
                        <![CDATA[ Android ]]>
                    </category>
                
                    <category>
                        <![CDATA[ android app development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ mobile app development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Python ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Tue, 23 Feb 2021 19:00:00 +0000</pubDate>
                <media:content url="https://cdn-media-2.freecodecamp.org/w1280/6029aa6f0a2838549dcc582d.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Precious Oladele</p>
<p>Hey there, how are you? I'm an 18 year old a backend developer and an aspiring Machine Learning Engineer. And in this article, I'm going to be writing about how to build a web app on your phone using Python 😁. Let's dive into it.</p>
<p><img src="https://lh3.googleusercontent.com/TW_PdXBpgeWY4mLcHjFisp8e7Lk7Zsn1aFarXBkmvhEMP0XR5xzTDxhKcCizsrJ25rkPeMeWp7ctlG0Wy7_WFUS0bzT-JVJfpe6X_3OqnuE_df2q5B3KIrhl3EG47w3Dik3nIZE" alt="Image" width="720" height="1600" loading="lazy"></p>
<h2 id="heading-requirements"><strong>Requirements</strong></h2>
<p>The first thing we need here is an Android phone, at least version 6.0 and upward. But what if I told you that's all we need? Seems too good to be true.</p>
<p>Now the next thing we need to do is install a mobile application on our phone called pydroid3.  </p>
<p><img src="https://lh6.googleusercontent.com/fwM9r46B-sTofVF6IybUOhCTYoM8vSAPfumfBIiiL_wWLQpgdQgeR2B_2-N28NtNLaA7HvTtsZxlXdX03anCGvbt4QAlhQ_wyb9_AIfqG9L4ZMCjQOrKLg5OFPeZgKrJdqKEeb8" alt="Image" width="720" height="1600" loading="lazy"></p>
<p>As you can see, pydroid3 is a mobile application that lets you write Python on your mobile phone, so go ahead and install it. </p>
<p>The next thing we need to do is install Django. If you're not familiar with Django, please check out the <a target="_blank" href="https://www.djangoproject.com/">Django docs here</a>. </p>
<p>To install Django we need to open up the side navigation in our pydroid3 and select Terminal:</p>
<p><img src="https://lh6.googleusercontent.com/qO3djIyoXMZB8MzcIaFDmNddhB2t9XgLLgCzonR2CDkWJc0pXtap9gyGhqZfpv0uFCCvtYnynL6pAOfgactlDfpwoy03TfgqEoN2W_gAO7nOeoaLbySZEQkOSBuprhs67jc-Ens" alt="Image" width="720" height="1600" loading="lazy"></p>
<p>Then click on it and we should see this:</p>
<p><img src="https://lh3.googleusercontent.com/fTwNrfhCQpxKBFbrsN3B6dt4kFWvDUEJElZ897o-d21XbiYj42gZBLhiLMt7ffvSp44OQBrubC9jK62WvzneTlF-7WxcZZygHEqo4hmQ_9V42Pw4FgvdKB75EA3fv4q5nGZiL7k" alt="Image" width="720" height="1600" loading="lazy"></p>
<p>Once that is done all you need to do is type the following command:</p>
<pre><code class="lang-python">pip install django
</code></pre>
<p>And you should get the below. I am getting a "requirements satisfied" message because I already have it installed.</p>
<p><img src="https://lh6.googleusercontent.com/vYhoSBXGgvq2EiX6iXQ1RBLrvUe8zQHM3Aq65ZDIDRKSOoLqOrW5QQWE5yQ-ThbhzYTb6kwKf_jHzVoQ79wTbz2KZNv32oEBX1LjAFeMYaiQb4bebYOWii-h1W3EKQkTWvgA2_Q" alt="Image" width="720" height="1600" loading="lazy"></p>
<p>It has installed successfully, but let's confirm that. In the terminal type <code>django-admin</code> and hit enter.</p>
<p>You should get this:</p>
<p><img src="https://lh5.googleusercontent.com/jU17O6AVeFcy6rYMJ0mp_DEqnR9q51F-mhLZH1K7Ny8tixSeY7Xl8Jx27hBfxWfHPimt-1xCfO6x2AOlvYKYR92slC3sBwJNRg9uDJsJ6had0Yq1UTXZ5_CQvfCwwKneKCO_Gp4" alt="Image" width="720" height="1600" loading="lazy"></p>
<p>This means that it's actually installed already.</p>
<h2 id="heading-how-to-build-our-project">How to Build our Project</h2>
<p>So let's get started with building our project. Open up your terminal and type in the following command:</p>
<p><code>django-admin startproject myapp</code></p>
<p>This creates a Django application called myapp in your root folder.</p>
<p>Change directory to it by typing <code>cd myapp</code> and type in <code>python manage.py runserver</code>. Then you should get this:</p>
<p><img src="https://lh6.googleusercontent.com/fqO-uHACjoAXNQSrm5Pikjr-GQQkY3SbkE3G9Sgel1XZbePIf7hJaePd8yGxdrbYiyRdpeWCFUYBNo6iKMzTJqZg3s8j6CTGIoZYH-YJjT-tjHA0FCKtdGJEGzNy0Y8Qj5uTQrg" alt="Image" width="720" height="1600" loading="lazy"></p>
<p>Now the server has started. Next, to test it in the browser visit <a target="_blank" href="http://127.0.0.1:8000">127.0.0.1:8000</a>.</p>
<p><img src="https://lh3.googleusercontent.com/oqMFGPasUPLxuZoRqWHQ9mEhpitsg2XK8XPzLz_U-TvnFGzjkIaHVKUHXxwYkMDskLp_36F75BIAb-qv37bHccUESSZ9Jqa6XV7FGoWYk_IS8SfPYZfMTSNmo2ei-SMVa9cp_C8" alt="Image" width="720" height="1600" loading="lazy"></p>
<p>And boom! You should see that Django has been setup successfully.</p>
<p>The next thing we need to do is create our Django app. In Django, the project folder serves as the root while the app serves as the application itself. </p>
<p>To create a Django app, make sure you are still in the directory, then type <code>python manage.py startapp todo</code>. This creates a To-do app in our myapp project like this:</p>
<p><img src="https://lh5.googleusercontent.com/ycIZAg7VGJO4Auwc7z_bsx5CU19Ks-rfubo_3amBKgvO-HeHb2I7mQu_loWg6leR22dvlMGh0FPgO1_-anmVpEHO4O4dlQik-MfiqF7Dx9BmxuI6YBjqPMcv8S3czgVCyftBu80" alt="Image" width="720" height="1600" loading="lazy"></p>
<p>Then inside the todo folder we should see something like this:</p>
<p><img src="https://lh6.googleusercontent.com/Fc60wk6pMuEQ8JvIwfOK2E1zezR9n_N-8o_X__F-yr1D1yD0BEuV62G9zoqG5GQnyA0shbI79JvNs3Z-YHunEoUyZw7LAt2eumkyKjj9M39sDbmDgzZ_axvjRyVeyLZC5ohVQmY" alt="Image" width="720" height="1600" loading="lazy"></p>
<p>We will take a further look at the files when we begin working with them.</p>
<h2 id="heading-how-to-configure-our-application">How to Configure our Application</h2>
<p>Now let's make it possible for the app to be served by the Django project. First of all, open up your <code>settings.py</code> file in the myapp folder and add <code>'todo'</code> to the installed apps like this:</p>
<p><img src="https://lh4.googleusercontent.com/mxTcaRk-ON73sPH6XL31kvZmUJjfwn1knbhMgTJALeyx6l8A1umvtXjLazS34oTjbPZeivGGTe6w6zsEQ1QzhTjaYDJ5tHsbhpeyxAfrvABzGHrNsElcv7RR9kQZi_Tttt4PjIc" alt="Image" width="720" height="1600" loading="lazy"></p>
<p>Next we need to open up our <code>urls.py</code> and add the following to your code:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.urls <span class="hljs-keyword">import</span> path, include

path(<span class="hljs-string">''</span>, include(<span class="hljs-string">'todo.urls'</span>))
</code></pre>
<p><img src="https://lh6.googleusercontent.com/VEWQQt84a9DSeqmuT-LrE9EMmYnDnDfwJQQtJhI21WDTJf4EDaE212wj7BLoBX85Vjm90gFb6KsB6yGJ6PDyfgdTT9BL5hcmDZzNfIdHlceR40qJNVubaNKduXjA2viT7yqLJ14" alt="Image" width="720" height="1600" loading="lazy"></p>
<p>What actually happened was that I added <code>include</code> to the from the <code>django.urls</code> import path. And below the path (<code>admin</code>) , we created an empty path that points to or includes the <code>urls.py</code> file in the todo app directory. I hope that's clear.</p>
<p>Next we need to create a new file in the todo file directory named <code>urls.py</code> and add the following code in it:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.urls <span class="hljs-keyword">import</span> path
<span class="hljs-keyword">from</span> . <span class="hljs-keyword">import</span> views

urlpatterns = [
    path(<span class="hljs-string">''</span>, views.index, name=<span class="hljs-string">'home'</span>)
 ]
</code></pre>
<p><img src="https://lh6.googleusercontent.com/cmxgwJ5PeIXW_yGgo9AKaVK10pDjGFl26gML6VicCQVLtsiCiorL5tBahCMOxHG-1HlrocwbaVod5SN6DFJFIZ5n1gidGOfJdaGW_p8holylN4aCUb-2ankvfIQygHz6cjT2tgc" alt="Image" width="720" height="1600" loading="lazy"></p>
<p>We imported <code>path</code> from <code>Django.urls</code> and also imported <code>views</code> from the root directory. Then we created our <code>urlpatterns</code> with the first part as the root link. As you can see, the views.index just means that we're pointing this views to the index function in on <code>views.py</code> file. You will see how that works in a jiffy.</p>
<p>Let's go ahead to our <code>views.py</code> file and add some code.</p>
<p>At the top, import <code>HttpResponse</code> like this:</p>
<p><code>from django.http import HttpResponse</code></p>
<p>And add this below it:</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">index</span>(<span class="hljs-params">request</span>):</span>
    <span class="hljs-keyword">return</span> HttpResponse(<span class="hljs-string">'Hello'</span>)
</code></pre>
<p><img src="https://lh5.googleusercontent.com/QUpf-9cT8Z-dKXTkO1FTm2-IkjD3_NIfYSQCy_XlALUTnIg_XrrxKurZLAJ19DQCk1W5mqBx4Mo5IL9ycL5gGS_w4LyI4zXxSo8y23mNaZ2OodFg-qLEi3Dh2FN_m7ueYjPYrb4" alt="Image" width="720" height="1600" loading="lazy"></p>
<p>As you can see, we created the index function we called in our <code>urls.py</code> and we passed in a request parameter into it. Then we returned an <code>HttpResponse</code>. </p>
<p>But before the <code>HttpResponse</code> can work, we have to import it from <code>django.http import HttpResponse</code> – as simple as ABC. Let's try this: open up your terminal and cd into myapp and type <code>python manage.py runserver</code> to test it.</p>
<p><img src="https://lh3.googleusercontent.com/Tqb7c-adOuVHbyi-7XBQsv0HHJvxjUhcAZ3N4d5nkOEVNVwfSXxkENlD0l0UI3Jd4qLhO3k8ELDW6yG8yRiP0MmjkO0Q4TvGTYunQIBNgSMNrXxfI7ygMHeN2FtjoJc37mVIVr0" alt="Image" width="720" height="1600" loading="lazy"></p>
<p>As you can see, it returned the response. So next we will load our template HTML files.</p>
<p>To load our HTML files we need to create a folder like this in the todo directory in this order:</p>
<p><code>todo/templates/todo</code></p>
<p>In the todo directory, create a folder called templates. Inside that folder, create a folder called todo, as simple as that.</p>
<p>Then go ahead and create a simple HTML file called index.html and write this in it:</p>
<p><code>&lt;h1&gt;Hello world&lt;/h1&gt;</code></p>
<p>To load it, make your <code>views.py</code> code look like this:</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">index</span>(<span class="hljs-params">request</span>):</span>
    <span class="hljs-keyword">return</span> render(request, <span class="hljs-string">'todo/index.html'</span>)
</code></pre>
<p><img src="https://lh3.googleusercontent.com/mhirciumIf_FcO764txwH5MOMl40vkZ6f41c0oXFreX1UA2IiqQG9E42TfbUBCZto4xG6-vl0t5sQj3ID1FBk_gL074Rzm4pn5a8RmMsP7DuMZKYVi1KQg-Bk35yr1gJGiE2ukg" alt="Image" width="720" height="1600" loading="lazy"></p>
<p>Now instead of returning response we returned a render view that allows us to render our HTML template now, save this open up your terminal cd into myapp and run it. We should have this</p>
<p><img src="https://lh6.googleusercontent.com/NzW4_E80BNOtRq-E4qUg1GdvqHUUQQAxMAdUSGhxROCDkSUnzddSyX4E7Wz5_zPY29twa7D2PVmS85LYmCnzEAvgE-oU2MEk1mDeNhFW5FBuD2eAjDxpPkJfXiJAMEyk1uKZVkw" alt="Image" width="720" height="1600" loading="lazy"></p>
<p>As you can see it works well - on to the next step.</p>
<h2 id="heading-how-to-set-up-the-static-files">How to Set Up the Static Files</h2>
<p>Now to set up the static files, create a new folder in your todo directory and name it static. Inside that folder, create a folder and name it todo.</p>
<p>So it should be like this: <code>/static/todo/</code>.</p>
<p>In the todo directory, create a file and name it <code>main.css</code>. Then let's write a little styling in it:</p>
<pre><code class="lang-css"><span class="hljs-selector-tag">body</span> {
<span class="hljs-attribute">background-color</span>: red;
}
</code></pre>
<p>And save it.</p>
<p>Now let's re-edit our <code>index.html</code> file by writing this code:</p>
<pre><code class="lang-django">{% load static %}
&lt;!Doctype html&gt;
&lt;html&gt;
&lt;head&gt;
&lt;title&gt;My page&lt;/title&gt;
&lt;link rel="stylesheet" href="{% static 'todo/main.css' %}" &gt;
&lt;/head&gt;
&lt;body&gt;
Hello
&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p><img src="https://lh5.googleusercontent.com/Luboze-gNbfQkpTZVwOChtQKrQpC2eWnsTAE41f9mDdWaqaKtk2yYAV0uP3ufKE_EDrpfCoRvOFlHmLCJKucPNB_kQmZoaAZB5reCcW2wrddbsDbRPoIe2iacGLpFfLEcGYZEnA" alt="Image" width="720" height="1600" loading="lazy"></p>
<p>And now let's run it:</p>
<p><img src="https://lh5.googleusercontent.com/ARWYir-7j8-yF9yCzc3bNuW1ZyLKOG30iljprX4AJsnyIdYLtK_0Of7Uu4WJLuufoyRkVL5LnG8J-bepoBcRzm1e57AuaLmbA5iIyO_RY_KsKRVrsc0OfGmDbLOkT-FIZECwIyY" alt="Image" width="720" height="1600" loading="lazy"></p>
<p>If you've followed along with me, then you should have the above.</p>
<h2 id="heading-how-to-load-the-models-and-admin-panel">How to Load the Models and Admin Panel</h2>
<p>Now to load up our admin panel, we need to create a superuser. This is simple to do – just open up your terminal and cd into the myapp folder then type <code>python manage.py createsuperuser</code> and hit enter. You should see this:</p>
<p><img src="https://lh3.googleusercontent.com/PBTNq4SLyU4xMFsxh8wXuP0fUCnNKqL0zPiAqclNSPc4J7j4izPVgikXXQpaPqcPeSfFhrlQgf2xwyuhWz-s4RJWn1ftc5icsi9bt2QwmjKxjp3reecfmCxQ3GdVvE04HUAc8po" alt="Image" width="720" height="1600" loading="lazy"></p>
<p>We get an error because we haven't run <code>python manage.py migrate</code> yet. So type that and hit enter, and you should have something like this:</p>
<p><img src="https://lh3.googleusercontent.com/_oEnoQWnv1VRtZf8W60ZyfFVGQV-nFzYKX4oj45SLCLUlPNNyZOefRkIj8ROdoNdkgECWr4OKmxRVUsRZy2c27XwsM7wQ4_7xeJWnlzPrBFZ79t7J8zZXFJLtfDqJf1vrvtShjc" alt="Image" width="720" height="1600" loading="lazy"></p>
<p>Now type in <code>python manage.py createsuperuser</code> and hit enter:</p>
<p><img src="https://lh3.googleusercontent.com/t8Z8qo8Z3xNi9C86RjkiujHiS6en5b16eYPA5uMTfXAQYNpFjjuWaY_WEL0TrxLUlpaJJHzF143Vk0UuTQIzuD4GICQF4X1K2CF0vyb1ws33JN2W_FeyVu3xMOsn1posUZW0eFs" alt="Image" width="720" height="1600" loading="lazy"></p>
<p>Just fill in the credentials. The next thing we need to do is to run our server and point to 127.0.0.1:8000/admin.</p>
<p><img src="https://lh6.googleusercontent.com/Uoen79EV8PaEDuhnt2eBaCnnJAEzHhLydikTi8BOxUSZ9DrGp9GbtUk-Um7TmMDW64Zd0RbAkXja8RjyqiX58hlWdFyrzHTUVN0NCx93e9BOx36Va4ysCX7JyJRlEmdUBnbltuA" alt="Image" width="720" height="1600" loading="lazy"></p>
<p>Login and you will be directed to the dashboard:</p>
<p><img src="https://lh3.googleusercontent.com/C8A8OermBdrvdB_6NEHg2mFgkkuVBsePdfdmlNhulSw2m7Jkdhea_jdDFNQnbvVgqxJcXj-ftbcNmdR6nYImJC2AV9edqcPB5pkhUm0zvImzzzAokHZ4bDwYe4BPPvnXsK18Ng0" alt="Image" width="720" height="1600" loading="lazy"></p>
<p>Now that we have done the admin panel, let's work with the model (database). We'll create a model that collects contents. So open your <code>models.py</code> file and type in this code:</p>
<pre><code class="lang-python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Post</span>(<span class="hljs-params">models.Model</span>):</span>
    content = models.CharField(max_length=<span class="hljs-number">255</span>, null=<span class="hljs-literal">False</span>)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__str__</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-keyword">return</span> self.content
</code></pre>
<p><img src="https://lh4.googleusercontent.com/pyZXf_3jSGzz-sciBxAvb-ry4_TbZMnuHWWWAOl17LQ5hCi55DoKxzq0iYu6wuv8UsQhn3-w27GOzlt2N_9mpdKoHcZza9mWoBgselVQXC6bPD-ev-uTjlW1RbN1c2OussUgEpg" alt="Image" width="720" height="1600" loading="lazy"></p>
<p>We create a class that has the parameter <code>models.Model</code> and gives a variable <code>content</code> that holds a <code>CharField()</code>, more like a text field. Lastly we create a magic <code>str</code> that returns the name of the model instead of an object.</p>
<p>So next we need to run the migration. Open your terminal, cd into myapp, and type <code>python manage.py makemigrations</code>. You should see this:</p>
<p><img src="https://lh6.googleusercontent.com/UBbVNNg1d8jhPTusB-HRRoUsqFfxaZdJLzSIzNIt3P4kby8Tor4G8Bme1e-yq8mOLFgfrUh3nb6MC3BSaOUQDr68_tEmIRtQBS7N7Y66wTbXdMMg-0EJ0svM3tw3j9GLgquC_IU" alt="Image" width="720" height="1600" loading="lazy"></p>
<p>That means it has created the Post table in our database. Then also run <code>python manage.py migrate</code> which will result in the following:</p>
<p><img src="https://lh6.googleusercontent.com/VyQYel1QFdxc2D5oSOdDD6QPth2jVC5_CTj8SVyDo8pAusvl6qjH7XQUhmhbfXNLjdUiAc566pYTj0O2c-AsRHwVLeDo2xeOv1HWsldCwH1oxu3sM5WJTNOj9-fpZEOfVHMYZ6k" alt="Image" width="720" height="1600" loading="lazy"></p>
<p>This means that all is clear. Now to add it to the admin page, open up <code>admin.py</code> and type in this code:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> .models <span class="hljs-keyword">import</span> *

admin.site.register(Post)
</code></pre>
<p><img src="https://lh3.googleusercontent.com/jzqRK8kVE6raStmHC8jJoqr8oYOXhygDpe8hoN_JdSRiF3Mpes3_Evw83U0nMczqgAobIY8zp_Z6ve-xb3jv6x7uChFzvdTyDqDZysD2j0pKxiGu2-V9pkvH02HKAzBA2HZr6WQ" alt="Image" width="720" height="1600" loading="lazy"></p>
<p>We imported all model classes from the model and registered the post model in the admin panel. Now if we open the admin panel we should see the post and save some data.</p>
<p><img src="https://lh4.googleusercontent.com/E9gkvNmpFiCJg24zYj7GpLzsM8AsoGUkoZHcS1Z3bxMva_Z3Jov5Yy7UzbgU251laLwGGRWqaFK1iIrILblSyktYK42Q-fzgS6ihGf0LYxR0Zl9qvkmG7sneHM2KFRoSPDy2k3o" alt="Image" width="720" height="1600" loading="lazy"></p>
<p>Notice that it's now in the todo app list:</p>
<p><img src="https://lh3.googleusercontent.com/BSyVagLKFGvtINW-jnuhrXRoFdB87S5lGksH37z5uewqVCn_WBHP-eI8gF6BUoG56Dz-SnKUtRonFhNX--c23V07WfXhOxHmCmJ460cXAr__NjTAkvXB4JnxIXlbsQcRtDO0uNU" alt="Image" width="720" height="1600" loading="lazy"></p>
<p>After clicking on it you should see this:</p>
<p><img src="https://lh3.googleusercontent.com/4zxSgdVcDnDrpr6aIquG854x59GQb0ZMJ3D-YnAs-9EDR0EYwHl_HBAbrpPGGr7YLfWn0PjJA19aukrUcBbUMURpn4ofEGCwWF4541ee_-OKZQj_cWuv_yxWvUGYGOZfdzu6C90" alt="Image" width="720" height="1600" loading="lazy"></p>
<p>Then you can create a post if you like.</p>
<h2 id="heading-how-to-render-data-from-db-to-view">How to Render Data from DB to View</h2>
<p>Lastly we will fetch our data from the DB. To do so we need to update our <code>views.py</code> as follows:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> .models <span class="hljs-keyword">import</span> *

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">index</span>(<span class="hljs-params">request</span>):</span>
    content = Post.objects.all()
    context = {<span class="hljs-string">'content'</span>: content}
    <span class="hljs-keyword">return</span> render(request, <span class="hljs-string">'todo/index.html'</span>, context)
</code></pre>
<p><img src="https://lh4.googleusercontent.com/NHpq8LEAtu06ntzUodCuBZT86FS_u_TPphhlfZ-CiP5rFglQcjtRB0zUdK0jkz_udZeXRh8JNqdZOhRSfV9A69I63b8P5DtBGtQo44zmwufnGTaybAaWAL0yOn9T544_mdXaLN4" alt="Image" width="720" height="1600" loading="lazy"></p>
<p>It's as simple as that: we imported all from <code>models.py</code>, created a variable called <code>content</code>, and retrieved all the data from the table Post. Then we passed it as a dictionary to our view. So in our index.html to make it work just add this:</p>
<pre><code class="lang-django">{% for contents in content %}
{{content.content}}
{% endfor %}
</code></pre>
<p><img src="https://lh4.googleusercontent.com/4zgGmOcVBVa906mn0AVk0Vh9MbaFeYS0VUVoOC00Jw6wtR54S55BMPjz5t0_z2LTgbs9Ldpt3VOKcEjgxMhSE63xGu8XKSx2tWbKFYp2ndxHc31pcAMFdSturJqEy07ca_IYC1c" alt="Image" width="720" height="1600" loading="lazy"></p>
<p>Here, we wrote a loop using the templates tag and fetched all the data content. Now open your terminal, cd into myapp, and run the server to see the magic happen:</p>
<p><img src="https://lh5.googleusercontent.com/gKJf7AGR-0ZxOCeD_QKGffg4d-wpK0Lk8Z0Fkdj39Rj1V6dpWGf_KA1iBDJ2xE-Lq_zsJQHq6eIywPujAVmEk_R7e-Ug7ox94Rk5x212Bk7cBm0fHaMnGtqQM9zscDELygE1LvI" alt="Image" width="720" height="1600" loading="lazy"></p>
<p>It works, but let's confirm that it does:</p>
<p><img src="https://lh5.googleusercontent.com/IVjbVn-_3Exnnoq2s0pvHTeL2paWcqogzg1mp_Aj15GtXKqUPerrFDGZ-SjYKqpUX8Es1KGo0fSWoAOACLgri_LcT5oV7tkG6dtL2OestlnQC25OzFdYEhcyb0KPH3b12BBdJTU" alt="Image" width="720" height="1600" loading="lazy"></p>
<p>And the result should be the following:</p>
<p><img src="https://lh3.googleusercontent.com/jlYy4UCV3MJd-JytvGUBLgC20k3-cduvDQ2O3FIb9kAF7VgRyGxyqb_G1Mjiqis261HQS68uIJUk5I9ccFJBFL6Ht3LiePvprBcsqkSS9lZZzJ_cc2noxJm32GPp9ytsiYl7t2o" alt="Image" width="720" height="1600" loading="lazy"></p>
<p>Violà – it works fine. Lastly you can just add a line break so you can read it more clearly. And we're done!</p>
<p>Thank you for reading. If you want to go through an in-depth Django tutorial please visit my YouTube channel <a target="_blank" href="https://youtube.com/channel/UCLcHGKxbEO1XGVETXqzYXLA">Devstack</a> and subscribe.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ ADB Android Install Guide: Drivers and Commands ]]>
                </title>
                <description>
                    <![CDATA[ In this article, we will explore how you can use the ADB to gain some fine-grained control when you're installing, testing, diagnosing, and managing one or more devices and emulators. For my first few years as a software developer, primarily working ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/adb-android-install-guide-drivers-and-commands/</link>
                <guid isPermaLink="false">66d460c5a326133d12440a5b</guid>
                
                    <category>
                        <![CDATA[ Android ]]>
                    </category>
                
                    <category>
                        <![CDATA[ android app development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Android Studio ]]>
                    </category>
                
                    <category>
                        <![CDATA[ command line ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Ryan Michael Kay ]]>
                </dc:creator>
                <pubDate>Thu, 18 Feb 2021 16:48:06 +0000</pubDate>
                <media:content url="https://cdn-media-2.freecodecamp.org/w1280/60297d780a2838549dcc57f3.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In this article, we will explore how you can use the ADB to gain some fine-grained control when you're installing, testing, diagnosing, and managing one or more devices and emulators.</p>
<p>For my first few years as a software developer, primarily working with the Android SDK, I had no idea of what the Android Debug Bridge (ADB/adb) was, what it did, or when to use it.</p>
<p>Amusingly, it was not some professional goal which motivated me to learn about it initially. Rather it was my boot looping Nexus 6 which I desperately wanted to resurrect. For a problem like that, Android Studio and Gradle are about as useful as a waterproof tea bag.</p>
<p>I would also like to mention that this article has been written with <strong>two kinds of individuals in mind</strong>:</p>
<ul>
<li><p>Those who are familiar with CLI, Shell, Processes, and the Client-Server Model</p>
</li>
<li><p>Those who are not familiar with CLI, Shell, Processes, and the Client-Server Model</p>
</li>
</ul>
<p>For those in the first category, you may wish to skip the section titled: "<strong>How to Work With The ADB</strong>."</p>
<p>For those in the second category, I will assume you were like me as a Junior developer and know very little about CLIs, Shells, and the ADB. The first section is a soft introduction and glossary for some basic terms and ideas, explained in the simplest way I can manage.</p>
<h2 id="heading-preliminaries">Preliminaries</h2>
<p>Here, we will learn about some topics which are important if you want to understand how the ADB works and is used.</p>
<p>Some of you may have been scared away from learning command line tools in the past by sneering Vim enthusiasts or judgmental Unix System Administrators. As you will see, I freely admit that CLI is not ideal for how my brain works, so I think you might enjoy my take on the subject.</p>
<h3 id="heading-command-line">Command Line</h3>
<p>Simply put, a command line is an interface (way of sending/receiving information) to a computer which <strong>only uses lines of text</strong>.</p>
<p>It is important to understand that a command line interface (CLI) is not itself a program, but rather some programs will provide a CLI (and perhaps other interfaces such as a GUI as well).</p>
<p>At some point, you may have typed something into Windows Command Prompt (or MS-DOS if you are a 90s kid like me), Mac Terminal, or something like GNOME Terminal common on many Linux distributions. All of these are primarily used via a CLI.</p>
<p>The benefits and deficits of using a CLI depend largely on the individual using it, and what kind of problem they are trying to solve. <strong>I personally do not like using a CLI unless it is for something that I do almost every day</strong>.</p>
<p>My brain is simply not suited for memorizing obscure shorthand text commands (I had trouble learning to read as a kid for the same reason), so I must rely on a great deal of repetition-based implicit memory (muscle memory) and cheat sheets.</p>
<p>For those who are willing to put the time in even if it is a struggle (like I do), or those who are really quite good at remembering such things, <strong>you will likely learn to appreciate how much more efficient you can be within a CLI versus a GUI</strong>.</p>
<p>Many operations can be carried out in a fraction of the time it takes to point and click your way through various menus and screens. It is also possible to write scripts, which are files containing a series of text commands, that can increase your efficiency even further.</p>
<h3 id="heading-how-to-use-the-abd-shell">How to use the ABD Shell</h3>
<p>I will have to assume that you are familiar with the term Operating System (OS), which includes Android, iOS, Windows, Mac, Linux, and any other Unix-like system.</p>
<p>Why is this term relevant to the ADB? To give an explanation which prioritizes clarity over precision, the Android OS is based on Linux, and Linux is based on Unix.</p>
<p>As a result of this, we can use the ADB to get a hold of the Unix Shell for the device or emulator we are working with. This allows us a great deal of flexibility, capabilities, and control over the device or emulator by directly interacting with its shell.</p>
<p>A shell is a general term for the program which you use to interact with an OS. Just as a turtle shell provides protection and access to a turtle (and is the outermost layer), the shell of an OS both protects and provides access to the inner workings of the OS. Personally, I was quite surprised to learn that "Shell" was not some esoteric acronym.</p>
<p>Do not feel the need to overthink this term. If you are reading this on a computer of some kind, you used a shell to help you get here.</p>
<p>A shell can provide either or both a CLI or GUI. In either case you will use it to create/update/delete/move files, start other programs, and access the various services of the OS which are made available through the shell.</p>
<h3 id="heading-how-to-use-the-abd-client-and-abd-server">How to use the ABD Client and ABD Server</h3>
<p>Again, let us start with a slightly imprecise explanation which is hopefully easier to understand. I will correct this definition shortly, though.</p>
<p>Clients and Servers are both computers. The reason why we differentiate them in this way is based on their <strong>role</strong>. For example, your computer (whether it is a desktop, laptop, phone, or whatever else) is a Client of a freeCodeCamp Server, which <strong>serves</strong> you this HTML page.</p>
<p>In general, a client is <strong>something which uses something else</strong>, whereas a server is <strong>that which is being used</strong>. Do not overthink this term, as a Client-Server Model can describe a very large number of things both inside and outside of computing.</p>
<p>Now, when I said that Clients and Servers are both “computers”, that is not really true in the context that we will use these terms later on.</p>
<p>As programmers and engineers, we typically ought to think of Clients and Servers as being processes (<strong>a process is simply a running program</strong>).</p>
<p>This means that while a Client process and a Server process often do run on separate computers, it is also fine if they run on the same computer.</p>
<p>They will occupy distinct locations in the memory space of said computer, so effectively the only difference is that they will communicate using IPC (inter-process communication) as opposed to sending messages to each other through a network connection.</p>
<p>As we will see shortly, the ADB makes use of a Server process, which allows multiple developers (multiple clients) to manage multiple Android devices and/or emulators.</p>
<p>In an enterprise setting, this Server process would likely sit on a remote (communicated to through a network connection) computer, but we will set up a Server which is local to our Client. Doing that will be much simpler than you probably think it will be.</p>
<h3 id="heading-what-is-an-abd-daemon">What is an ABD Daemon?</h3>
<p>In case you skipped ahead, I already explained that a process is simply a running program. A Daemon is a process which runs in the background, which is to say that the user does not directly interact with it.</p>
<p>For example, if you open a web browser, then chances are that the actual work of managing the network connections required to connect to the Internet will be carried out by something like a NetworkManager Daemon (as opposed to the browser process itself).</p>
<p>Each Android device (physical or emulated), assuming it is configured properly, will have an ADB Daemon (adbd) which executes commands given to it by a Server process.</p>
<p>In short, when our Client issues a command to the Server, the Server will forward that command to the ADBD, which will execute it on the device.</p>
<h2 id="heading-how-to-use-adb-for-android-development">How to Use ADB for Android Development</h2>
<p>For the remainder of this article, we will explore the following topics:</p>
<ul>
<li><p>Drivers and configuration necessary to use the ADB on your system</p>
</li>
<li><p>Using the ADB with physical devices and emulators</p>
</li>
<li><p>Basic commands using the ADB’s CLI</p>
</li>
<li><p>A glance at more complicated usage using an Android device’s Shell via the ADB</p>
</li>
</ul>
<p>Before proceeding, you will want to establish what CLI tool you will be using to interact with the ADB. On Windows, I prefer using PowerShell, but Command Prompt would work too. For Linux and Mac, the default Terminal should work.</p>
<p>Feel free to use whatever gets the job done.</p>
<p>This article contains a very detailed explanation of the whole process, but I have prepared a video tutorial which covers it succintly here:</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/g___gGA9jn8" 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>
<p> </p>
<h3 id="heading-how-to-understand-the-cli-examples">How To Understand The CLI Examples</h3>
<p>This article contains many commands to be inputted to your preferred CLI tool. Any part of a given command which changes situationally will be written within angle brackets.</p>
<p><strong>Do not include the angle brackets in the CLI command you write.</strong></p>
<p>For example, if I wrote...:</p>
<p><code>adb pair &lt;ip-address&gt;:&lt;port&gt;</code></p>
<p>...you would substitute the angle brackets and name for the actual value, such as:</p>
<p><code>adb pair 192.168.0.1:5554</code></p>
<h3 id="heading-abd-drivers-amp-configuration">ABD Drivers &amp; Configuration</h3>
<p>Firstly, ensure that you have the latest (or at least a recent) version of the <a target="_blank" href="https://developer.android.com/studio/releases/platform-tools">Android SDK Platform-Tools</a>. If for some reason you do not use Android Studio (AS), click that link and download the standalone package for your respective OS.</p>
<p>If you have Android Studio, you can download or update this package using the SDK Manager.</p>
<p>There is typically a toolbar icon in AS to open the SDK Manager, but they like to change what it looks like practically every hotfix.</p>
<p>If you do not have luck finding it, go to <strong>File -&gt; Settings</strong> and in the search bar, type “SDK”, and search for the “Android SDK” menu item.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/as_sdk_manager.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Systems setting showing that Android SDK Platform Tools are installed</em></p>
<p>The next step changes depending on a number of variables. As discussed in the <strong>Preliminaries</strong> section, the ADB uses a Client-Server Model which allows a lot of flexibility in how you use the tool.</p>
<p>To be more specific, you may have:</p>
<ul>
<li><p>Multiple Clients interacting with a remote Server</p>
</li>
<li><p>A Server which is local (same computer) to one Client</p>
</li>
<li><p>A variety of physical devices and emulators hooked up to the same server</p>
</li>
</ul>
<p>Advanced configuration with multiple Clients and an exceedingly large number of devices is possible with the ADB, but outside of the scope of this article.</p>
<p>One Server can manage up to 16 emulators and as many physical devices as you would like (within reason), without requiring advanced configuration.</p>
<p>For the remainder of this article, the most we will work with is one physical device and one emulator for a single ADB server process.</p>
<h4 id="heading-how-to-configure-an-abd-emulator">How to Configure An ABD Emulator</h4>
<p>Most likely you do not need to make any further configurations, but it is possible you may need to enable <strong>Developer Options</strong> on your emulator. You will know very shortly if it is working properly when we get to our first few ADB commands.</p>
<p>If you do wish to enable this feature on your emulator, you will need to <a target="_blank" href="https://developer.android.com/studio/debug/dev-options">research</a> how to do that for your particular version of Android.</p>
<h3 id="heading-usb-debugging-how-to-configure-a-physical-device">USB Debugging – How to Configure a Physical Device</h3>
<p>If you are not planning to use a physical Android device, you can skip this section. However, it is worth noting that you may still need to enable Developer Options</p>
<p>In order to proceed, you will need to configure either USB Debugging or WiFi Debugging on your Android device and development machine.</p>
<p>In either case, start by enabling <strong>Developer Options</strong> on your device. You will need to <a target="_blank" href="https://developer.android.com/studio/debug/dev-options">research</a> how to do that for your particular version of Android.</p>
<h4 id="heading-usb-debugging">USB Debugging</h4>
<p>Ensure that you have enabled USB Debugging on the Android device via Developer Options. The link I shared above will describe that process, which tends to change somewhat across different versions of the Android OS.</p>
<p>Before proceeding, Windows users will need to <a target="_blank" href="https://developer.android.com/studio/run/oem-usb">download a USB Driver</a>. Ubuntu users also <a target="_blank" href="https://developer.android.com/studio/run/device">require some extra steps</a>. For Mac and Chrome OS, you should be good to go.</p>
<p>Once USB Debugging is enabled via Developer Options, connect your Android device via a USB cable.</p>
<h4 id="heading-wifi-debugging">WiFi Debugging</h4>
<p>If you happen to have multiple physical devices or a shortage of USB cables, then you may want to opt for WiFi Debugging.</p>
<p>Again, visit Developer Options on your Android device and enable Wireless debugging. It should prompt you about Allowing debugging on the network which the device is currently connected to, which you should allow (assuming that is the appropriate network).</p>
<p><strong>Time to start working with your CLI</strong>. First, you will need to locate the platform-tools directory (or folder – same thing) within your Android SDK installation directory.</p>
<p>Assuming you have Android Studio installed, a quick way to locate it via the app is to again go to File -&gt; Settings, then type “SDK” in the search bar. The “Android SDK” menu will show you where your SDK is installed, which will be the directory that should contain platform-tools.</p>
<p>In the example below, <strong>I copied the path to my Android SDK directory</strong>, and then opened an instance of Windows PowerShell. I then typed the following commands:</p>
<p><strong>Change Directory:</strong></p>
<pre><code class="lang-pgsql">cd &lt;<span class="hljs-type">path</span>-<span class="hljs-keyword">to</span>-SDK-directory&gt;
</code></pre>
<p><strong>List Files and Directories:</strong></p>
<pre><code class="lang-pgsql">ls
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/power_shell_cd_ls.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Next, I typed <code>cd platform-tools</code> to navigate to that directory. Note that the following steps assume you are using a device which is running Android OS 11 or higher.</p>
<p>If you are working with a device running Android 10 or lower, detailed instructions for that situation can be <a target="_blank" href="https://developer.android.com/studio/command-line/adb#wireless">found here</a>.</p>
<p>Once you are within the platform-tools directory, you are ready to pair an Android device to a development machine using the following steps:</p>
<ol>
<li><p>Within the Wireless debugging submenu in Settings -&gt; System -&gt; Developer options, select <strong>Pair device with pairing code</strong>.</p>
</li>
<li><p>Within your CLI tool which should be set to the platform-tools directory, enter the following command:</p>
</li>
</ol>
<p><code>adb pair &lt;IP address&gt;:&lt;Port&gt;</code></p>
<p>where both the IP address and the Port come from the dialogue on your Android device which popped up after selecting <strong>Pair device with pairing code</strong> (do not include the angle brackets).</p>
<p><strong>Note: You may need to prepend your call to adb with some other symbols or commands depending on which CLI tool you are using, your OS, and your access controls.</strong> For example, I had to type .\adb pair : using PowerShell on Windows.</p>
<ol start="3">
<li><p>Assuming things went well with your CLI, you will be prompted to enter the pairing code which was made visible in the same dialog on the Android device which gave you the IP address and Port number.</p>
</li>
<li><p>After entering the pairing code, you will know this operation was successful if you receive a message stating:</p>
</li>
</ol>
<p><code>Successfully paired to &lt;IP Address&gt;:&lt;Port&gt; [guid=&lt;Some GUID&gt;]</code></p>
<ol start="5">
<li>If you are using Windows or Linux, you will also need to run the following command using the IP Address &amp; Port which is visible from within the Wireless debugging preferences menu (not the dialogue which pops up after selecting Pair device with pairing code):</li>
</ol>
<p><code>adb connect &lt;IP Address&gt;:&lt;Port&gt;</code></p>
<p>after which you should receive a notification on the phone to indicate that you are connected.</p>
<h3 id="heading-how-to-use-the-adb-commands">How to Use The ADB: Commands</h3>
<p>Assuming you managed to properly configure your Android device and your development machine, you can now use the ADB tool.</p>
<p>Before proceeding, navigate to the directory which contains adb using a CLI tool (unless you just followed the steps in the previous section for setting up WiFi debugging).</p>
<p>Otherwise do so now, or check out that section for instructions on how to locate that folder.</p>
<h4 id="heading-how-to-see-which-devices-are-currently-connected-to-the-server">How to See Which Devices Are Currently Connected To The Server</h4>
<p>You can now start up an adb server by calling just about any command on the ADB except <code>adb kill-server</code>. Whether or not your server process is running, type in the following command:</p>
<p><code>adb devices</code></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/pwershell_devices-1.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>In the above screenshot, I first called <code>adb devices</code> when my Android phone was connected to the server. After killing the server via the <code>adb kill-server</code> command, I once again called devices which restarted the server.</p>
<p>Again, if an ADB server is not currently running, <strong>calling more or less any ADB command will start the server back up</strong> (except <code>adb kill-server</code>, of course). There is an explicit <code>adb start-server</code> command, but in practice I have never needed to use it.</p>
<p>Since the server was reset, devices did not return any items. Therefore, before moving to the next example I had to once again use the <code>adb pair</code> and <code>adb connect</code> (if on Windows or Linux) commands described in the previous section.</p>
<p>I have now fired up an emulator using PowerShell and the emulator program which is also located in a subdirectory of platform-tools called "emulator."</p>
<p>You may of course use the AVD Manager or Android Studio to start up an emulator to follow along with the example if you would like to.</p>
<p>If you have many connected devices, a useful option for the <code>adb devices</code> command is <code>-l</code>, which gives you more information about the devices.</p>
<p>Below you will see several entries which refer to my physical Android device, as well as an emulator which has been attached to a specific port:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/powershell_devices_list-1.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h4 id="heading-how-to-send-commands-to-a-specific-device">How to Send Commands To A Specific Device</h4>
<p>To avoid accidentally bricking my phone, I want to send commands to the emulator instead. To do this, I must prepend the <code>-s</code> option, followed by the serial number of the target device, before typing the command.</p>
<p>The serial number is the first set of characters which describes a connected device after using the devices command.</p>
<p>For example, the emulator’s serial number in this case is just the word emulator followed by the port which the emulator is currently attached to.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/powershell_devices_serial-1.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>The other red arrow points to the serial number for my phone (blocked out for obvious reasons).</p>
<p>Naturally, if you only have one device connected (whatever kind it is), you do not need to use the <code>-s</code> option.</p>
<h4 id="heading-install-an-apk-app-on-a-device">Install An APK (App) On A Device</h4>
<p>I am now going to install a test APK on the running emulator using the <code>adb install</code> command.</p>
<p>This is basically equivalent to having Android Studio and Gradle install a debug APK. As you will see, test APKs require the <code>-t</code> option after the install command:</p>
<p><code>adb -s &lt;device-serial-number&gt; install -t &lt;path-to-APK&gt;</code></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/powershell_install_test_apk.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><strong>Note: The Android OS requires that any APK must be signed before it can be installed</strong> (even if it is just a test/debug APK).</p>
<p>One solution is to build and run the app to be installed in Android Studio, which will sign it with a generated debug certificate. There are several other ways to sign such an APK which you can explore by visiting this <a target="_blank" href="https://developer.android.com/studio/publish/app-signing#debug-mode">link</a>.</p>
<h4 id="heading-what-else-can-adb-do">What Else Can ADB Do?</h4>
<p>Before we take a look at some more advanced usage of the ADB, I strongly encourage you to try the <code>adb --help</code> command. As is customary for most CLI based programs, the help command will print out documentation which describes the various commands and options of the tool.</p>
<p>I am happy to say that the documentation for the ADB is quite legible and useful, which is not always the case in CLI programs.</p>
<h2 id="heading-advanced-adb-usage-tips">Advanced ADB Usage Tips</h2>
<p>It would be a waste of time for both of us to cover every usage and command of the ADB in this article.</p>
<p>In case there is any confusion, using the ADB to install APKs and do many of the things which Android Studio and Gradle do for you is not something that I would recommend (unless you have a good reason to do so).</p>
<p>With that being said, there are plenty of things that the ADB can do which are either difficult or impossible to do without it.</p>
<p>In the preliminaries section, I mentioned that the ADB can be used to get a hook to the shell of the device. To finish this article off, we will look at how to use shell commands and where to find more information about them.</p>
<p>If you do not know what a shell is, you probably skipped the section above where I explained that.</p>
<h3 id="heading-how-to-use-the-abd-shell-1">How to Use the ABD Shell</h3>
<p>Sending a command to the device’s shell using the ADB is fairly simple. Remember that if you have multiple devices connected, follow it with <code>-s &lt;device-serial-number&gt;</code> to direct the command to a specific device.</p>
<p>To make a single shell command, we must use the <code>adb shell</code> command (big surprise, eh?), followed by the actual command we want to make on the device's shell:</p>
<p><code>adb shell ls</code></p>
<p>Output:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/powershell_shell_ls.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>As mentioned previously, the <code>ls</code> command displays a list of files and directories at the CLI's current directory. This happens to be the Android device's root directory until we move to a different one.</p>
<p>If you plan to be making many commands via the Shell, you can also start an interactive Shell session. This can be done via the simple command:</p>
<p><code>adb shell</code></p>
<p>While in an interactive Shell session, you can type Shell commands directly without further use of <code>adb shell &lt;command&gt;</code>.</p>
<p>Note that when you want to quit the interactive Shell session, you can do so by typing <code>exit</code> or hitting Ctrl + D.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/powershell_start_interactive_shell.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>There are a variety of different commands and utilities you can work with via the Shell. The ActivityManager (<code>am</code>) of an Android device can be particularly useful for testing different components (Activities, Services, BroadcastReceivers, to name a few) of an Android app under different circumstances.</p>
<p>Suppose we want to launch straight into a particular Activity, but this Activity is not designated as the launcher Activity in the manifest.</p>
<p>You will still need to add the <code>android:exported=”true”</code> attribute to each <code>&lt;activity/&gt;</code> entry in the manifest which you want to launch (assuming it is not already the launcher Activity).</p>
<p>After that you can use the following command to go straight to it:</p>
<p><code>am start -n &lt;app-package-id&gt;/&lt;activity-name&gt;</code></p>
<p>Note that the <code>&lt;activity-name&gt;</code> must include whichever packages, relative to the package-id, within which it is located. See the output below for an example of launching an Activity which sits within several packages.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/powershell_shell_start.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h3 id="heading-further-reading">Further Reading</h3>
<p>My goal in this article was to do my best to introduce, explain, and guide you through the usage of the ABD in my own words (insofar as that is possible).</p>
<p>At this point I would need to start making some very contrived examples or to simply carbon copy the documentation, neither of which are things that I am interested in doing.</p>
<p>Instead, I would like to encourage you to visit the <a target="_blank" href="https://developer.android.com/studio/command-line/adb#shellcommands">documentation</a>, and to have a brief look at some of the cool things you can do using tools like the Activity Manager, Package Manager, Policy Manager, and others.</p>
<h4 id="heading-you-can-get-in-touch-with-me-on-social-media-here"><strong>You can get in touch with me on social media here:</strong></h4>
<p><a target="_blank" href="https://www.instagram.com/wiseassbrand/">https://www.instagram.com/rkay301/</a><br><a target="_blank" href="https://www.facebook.com/wiseassblog/">https://www.facebook.com/wiseassblog/</a><br><a target="_blank" href="https://twitter.com/wiseass301">https://twitter.com/wiseass301</a><br><a target="_blank" href="http://wiseassblog.com/">http://wiseassblog.com/</a></p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
