<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged androiddevelopment</title>
<link>https://coderlegion.com/tag/androiddevelopment</link>
<description>Powered by Question2Answer</description>
<item>
<title>The Night My Pipeline Rejected 43.8% of Everything — and Was Right</title>
<link>https://coderlegion.com/31505/the-night-my-pipeline-rejected-43-8-of-everything-and-was-right</link>
<description>&lt;p&gt;Last month, my automated software-ingestion pipeline did something terrifying: it rejected &lt;strong&gt;43.8% of all updates&lt;/strong&gt; in a single run. Forty-three point eight percent. My first instinct was that the pipeline was broken. It wasn't. It was the only thing in the room doing its job.&lt;/p&gt;
&lt;p&gt;I run an open software catalog. Every few days, an automated pipeline pulls new releases from vendors, GitHub, and F-Droid, verifies them, and publishes them. That night it pulled the fire alarm on nearly half the catalog &amp;mdash; two SHA-256 hash mismatches (Google Chrome and CCleaner, of all things) and twelve signature mismatches across apps like Obsidian, Tor, and Ventoy.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s what I learned fixing it, distilled into the checklist I now run before any binary goes live.&lt;/p&gt;
&lt;h2&gt;1. START WITH THE HASH &amp;mdash; BUT KNOW WHAT IT CAN&amp;rsquo;T TELL YOU&lt;/h2&gt;
&lt;p&gt;Every release I publish carries a SHA-256 checksum, recomputed at download time. It&amp;rsquo;s the cheapest integrity check in existence:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sha256sum installer.exe
# compare against the publisher's published value&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the hash doesn&amp;rsquo;t match, stop. Don&amp;rsquo;t install it, don&amp;rsquo;t &amp;ldquo;try it anyway.&amp;rdquo; A mismatched hash means the file changed in transit &amp;mdash; or it isn&amp;rsquo;t the file the publisher shipped. That night, Chrome and CCleaner&amp;rsquo;s hashes didn&amp;rsquo;t match their expected values. The pipeline refused them, and rightly so: a hash mismatch is never a rounding error.&lt;/p&gt;
&lt;p&gt;But a hash answers only one question: &lt;em&gt;did this file change?&lt;/em&gt; It says nothing about &lt;em&gt;who made it&lt;/em&gt;. That&amp;rsquo;s what signatures are for.&lt;/p&gt;
&lt;h2&gt;2. VERIFY THE SIGNATURE, NOT JUST THE CHECKSUM&lt;/h2&gt;
&lt;p&gt;A checksum tells you the file is intact. A signature tells you it came from the right key. For Android packages, the tooling is free:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;apksigner verify --print-certs app.apk&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The critical step most people skip: check that the certificate &lt;strong&gt;fingerprint&lt;/strong&gt; matches the publisher&amp;rsquo;s known key. The first time you see a new fingerprint on a familiar app, treat it as suspicious until proven otherwise. For desktop software, GPG serves the same role:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gpg --verify installer.exe.sig installer.exe&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The pattern is identical everywhere: fetch the artifact, fetch the signature, verify against the publisher&amp;rsquo;s public key. And if a project ships binaries with no signatures at all? That&amp;rsquo;s a data point about the project &amp;mdash; not a reason to skip verification.&lt;/p&gt;
&lt;h2&gt;3. ENROLLMENT IS THE HALF OF VERIFICATION EVERYONE FORGETS&lt;/h2&gt;
&lt;p&gt;Here&amp;rsquo;s the mistake that cost me a week. My pipeline verified every APK signature &lt;em&gt;correctly&lt;/em&gt; &amp;mdash; and still rejected twelve apps. The parser wasn&amp;rsquo;t broken. The cryptography was fine. The problem: &lt;strong&gt;I had never enrolled those signers in the first place.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Signature verification has two halves, and I was only doing one:&lt;/p&gt;
&lt;p&gt;1. Is this signature cryptographically valid? &lt;em&gt;(the tool answers this)&lt;/em&gt;&lt;br&gt;
2. Do I recognize this signer? &lt;em&gt;(only my allowlist answers this)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Twelve apps were signed by keys my system had simply never recorded &amp;mdash; new maintainers, rotated keys, corporate renames. My favorite: a publisher whose legal name changed by a single dot (&amp;ldquo;Dynalist Inc.&amp;rdquo; vs &amp;ldquo;Dynalist Inc&amp;rdquo;), which my strict string match treated as a stranger. The pipeline was doing its job; my trust store was incomplete.&lt;/p&gt;
&lt;p&gt;The fix is boring and essential. Keep an explicit registry of known-good signers per package, keyed by certificate fingerprint:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# trust_store.json &amp;mdash; enrolled signers, one entry per package
{
  &quot;com.example.app&quot;: {
    &quot;fingerprints&quot;: [&quot;AB:CD:EF:12:34...&quot;],
    &quot;enrolled_on&quot;: &quot;2026-10-13&quot;,
    &quot;note&quot;: &quot;Key rotation announced in v4.2 changelog&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When a new fingerprint appears, that&amp;rsquo;s an &lt;strong&gt;event&lt;/strong&gt;, not an error &amp;mdash; investigate it once, enroll it deliberately, and move on. Auto-trusting unknown signers defeats the entire purpose.&lt;/p&gt;
&lt;h2&gt;4. REJECT LOUDLY &amp;mdash; WITH A THRESHOLD YOU CHOSE IN ADVANCE&lt;/h2&gt;
&lt;p&gt;A verification system that silently accepts everything is decoration. Mine fails the entire ingestion run closed if more than 15% of updates get rejected. That number is deliberate: a sudden spike means either the upstream world changed (keys rotated, publishers restructured) or my pipeline broke. Either way, I want a human looking at it before anything ships.&lt;/p&gt;
&lt;p&gt;The exact threshold matters less than the principle: decide in advance what &amp;ldquo;too many failures&amp;rdquo; looks like, and make the pipeline &lt;strong&gt;stop&lt;/strong&gt; instead of shrugging. Quiet failures are how bad binaries reach users. My 43.8% night looked like a disaster and was actually the system working exactly as designed &amp;mdash; it just needed a human to enroll the new signers and re-run.&lt;/p&gt;
&lt;h2&gt;5. DECIDE IN ADVANCE WHAT TO DO WITH THE UNVERIFIABLE&lt;/h2&gt;
&lt;p&gt;Not everything is signable. Some legitimate tools ship unsigned binaries &amp;mdash; portable builds, indie utilities, projects too small for signing infrastructure. You have three honest options: reject them, allowlist them explicitly, or verify them another way (reproducible builds, publisher-published hashes fetched over HTTPS).&lt;/p&gt;
&lt;p&gt;I allowlist them &amp;mdash; explicitly, per binary, with a written reason. What I never do is let an unsigned binary through on a technicality. &amp;ldquo;The check didn&amp;rsquo;t apply&amp;rdquo; is not the same as &amp;ldquo;the check passed,&amp;rdquo; and conflating the two is how exceptions quietly become the rule. Ventoy&amp;rsquo;s unsigned binary, for example, sits in my allowlist with a note explaining why &amp;mdash; visible, reviewable, deliberate.&lt;/p&gt;
&lt;h2&gt;THE CHECKLIST&lt;/h2&gt;
&lt;p&gt;Before any binary goes live:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SHA-256 matches the publisher&amp;rsquo;s published value (recomputed at download, not trusted from a mirror)&lt;/li&gt;
&lt;li&gt;Signature verifies against the publisher&amp;rsquo;s known key&lt;/li&gt;
&lt;li&gt;Signer fingerprint is in the enrolled registry &amp;mdash; new fingerprints get investigated, never auto-trusted&lt;/li&gt;
&lt;li&gt;Rejection rate for the run is under threshold, or the whole run stops&lt;/li&gt;
&lt;li&gt;Unsigned artifacts are explicitly allowlisted with a written reason &amp;mdash; never silently passed&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of this is exotic. It&amp;rsquo;s plumbing. But it&amp;rsquo;s the plumbing between a stranger&amp;rsquo;s server and your users&amp;rsquo; machines, and every piece of it earns its place the first time it catches something. Mine caught something on a random Tuesday night, rejected nearly half the catalog, and was right.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31505/the-night-my-pipeline-rejected-43-8-of-everything-and-was-right</guid>
<pubDate>Sat, 17 Oct 2026 09:46:03 +0000</pubDate>
</item>
<item>
<title>Android CI/CD for Beginners — Part 1: Introduction</title>
<link>https://coderlegion.com/28201/android-ci-cd-for-beginners-part-1-introduction</link>
<description>&lt;p&gt;Stop building everything manually. Learn how modern Android teams automate their development workflow&lt;/p&gt;
&lt;p&gt;This series is designed to provide a complete understanding of CI/CD for Android development. In Part 1, we’ll introduce the fundamentals of CI/CD, explore what it is, why it matters, and how it’s used in Android app development.&lt;/p&gt;
&lt;p&gt;CI/CD (Continuous Integration and Continuous Delivery/Deployment) is a set of practices that automate repetitive tasks in the software development process, such as building, testing, and releasing applications.&lt;/p&gt;
&lt;p&gt;Instead of manually performing these tasks every time you make a change, a CI/CD pipeline executes them automatically whenever predefined events occur, such as pushing code to a Git repository.&lt;/p&gt;
&lt;p&gt;How can CI/CD automate tasks in Android development?&lt;br&gt;
CI/CD automates many of the repetitive tasks involved in the Android development lifecycle. Instead of running these steps manually every time you make a change, they can be executed automatically whenever code is pushed to your repository.&lt;/p&gt;
&lt;p&gt;With a CI/CD pipeline, your Android app can be automatically:&lt;/p&gt;
&lt;p&gt;Built using Gradle.&lt;br&gt;
Analyzed for code quality with Android Lint.&lt;br&gt;
Tested by running unit and instrumentation tests.&lt;br&gt;
Packaged into a signed or unsigned APK or Android App Bundle (AAB).&lt;br&gt;
Distributed to testers or prepared for release to the Google Play Store.&lt;br&gt;
You might be wondering: Why do we need to automate these tasks?&lt;/p&gt;
&lt;p&gt;In real-world Android projects, multiple developers often work on the same codebase. Every new feature, bug fix, or improvement is usually submitted as a pull request (PR).&lt;/p&gt;
&lt;p&gt;Before those changes are merged, the team needs to ensure they meet the project’s quality standards and don’t introduce bugs or break existing functionality.&lt;/p&gt;
&lt;p&gt;Manually building the app, running lint checks, executing tests, and verifying every pull request can quickly become repetitive and time-consuming, especially as the project grows.&lt;/p&gt;
&lt;p&gt;This is where CI/CD becomes valuable. Whenever a pull request is opened or updated, a CI pipeline can automatically build the app, run lint checks, execute unit and instrumentation tests, and report whether all checks have passed.&lt;/p&gt;
&lt;p&gt;If any step fails, developers are notified immediately, allowing issues to be fixed before the changes are merged.&lt;/p&gt;
&lt;p&gt;Once all the automated checks succeed, the CD pipeline can take over by generating an APK or App Bundle and distributing it to testers or preparing it for release.&lt;/p&gt;
&lt;p&gt;This eliminates many manual steps, speeds up the development process, and helps maintain a stable and reliable codebase.&lt;/p&gt;
&lt;p&gt;Common CI/CD Terminology&lt;br&gt;
Before we start building our first CI/CD pipeline, it’s important to understand some common terms you’ll encounter throughout this series. These concepts are not exclusive to Android development, they’re used across many CI/CD platforms. We’ll explore how these ideas come together in practice in the upcoming sections.&lt;/p&gt;
&lt;p&gt;Pipeline&lt;br&gt;
A pipeline is the complete automated process that runs after a specific event, such as pushing code to a Git repository or opening a pull request. A pipeline can build your app, run tests, generate an APK, and even distribute it to testers — all without manual intervention.&lt;/p&gt;
&lt;p&gt;Think of a pipeline as a sequence of automated tasks executed in a predefined order.&lt;/p&gt;
&lt;p&gt;Workflow&lt;br&gt;
In GitHub Actions, a pipeline is defined using a workflow. A workflow is a YAML (.yml or .yaml) file stored inside the .github/workflows directory of your project.&lt;/p&gt;
&lt;p&gt;It describes what should happen, when it should happen, and how it should be executed.&lt;/p&gt;
&lt;p&gt;A repository can contain multiple workflows, each responsible for different tasks. For example, one workflow may run tests for every pull request, while another creates a release build whenever a new version is published.&lt;/p&gt;
&lt;p&gt;Event (Trigger)&lt;br&gt;
A workflow doesn’t run on its own — it needs an event, also known as a trigger.&lt;/p&gt;
&lt;p&gt;Common triggers include:&lt;/p&gt;
&lt;p&gt;Pushing code to a branch (push)&lt;br&gt;
Opening or updating a pull request (pull_request)&lt;br&gt;
Manually starting a workflow (workflow_dispatch)&lt;br&gt;
Creating a release (release)&lt;br&gt;
Whenever one of these events occurs, GitHub Actions starts the corresponding workflow.&lt;/p&gt;
&lt;p&gt;Runner&lt;br&gt;
A runner is the machine responsible for executing your workflow.&lt;/p&gt;
&lt;p&gt;GitHub provides hosted runners running Linux, Windows, and macOS. For Android projects, Linux runners are commonly used because they provide a fast and cost-effective environment for building Android applications.&lt;/p&gt;
&lt;p&gt;You can also configure your own self-hosted runner if you need custom hardware or software.&lt;/p&gt;
&lt;p&gt;Job&lt;br&gt;
A job is a collection of related tasks executed on the same runner.&lt;/p&gt;
&lt;p&gt;For example, one job might build the Android app, while another runs unit tests. Multiple jobs can run sequentially or in parallel, depending on how the workflow is configured.&lt;/p&gt;
&lt;p&gt;Step&lt;br&gt;
A step is a single task inside a job.&lt;/p&gt;
&lt;p&gt;Examples of steps include:&lt;/p&gt;
&lt;p&gt;Checking out the repository&lt;br&gt;
Setting up Java&lt;br&gt;
Installing Android dependencies&lt;br&gt;
Running Gradle commands&lt;br&gt;
Uploading the generated APK&lt;br&gt;
A job is simply a sequence of steps executed one after another.&lt;/p&gt;
&lt;p&gt;Action&lt;br&gt;
An Action is a reusable piece of automation that performs a specific task.&lt;/p&gt;
&lt;p&gt;Instead of writing everything from scratch, you can use actions created by GitHub or the community.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;p&gt;actions/checkout downloads your repository onto the runner.&lt;br&gt;
actions/setup-java installs and configures Java.&lt;br&gt;
actions/upload-artifact uploads build outputs such as APKs or test reports.&lt;br&gt;
Actions make workflows shorter, cleaner, and easier to maintain.&lt;/p&gt;
&lt;p&gt;Artifact&lt;br&gt;
An artifact is a file generated during a workflow that you want to keep after the workflow finishes.&lt;/p&gt;
&lt;p&gt;In Android projects, common artifacts include:&lt;/p&gt;
&lt;p&gt;APK files&lt;br&gt;
Android App Bundles (AAB)&lt;br&gt;
Lint reports&lt;br&gt;
Unit test reports&lt;br&gt;
Code coverage reports&lt;br&gt;
Artifacts can be downloaded later for testing, debugging, or distribution.&lt;/p&gt;
&lt;p&gt;GitHub Actions&lt;br&gt;
Throughout this series, we’ll use GitHub Actions as our CI/CD platform.&lt;/p&gt;
&lt;p&gt;GitHub Actions is GitHub’s built-in automation service that allows you to create workflows using YAML files. It automatically responds to events in your repository, runs your workflows on runners, and reports the results all directly within GitHub.&lt;/p&gt;
&lt;p&gt;Now that you’re familiar with these core concepts, we’ll start creating our first Android CI workflow in the next part of this series.&lt;/p&gt;
&lt;p&gt;That all it for now…&lt;br&gt;
Lets stop here for now as this is already too much if your just starting out&lt;/p&gt;
&lt;p&gt;Now that you understand the purpose of CI/CD and the terminology behind it, it’s time to build your first pipeline.&lt;/p&gt;
&lt;p&gt;In Part 2, we’ll set up GitHub Actions, create our first workflow, and automatically build an Android application whenever code is pushed to GitHub.&lt;/p&gt;
&lt;p&gt;👋Bye!!!&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/28201/android-ci-cd-for-beginners-part-1-introduction</guid>
<pubDate>Tue, 22 Sep 2026 17:12:07 +0000</pubDate>
</item>
<item>
<title>Google Play’s 2027 app quality requirements</title>
<link>https://coderlegion.com/27177/google-plays-2027-app-quality-requirements</link>
<description>&lt;p&gt;Google Play is introducing new requirements for memory usage, code optimization, and automatic login restoration when users change Android devices. Announced on August 26, 2026, the changes take effect in February and April 2027.&lt;/p&gt;
&lt;h3 id=&quot;memory-requirements-from-february-2027&quot;&gt;Memory requirements from February 2027&lt;/h3&gt;
&lt;p&gt;Play will assess dynamic memory usage, measured as anonymous RSS plus swap, and memory held by bitmaps. The thresholds vary by process state, with separate dynamic memory limits for apps and games across device RAM tiers. The Android vitals tables list the applicable limits.&lt;/p&gt;
&lt;p&gt;For phones and tablets, Play uses P90 measurements and the last 28 days of data. Evaluation includes available data from Android 13 onward. These requirements therefore reach beyond apps running on Android 17. Measurement scope.&lt;/p&gt;
&lt;p&gt;Android 17 also introduces limits enforced by the operating system. Excessive memory use can trigger additional compression work and eventually process termination. Teams should examine memory use across foreground, background, and cached states.&lt;/p&gt;
&lt;h3 id=&quot;dex-optimization-from-february-2027&quot;&gt;DEX optimization from February 2027&lt;/h3&gt;
&lt;p&gt;Qualifying uploads must meet a minimum of 25% coverage in each of three areas: optimization, shrinking, and obfuscation. The requirement applies to apps with more than 10 MB of DEX code and games with more than 50 MB. These are DEX code thresholds, not total download sizes. DEX requirements.&lt;/p&gt;
&lt;p&gt;Google recommends R8, although another optimizer can meet the requirement. Review optimization settings and keep rules, then inspect the percentages reported for the actual bundle uploaded to Play Console.&lt;/p&gt;
&lt;h3 id=&quot;login-restoration-from-april-2027&quot;&gt;Login restoration from April 2027&lt;/h3&gt;
&lt;p&gt;Apps offering optional or mandatory login must support automatic restoration of the user’s authenticated state when they transfer to another Android device. Google calls this Zero-Tap Sign-In. Games are currently exempt. Migration requirement.&lt;/p&gt;
&lt;p&gt;The requirement covers phones and tablets. Apps without accounts are unaffected, and guests should remain guests. Block Store integrations qualify only if they successfully restore login and are in production by September 30, 2026. Other exceptions and exemption criteria are detailed in the technical requirements.&lt;/p&gt;
&lt;p&gt;The recommended implementation uses Restore Credentials through Credential Manager. It includes creating a restore key for an authenticated user, handling restoration with the app’s backend, and deleting the key on logout. Plan for both app and backend changes.&lt;/p&gt;
&lt;h3 id=&quot;checks-to-schedule-now&quot;&gt;Checks to schedule now&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Inspect Android vitals memory metrics by device RAM tier and process state, including behavior after leaving the app.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Review the uploaded release bundle’s DEX size and optimization results. Test critical flows after changing R8 rules.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Audit the device migration flow with the authentication team and test both device transfer and cloud restoration.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Google says apps that miss the requirements may face reduced store visibility and publishing capabilities. Further enforcement details are still to come. Check the &lt;a rel=&quot;nofollow&quot; href=&quot;https://android-developers.googleblog.com/2026/08/app-quality-memory-optimization-secure-onboarding.html&quot;&gt;official announcement&lt;/a&gt; and &lt;a rel=&quot;nofollow&quot; href=&quot;https://support.google.com/googleplay/android-developer/answer/17492799?hl=en&quot;&gt;technical requirements&lt;/a&gt; when planning releases.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27177/google-plays-2027-app-quality-requirements</guid>
<pubDate>Mon, 14 Sep 2026 15:41:55 +0000</pubDate>
</item>
<item>
<title>Getting Started with Native Android: Kotlin &amp; Jetpack Compose</title>
<link>https://coderlegion.com/26446/getting-started-with-native-android-kotlin-jetpack-compose</link>
<description>&lt;p&gt;Hey devs! For anyone looking to get into native mobile development, I put together a starter guide on building Android apps using Kotlin and Jetpack Compose.&lt;/p&gt;
&lt;p&gt;Key takeaways:&lt;/p&gt;
&lt;p&gt;Transitioning from traditional imperative UI to modern declarative composables&lt;/p&gt;
&lt;p&gt;Managing UI state with remember and mutableStateOf&lt;/p&gt;
&lt;p&gt;Quick design cycles using live previews in Android Studio&lt;/p&gt;
&lt;p&gt;Read the full post on SunnyWriteUps! Are you currently using Jetpack Compose, KMP, or cross-platform frameworks for your mobile builds?&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://sunnyblog.apexmansunny.workers.dev/posts/get-started-with-kotlin/&quot;&gt;https://sunnyblog.apexmansunny.workers.dev/posts/get-started-with-kotlin/&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26446/getting-started-with-native-android-kotlin-jetpack-compose</guid>
<pubDate>Tue, 08 Sep 2026 04:14:28 +0000</pubDate>
</item>
<item>
<title>Eunify: Bridging the Continuity Gap Between Mac and Android</title>
<link>https://coderlegion.com/18702/eunify-bridging-the-continuity-gap-between-mac-and-android</link>
<description>&lt;p&gt;If you’ve ever lived deep within the Apple ecosystem, you know the magic of Continuity. The ability to copy a link on your iPhone and instantly paste it on your Mac, or use your phone’s camera as a wireless webcam, feels like living in the future. Apple built these features seamlessly into their OS, giving their users a cohesive multi-device experience.&lt;/p&gt;
&lt;p&gt;But what if you love your Mac, but prefer an Android phone? Out of the box, you get nothing. The wall around Apple's garden is tall, and cross-platform continuity has historically been clunky, relying on a patchwork of third-party apps that drain battery and feel decidedly un-magical.&lt;/p&gt;
&lt;p&gt;Eunify (&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/FredrickOdondi/eunify&quot;&gt;https://github.com/FredrickOdondi/eunify&lt;/a&gt;) changes that.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What is Eunify?&lt;/strong&gt;&lt;br&gt;
Eunify is an open-source &quot;Continuity Bridge&quot; designed specifically for users who split their digital lives between macOS and Android. It consists of a native macOS host app (built with Swift and SwiftUI) and an Android client (built with Flutter), tied together by a clever cloud relay system.&lt;/p&gt;
&lt;p&gt;The goal? To give Android users the exact same seamless, real-time continuity features that were previously exclusive to the Apple ecosystem.&lt;/p&gt;
&lt;p&gt;Bringing Apple Magic to Android&lt;br&gt;
Eunify doesn't just mimic one or two features; it aims to replicate the entire Continuity suite:&lt;/p&gt;
&lt;p&gt;Universal Clipboard: Copy text or a block of code on your Mac, and it instantly lands in your Android clipboard.&lt;br&gt;
Tab &amp;amp; URL Push: Drag a Chrome or Safari tab onto the Eunify Mac app, and it opens instantly on your phone. You can also send links in reverse, from your Android back to your Mac browser.&lt;br&gt;
Notification Mirroring: See your Android push notifications appear on your Mac in real-time, complete with actionable quick replies.&lt;br&gt;
File Transfer: Drag any file onto the Mac app to send it directly to your Android device via high-speed P2P WebRTC.&lt;br&gt;
Continuity Camera: Stream your Android phone's camera feed directly to your Mac to use as a wireless webcam.&lt;br&gt;
Biometric Mac Unlock: Tap your fingerprint on your Android phone to unlock your Mac remotely.&lt;br&gt;
Media Sync: See what's playing on your Android and control it right from your Mac desktop.&lt;br&gt;
How It Works Under the Hood&lt;br&gt;
Achieving this level of synergy across fundamentally different operating systems requires a robust technical foundation.&lt;/p&gt;
&lt;p&gt;The Handshake: It starts with a simple QR code. The macOS app generates a QR code encoding a unique room ID. Scanning this with the Android app connects both devices to the same Supabase Realtime (WebSocket) channel.&lt;br&gt;
Signaling: Standard events—like URL pushes, clipboard syncs, and notification mirroring—are securely transmitted as structured JSON payloads over the Supabase cloud relay.&lt;br&gt;
Peer-to-Peer Streaming: For heavy lifting (binary data like files and live camera frames), the WebSocket channel is used to negotiate a direct WebRTC DataChannel. This ensures fast, secure, peer-to-peer data streaming without routing massive payloads through a cloud server.&lt;br&gt;
Conquering Android Background Restrictions&lt;br&gt;
One of the biggest hurdles for an app like this is keeping the connection alive while the mobile app is in the background. Modern Android is notoriously aggressive about killing background processes to save battery.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Eunify solves this with a two-layer approach:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A Foreground Service keeps the Supabase WebSocket connection alive with a WakeLock.&lt;br&gt;
A separate Background Isolate written in Dart re-initializes the connection independently, handling message delivery even if the main UI is completely killed by the OS.&lt;br&gt;
(It even employs clever workarounds, like using FlutterForegroundTask to briefly trampoline the app to the foreground on Android 10+ just to bypass background clipboard-writing restrictions.)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Get Started&lt;/strong&gt;&lt;br&gt;
If you're tired of emailing yourself links and want to bring your Android device into your Mac's ecosystem, Eunify is open source and ready to try.&lt;/p&gt;
&lt;p&gt;Check out the code, read the full protocol design, and download the latest releases directly from the GitHub repository:&lt;/p&gt;
&lt;p&gt;Explore Eunify on GitHub: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/FredrickOdondi/eunify&quot;&gt;https://github.com/FredrickOdondi/eunify&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://apkpure.com/eunify/com.eunify.client&quot;&gt;https://apkpure.com/eunify/com.eunify.client&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Built with Swift, SwiftUI, Flutter, Dart, and Supabase.&lt;/p&gt;
</description>
<category>Discussions</category>
<guid isPermaLink="true">https://coderlegion.com/18702/eunify-bridging-the-continuity-gap-between-mac-and-android</guid>
<pubDate>Fri, 22 May 2026 17:24:01 +0000</pubDate>
</item>
<item>
<title>Refined Your Flutter Recipe App using Gemini AI</title>
<link>https://coderlegion.com/12910/refined-your-flutter-recipe-app-using-gemini-ai</link>
<description>&lt;p&gt;Hola..Back to article writing and I want to tell a story about my journey as Boarding House Citizen. Living in a boarding house alone, sometimes leaving leftover food can feel kind of unreal in a good way. Back at home, leftovers usually came with rules, reminders, or a bit of guilt, especially after big celebrations like &lt;em&gt;Eid Al-Fitr&lt;/em&gt; or &lt;em&gt;Christmas&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Here, it’s not like that at all. The fridge is shared, containers are labeled, and everyone knows that food can be shared if it’s meant to be. When I cook too much or can’t finish my meal, I just pack it up instead of forcing myself to eat more. Someone else might enjoy it later after a long day at work or a late night of studying. It just makes sense.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Food leftovers should not be wasted&lt;/strong&gt;, even though many people feel unsure what to cook with them. Leftover rice, vegetables, or bread can be turned into simple meals like fried rice, soup, or toast. Checking the fridge before cooking helps reduce waste. Using leftovers saves money, cut daily meal cost, and is good for the environment.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Now imagine you could build a real-life app that calculates and combines all your leftover food into one new recipe and shows the budget cost like you’ll never think of&lt;/strong&gt;? Now we’ll make it using flutter.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://blog.stackademic.com/refined-your-flutter-recipe-app-using-gemini-ai-46a11884e473&quot;&gt;Note: due to character limitation, see full article on my Medium&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/12910/refined-your-flutter-recipe-app-using-gemini-ai</guid>
<pubDate>Mon, 16 Mar 2026 03:43:11 +0000</pubDate>
</item>
<item>
<title>Android build types, product flavors, and variants from zero to production</title>
<link>https://coderlegion.com/12289/android-build-types-product-flavors-and-variants-from-zero-to-production</link>
<description>&lt;p&gt;How do you build different versions of the same app from one codebase?&lt;/p&gt;
&lt;p&gt;You use build types, product flavors, and variants!&lt;/p&gt;
&lt;p&gt;Let's explain the concept and dive into some examples.&lt;/p&gt;
&lt;h3 id=&quot;what-is-a-build-type&quot;&gt;What is a build type&lt;/h3&gt;
&lt;p&gt;A build type defines how the app is built.&lt;/p&gt;
&lt;p&gt;The most common build types are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;debug&lt;/li&gt;
&lt;li&gt;release&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Debug is what you use during development. It usually:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Is debuggable&lt;/li&gt;
&lt;li&gt;Has logging enabled&lt;/li&gt;
&lt;li&gt;Is signed with a debug key&lt;/li&gt;
&lt;li&gt;May include tools like LeakCanary&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Release is what you ship to users. It usually:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Is not debuggable&lt;/li&gt;
&lt;li&gt;Has code shrinking enabled&lt;/li&gt;
&lt;li&gt;Is signed with your real keystore&lt;/li&gt;
&lt;li&gt;Has logging disabled&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So build types describe technical behavior.&lt;/p&gt;
&lt;h3 id=&quot;what-is-a-product-flavor&quot;&gt;What is a product flavor&lt;/h3&gt;
&lt;p&gt;A product flavor defines what app you are building.&lt;/p&gt;
&lt;p&gt;Examples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;demo vs prod, different backend servers&lt;/li&gt;
&lt;li&gt;free vs paid, different features&lt;/li&gt;
&lt;li&gt;white label apps for different clients&lt;/li&gt;
&lt;li&gt;staging vs production&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Flavors describe business or product differences.&lt;/p&gt;
&lt;p&gt;You can think of flavors as different personalities of your app.&lt;/p&gt;
&lt;h3 id=&quot;what-is-a-variant&quot;&gt;What is a variant&lt;/h3&gt;
&lt;p&gt;A variant is the combination of:&lt;/p&gt;
&lt;p&gt;One build type&lt;br&gt;
One flavor from each flavor dimension&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;demoDebug&lt;/li&gt;
&lt;li&gt;demoRelease&lt;/li&gt;
&lt;li&gt;prodDebug&lt;/li&gt;
&lt;li&gt;prodRelease&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each of those is a fully buildable and installable app.&lt;/p&gt;
&lt;p&gt;When you click Run in Android Studio, you are running a specific variant.&lt;/p&gt;
&lt;h3 id=&quot;step-1-basic-android-app-setup&quot;&gt;Step 1: Basic Android app setup&lt;/h3&gt;
&lt;p&gt;Open your app module file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;app/build.gradle.kts
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Inside the android block, you typically have something like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;android {
    namespace = &quot;com.example.variants&quot;
    compileSdk = 35

    defaultConfig {
        applicationId = &quot;com.example.variants&quot;
        minSdk = 24
        targetSdk = 35
        versionCode = 1
        versionName = &quot;1.0&quot;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This defines your base app configuration.&lt;/p&gt;
&lt;h3 id=&quot;step-2-understanding-build-types-in-practice&quot;&gt;Step 2: Understanding build types in practice&lt;/h3&gt;
&lt;p&gt;Add build types inside the android block:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;android {

    buildTypes {
        debug {
            applicationIdSuffix = &quot;.debug&quot;
            versionNameSuffix = &quot;.debug&quot;
            isDebuggable = true

            buildConfigField(&quot;Boolean&quot;, &quot;LOGGING_ENABLED&quot;, &quot;true&quot;)
        }

        release {
            isMinifyEnabled = true
            isShrinkResources = true

            buildConfigField(&quot;Boolean&quot;, &quot;LOGGING_ENABLED&quot;, &quot;false&quot;)

            proguardFiles(
                getDefaultProguardFile(&quot;proguard-android-optimize.txt&quot;),
                &quot;proguard-rules.pro&quot;
            )
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What this does:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Debug builds have a different application id, so you can install them next to release.&lt;/li&gt;
&lt;li&gt;Debug builds expose a constant called LOGGING_ENABLED.&lt;/li&gt;
&lt;li&gt;Release builds enable code shrinking.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Use the generated constant in code:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;object LoggerConfig {
    val loggingEnabled = BuildConfig.LOGGING_ENABLED
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now your app behaves differently depending on build type.&lt;/p&gt;
&lt;h3 id=&quot;step-3-creating-your-first-flavor&quot;&gt;Step 3: Creating your first flavor&lt;/h3&gt;
&lt;p&gt;Now we introduce flavors.&lt;/p&gt;
&lt;p&gt;We will create two environments:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;demo&lt;/li&gt;
&lt;li&gt;prod&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;First, define a flavor dimension:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;android {

    flavorDimensions += &quot;environment&quot;

    productFlavors {
        create(&quot;demo&quot;) {
            dimension = &quot;environment&quot;
            applicationIdSuffix = &quot;.demo&quot;
            versionNameSuffix = &quot;.demo&quot;

            buildConfigField(
                &quot;String&quot;,
                &quot;API_BASE_URL&quot;,
                &quot;\&quot;https://api.demo.example.com\&quot;&quot;
            )
        }

        create(&quot;prod&quot;) {
            dimension = &quot;environment&quot;

            buildConfigField(
                &quot;String&quot;,
                &quot;API_BASE_URL&quot;,
                &quot;\&quot;https://api.example.com\&quot;&quot;
            )
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;demoDebug&lt;/li&gt;
&lt;li&gt;demoRelease&lt;/li&gt;
&lt;li&gt;prodDebug&lt;/li&gt;
&lt;li&gt;prodRelease&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each one has a different API base URL.&lt;/p&gt;
&lt;p&gt;Use it in your networking layer:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;object ApiConfig {
    val baseUrl: String = BuildConfig.API_BASE_URL
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No if statements required.&lt;/p&gt;
&lt;h3 id=&quot;step-4-source-sets-and-environment-specific-code&quot;&gt;Step 4: Source sets and environment specific code&lt;/h3&gt;
&lt;p&gt;Android supports separate folders per flavor.&lt;/p&gt;
&lt;p&gt;Structure example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;app/src/main&lt;/li&gt;
&lt;li&gt;app/src/demo&lt;/li&gt;
&lt;li&gt;app/src/prod&lt;/li&gt;
&lt;li&gt;app/src/debug&lt;/li&gt;
&lt;li&gt;app/src/release&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Suppose you want a developer menu only in demo.&lt;/p&gt;
&lt;p&gt;Create:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;app/src/demo/java/com/example/variants/FeatureFlags.kt
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;object FeatureFlags {
    const val showDeveloperMenu = true
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And in:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;app/src/prod/java/com/example/variants/FeatureFlags.kt
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;object FeatureFlags {
    const val showDeveloperMenu = false
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The correct file is compiled automatically depending on variant.&lt;/p&gt;
&lt;p&gt;This is cleaner than runtime checks.&lt;/p&gt;
&lt;h3 id=&quot;step-5-multiple-flavor-dimensions&quot;&gt;Step 5: Multiple flavor dimensions&lt;/h3&gt;
&lt;p&gt;Now let's introduce branding.&lt;/p&gt;
&lt;p&gt;Suppose you want:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;One codebase&lt;/li&gt;
&lt;li&gt;Two clients: acme and globex&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Add a second dimension:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;android {

    flavorDimensions += listOf(&quot;environment&quot;, &quot;brand&quot;)

    productFlavors {

        create(&quot;demo&quot;) { dimension = &quot;environment&quot; }
        create(&quot;prod&quot;) { dimension = &quot;environment&quot; }

        create(&quot;acme&quot;) {
            dimension = &quot;brand&quot;
            applicationIdSuffix = &quot;.acme&quot;
            resValue(&quot;string&quot;, &quot;brand_name&quot;, &quot;Acme&quot;)
        }

        create(&quot;globex&quot;) {
            dimension = &quot;brand&quot;
            applicationIdSuffix = &quot;.globex&quot;
            resValue(&quot;string&quot;, &quot;brand_name&quot;, &quot;Globex&quot;)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now total variants:&lt;/p&gt;
&lt;p&gt;2 environments times 2 brands times 2 build types equals 8 variants.&lt;/p&gt;
&lt;p&gt;This is how large production apps handle white labeling.&lt;/p&gt;
&lt;h3 id=&quot;step-6-variant-specific-dependencies&quot;&gt;Step 6: Variant specific dependencies&lt;/h3&gt;
&lt;p&gt;You can attach dependencies to specific variants.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dependencies {

    implementation(&quot;androidx.core:core-ktx:1.13.1&quot;)

    debugImplementation(&quot;com.squareup.leakcanary:leakcanary-android:2.14&quot;)

    demoImplementation(&quot;com.jakewharton.timber:timber:5.0.1&quot;)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;LeakCanary only in debug.&lt;br&gt;
Timber only in demo builds.&lt;/p&gt;
&lt;p&gt;This keeps production builds clean.&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/12289/android-build-types-product-flavors-and-variants-from-zero-to-production</guid>
<pubDate>Sun, 01 Mar 2026 12:23:28 +0000</pubDate>
</item>
<item>
<title>I built an open-source IRC client for Android – AndroidIRCX</title>
<link>https://coderlegion.com/10712/i-built-an-open-source-irc-client-for-android-androidircx</link>
<description>&lt;p&gt;Hey everyone &lt;br&gt;
I’ve been working on an open-source IRC client for Android called AndroidIRCX, and I wanted to share it with the community.&lt;br&gt;
The goal was to create a modern, lightweight, and privacy-focused IRC app, while preserving the classic IRC experience. The app is actively maintained and continues to evolve based on user feedback.&lt;br&gt;
Key features:&lt;br&gt;
 Real-time notifications&lt;br&gt;
 Optional app lock (PIN / biometrics)&lt;br&gt;
 Supports irc:// and ircs:// deep links&lt;br&gt;
 Multiple servers and channels&lt;br&gt;
 Reliable auto-reconnect logic&lt;br&gt;
 ZNC (bouncer) ready&lt;br&gt;
 IRCv3 protocol support&lt;br&gt;
 End-to-end encryption (E2EE)&lt;br&gt;
 Clean, simple UI with no unnecessary tracking&lt;br&gt;
The entire project is fully open source, and contributions, issues, and pull requests are very welcome.&lt;br&gt;
 GitHub:&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/AndroidIRCx/AndroidIRCx&quot;&gt;https://github.com/AndroidIRCx/AndroidIRCx&lt;/a&gt;&lt;br&gt;
 Google Play:&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://play.google.com/store/apps/details?id=com.androidircx&quot;&gt;https://play.google.com/store/apps/details?id=com.androidircx&lt;/a&gt;&lt;br&gt;
 Website:&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://androidircx.com&quot;&gt;https://androidircx.com&lt;/a&gt;&lt;br&gt;
This started mostly as a passion project and as a way to keep IRC usable and relevant on modern Android devices.&lt;br&gt;
I’d love to hear your feedback, suggestions, or criticism &lt;br&gt;
Thanks for reading!&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/10712/i-built-an-open-source-irc-client-for-android-androidircx</guid>
<pubDate>Thu, 29 Jan 2026 13:38:36 +0000</pubDate>
</item>
<item>
<title>From JavaScript to Kotlin</title>
<link>https://coderlegion.com/10516/from-javascript-to-kotlin</link>
<description>&lt;h3 id=&quot;a-practical-guide-for-web-developers-moving-to-android&quot;&gt;A practical guide for Web developers moving to Android&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=17482027954365939808&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Switching from web development to mobile development can feel intimidating at first. If you come from a JavaScript background(like me)and start learning Android with Kotlin the syntax may look familiar in places yet surprisingly strict in others.&lt;/p&gt;
&lt;p&gt;This guide is written specifically for JavaScript and web developers who are transitioning to Android development using Kotlin. The goal is to make that transition smoother by comparing concepts side by side clarifying common confusions, and explaining why Kotlin feels different but often safer and more predictable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why Kotlin Feels Different Coming from JavaScript&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;JavaScript is flexible, dynamic, and forgiving. Kotlin is designed to be safe, explicit, and structured. This difference is intentional. When web developers struggle with Kotlin, it is usually not because Kotlin is hard, but because it enforces rules that JavaScript does not.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Key mindset shift:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;JavaScript optimizes for speed of writing&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Kotlin optimizes for correctness and maintainability&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Initial Confusion Points&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Strict typing&lt;/li&gt;
&lt;li&gt;Null safety rules &lt;/li&gt;
&lt;li&gt;Immutability by default&lt;/li&gt;
&lt;li&gt;More boilerplate at first glance&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These feel restrictive if you come from JavaScript, but they exist to protect you.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What gets easier over time&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Code becomes more predictable&lt;/li&gt;
&lt;li&gt;Refactoring feels safer&lt;/li&gt;
&lt;li&gt;Fewer runtime crashes&lt;/li&gt;
&lt;li&gt;Better IDE assistance&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Most developers report that after a few weeks, Kotlin feels clearer than JavaScript for large applications.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pros of Kotlin for JavaScript Developers&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Strong null safety&lt;/li&gt;
&lt;li&gt;Clear data flow and state management&lt;/li&gt;
&lt;li&gt;Excellent tooling in Android Studio&lt;/li&gt;
&lt;li&gt;Modern language features&lt;/li&gt;
&lt;li&gt;Cleaner long term maintenance&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Kotlin shines in applications that grow over time especially team based projects.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cons and Tradeoffs&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Slower initial learning curve&lt;/li&gt;
&lt;li&gt;More rules to remember&lt;/li&gt;
&lt;li&gt;Less flexibility compared to JavaScript&lt;/li&gt;
&lt;li&gt;Compilation errors can feel overwhelming at first&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These are short term costs for long term stability.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Will Kotlin reduce or increase confusion?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Short answer: &lt;em&gt;Initially it may increase confusion. Long term it significantly reduces it.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Why?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Kotlin removes ambiguity&lt;/li&gt;
&lt;li&gt;Errors are visible early&lt;/li&gt;
&lt;li&gt;Code intent is clearer&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For mobile apps where crashes impact users directly, this tradeoff is worth it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Core Language Comparison&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Variables and Mutability&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;JavaScript&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let name = &quot;Alex&quot;
const age = 30
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Kotlin&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var name = &quot;Alex&quot;
val age = 30
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Clarification&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;val&lt;/code&gt; is immutable and should be your default choice&lt;/li&gt;
&lt;li&gt;&lt;code&gt;var&lt;/code&gt; is mutable and should be used intentionally&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Why this helps&lt;br&gt;
Kotlin makes state changes explicit, which reduces bugs in large codebases and UI state management.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Type System&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;JavaScript&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let count = 5
count = &quot;five&quot; // valid
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Kotlin&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var count = 5
// count = &quot;five&quot; // compilation error
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Clarification&lt;br&gt;
Kotlin is statically typed most type errors are caught at compile time instead of runtime.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Benefit for mobile apps&lt;/li&gt;
&lt;li&gt;Crashes are caught earlier&lt;/li&gt;
&lt;li&gt;Refactoring is safer&lt;/li&gt;
&lt;li&gt;Large teams move faster with confidence&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Functions&lt;/strong&gt;&lt;br&gt;
&lt;em&gt;JavaScript&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function add(a, b) {
  return a + b
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Kotlin&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;fun add(a: Int, b: Int): Int {
    return a + b
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Clarification&lt;br&gt;
Types are required in Kotlin&lt;br&gt;
Function signatures become self documenting&lt;br&gt;
This reduces ambiguity when reading unfamiliar code.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Null safety&lt;/strong&gt;&lt;br&gt;
One of Kotlin’s biggest advantages&lt;/p&gt;
&lt;p&gt;&lt;em&gt;JavaScript&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let user = null
console.log(user.name) // runtime error
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Kotlin&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var user: User? = null
println(user?.name)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Clarification&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;?&lt;/code&gt; means the value can be null&lt;/li&gt;
&lt;li&gt;&lt;code&gt;?.&lt;/code&gt; safely accesses properties&lt;br&gt;
Why this matters&lt;br&gt;
Null pointer exceptions are one of the most common crash causes in mobile apps. Kotlin forces you to think about nullability explicitly.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Conditionals as expressions&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;JavaScript&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let result
if (x &amp;gt; 10) {
  result = &quot;Big&quot;
} else {
  result = &quot;Small&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Kotlin&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;val result = if (x &amp;gt; 10) &quot;Big&quot; else &quot;Small&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Clarification&lt;br&gt;
In Kotlin, if returns a value this leads to cleaner and more predictable code.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Loops and Collections&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;JavaScript&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;for (let i = 0; i &amp;lt; list.length; i++) {
  console.log(list[i])
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Kotlin&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;for (item in list) {
    println(item)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Or more functional style&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;list.forEach { println(it) }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Clarification&lt;br&gt;
Kotlin encourages readable collection APIs and less index based logic means fewer errors.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Final Thoughts&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you are coming from web development, learning Kotlin is not about abandoning what you know. It is about adapting your mindset.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Think less about writing code quickly&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Think more about writing code safely&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Once that shift happens Kotlin becomes not just understandable, but enjoyable.&lt;/p&gt;
&lt;p&gt;To read the new article visit my personal &lt;a rel=&quot;nofollow&quot; href=&quot;https://devlogs-by-azizkhuja.vercel.app/&quot;&gt;blog&lt;/a&gt; and join my @devlogsbyazizkhuja telegram &lt;a rel=&quot;nofollow&quot; href=&quot;https://t.me/devlogsbyazizkhuja&quot;&gt;channel&lt;/a&gt;!&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/10516/from-javascript-to-kotlin</guid>
<pubDate>Tue, 27 Jan 2026 04:12:59 +0000</pubDate>
</item>
<item>
<title>From XML Screens to Scalable Systems — My 6-Year Mobile Dev Journey (Still Loading…)</title>
<link>https://coderlegion.com/9049/from-xml-screens-to-scalable-systems-my-6-year-mobile-dev-journey-still-loading</link>
<description>&lt;p&gt;&lt;strong&gt; Introduction&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Six years ago, I opened Android Studio with zero clarity and max curiosity. No roadmap. No mentor. Just vibes, bugs, and the stubborn belief that I could build things people would actually use. Fast-forward to today: I’ve shipped apps, survived startup chaos, lived through COVID-era uncertainty, and evolved into a full-stack mobile app developer who’s still learning every single day.&lt;/p&gt;
&lt;p&gt;This is my journey — raw, messy, and very real.&lt;/p&gt;
&lt;p&gt; The Tech Stack That Shaped Me&lt;/p&gt;
&lt;p&gt;I didn’t “pick” a stack. The stack picked me.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Android (Java + XML)&lt;/strong&gt; – where discipline came from&lt;br&gt;
&lt;strong&gt;Kotlin&lt;/strong&gt; – where clean code finally made sense&lt;br&gt;
&lt;strong&gt;Flutter&lt;/strong&gt; – where cross-platform speed changed the game&lt;br&gt;
&lt;strong&gt;Firebase&lt;/strong&gt; – real-time apps, auth, push, analytics, the whole deal&lt;br&gt;
&lt;strong&gt;Supabase&lt;/strong&gt; – modern backend energy, SQL but developer-friendly&lt;br&gt;
&lt;strong&gt;SQLite&lt;/strong&gt; – offline-first thinking, data control&lt;br&gt;
&lt;strong&gt;Custom Servers &amp;amp; APIs&lt;/strong&gt; – because sometimes you need full control&lt;/p&gt;
&lt;p&gt;Over time, I stopped chasing tools and started solving problems. Big mindset shift.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt; Pain,  Gain — The Real Learning Curve&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Let’s be honest: tutorials don’t prepare you for production.&lt;/p&gt;
&lt;p&gt;I’ve:&lt;br&gt;
Shipped features that broke in real users’ hands&lt;br&gt;
Over-engineered apps that never saw the light of day&lt;br&gt;
Underestimated timelines (classic dev mistake)&lt;br&gt;
Burned out, restarted, and rebuilt — multiple times&lt;/p&gt;
&lt;p&gt;Running my own startup was the hardest and most educational phase. You don’t just code — you handle product decisions, user complaints, scaling issues, server costs, and the pressure of “this has to work.”&lt;/p&gt;
&lt;p&gt;That pain? It compounds into experience. The gain is insane.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt; COVID Era: Survival Mode → Skill Upgrade&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;COVID flipped everything. Projects paused. Plans broke. Motivation dipped.&lt;/p&gt;
&lt;p&gt;So I doubled down on skills:&lt;br&gt;
Refined architecture patterns&lt;br&gt;
Improved UI/UX thinking (not just “it works”)&lt;br&gt;
Learned to ship faster with fewer resources&lt;br&gt;
Adapted to remote-first workflows&lt;/p&gt;
&lt;p&gt;That phase rewired how I work. Less noise. More impact.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt; What I Bring to the Table Now&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;End-to-end mobile app development&lt;br&gt;
Scalable backend integration&lt;br&gt;
Performance-focused UI/UX&lt;br&gt;
Startup mindset (not just feature delivery)&lt;br&gt;
Clear communication + ownership&lt;/p&gt;
&lt;p&gt;I care about why a feature exists, not just how to build it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt; Open for Freelance &amp;amp; Collaborations&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I’m currently available for freelance, remote work, and long-term collaborations —&lt;br&gt;
 Worldwide &amp;amp; across India&lt;/p&gt;
&lt;p&gt;If you’re:&lt;br&gt;
A startup building v1&lt;br&gt;
A founder needing a reliable dev&lt;br&gt;
A team looking for someone who actually owns outcomes&lt;/p&gt;
&lt;p&gt;Let’s build something solid.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;✨ Final Thought&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This journey isn’t a highlight reel. It’s six years of up and down, shipping and failing, learning and unlearning. And honestly? I’m just getting started.&lt;/p&gt;
&lt;p&gt;If you’re building — keep going.&lt;br&gt;
If you’re stuck — you’re not alone.&lt;br&gt;
If you want to collaborate — my inbox is open.&lt;/p&gt;
&lt;p&gt;Let’s cook. &lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=7932023965072424628&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
</description>
<category>Personal Branding</category>
<guid isPermaLink="true">https://coderlegion.com/9049/from-xml-screens-to-scalable-systems-my-6-year-mobile-dev-journey-still-loading</guid>
<pubDate>Mon, 29 Dec 2025 12:13:47 +0000</pubDate>
</item>
<item>
<title>Music App Bug: Songs Broke on Switching — Here’s Why</title>
<link>https://coderlegion.com/4404/music-app-bug-songs-broke-on-switching-heres-why</link>
<description>&lt;p&gt;I was facing this annoying issue in Flutter music player:&lt;/p&gt;
&lt;p&gt;When you tap songs quickly (like switching from one to another), playback breaks, mini player disappears, and you have to tap again to play. It was 2-3 times out of every 10 switch. &lt;/p&gt;
&lt;p&gt;It took me a while to trace it. I tried:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Making our playback function async&lt;/li&gt;
&lt;li&gt;Adding delays&lt;/li&gt;
&lt;li&gt;Debugging tap triggers&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But nothing worked.&lt;/p&gt;
&lt;p&gt;Then I found the real culprit:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;await _player.stop(); //  this innocent-looking line was breaking everything
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;the-wrong-code&quot;&gt;The Wrong Code&lt;/h3&gt;
&lt;p&gt;In the background audio handler's updateQueue() method:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Future&amp;lt;void&amp;gt; updateQueue(List&amp;lt;MediaItem&amp;gt; newQueue) async {
  final newAudioSources = newQueue.map((item) {
    final uri = item.extras?['uri'] as String;
    return AudioSource.uri(Uri.parse(uri));
  }).toList();

  await _player.setAudioSource(_playlist, initialIndex: 0);
  await _player.stop(); // ❌ this line breaks playback!

  queue.add(newQueue);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;what-s-the-problem-&quot;&gt;What’s the Problem?&lt;/h3&gt;
&lt;p&gt;This creates a race condition:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;setAudioSource() loads and prepares the new song(s).&lt;/li&gt;
&lt;li&gt;Immediately after, stop() wipes the loaded source.&lt;/li&gt;
&lt;li&gt;So by the time the player is supposed to play, there's nothing left&lt;br&gt;
to play.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This bug especially shows up during rapid song switching, because set -&amp;gt; stop -&amp;gt; play happens too fast and gets out of sync.&lt;/p&gt;
&lt;h3 id=&quot;the-right-code&quot;&gt;The Right Code&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;Future&amp;lt;void&amp;gt; updateQueue(List&amp;lt;MediaItem&amp;gt; newQueue) async {
  final newAudioSources = newQueue.map((item) {
    final uri = item.extras?['uri'] as String;
    return AudioSource.uri(Uri.parse(uri));
  }).toList();

  _playlist.clear();
  _playlist.addAll(newAudioSources);

  await _player.setAudioSource(_playlist, initialIndex: 0);

  if (_player.playing) {
    await _player.play();
  }

  queue.add(newQueue);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you're using just_audio and audio_service, never call stop() right after setAudioSource().&lt;br&gt;
You're basically loading a song and immediately throwing it away.&lt;/p&gt;
&lt;p&gt;Let the player handle resets internally unless you truly need to stop everything.&lt;/p&gt;
&lt;p&gt;Happy building! &lt;/p&gt;
</description>
<category>Mobile Dev</category>
<guid isPermaLink="true">https://coderlegion.com/4404/music-app-bug-songs-broke-on-switching-heres-why</guid>
<pubDate>Sun, 03 Aug 2025 22:32:31 +0000</pubDate>
</item>
<item>
<title>Query Smarter, Not Harder</title>
<link>https://coderlegion.com/4282/query-smarter-not-harder</link>
<description>&lt;p&gt;If you're building a music app with on_audio_query, it’s tempting to call querySongs() everywhere—home screen, playlists, search, etc. But this means repeated storage scans → slower UI, battery drain, and unnecessary complexity.&lt;/p&gt;
&lt;h3 id=&quot;the-wrong-way-querying-every-time&quot;&gt;The Wrong Way – Querying Every Time&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;final OnAudioQuery audioQuery = OnAudioQuery();

Future&amp;lt;List&amp;lt;SongModel&amp;gt;&amp;gt; getSongs() async {
  return await audioQuery.querySongs(); // Called in every screen
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In each screen:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;final songs = await getSongs(); // Repeats scan again
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;the-better-way-query-once-share-via-provider&quot;&gt;The Better Way – Query Once, Share via Provider&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Fetch once in a service (e.g., MusicPlaybackService):&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;class MusicPlaybackService extends ChangeNotifier {
  List&amp;lt;SongModel&amp;gt; _allSongs = [];

  Future&amp;lt;void&amp;gt; loadSongs() async {
    final OnAudioQuery query = OnAudioQuery();
    _allSongs = await query.querySongs();
    notifyListeners();
  }

  List&amp;lt;SongModel&amp;gt; get allSongs =&amp;gt; _allSongs;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Provide it globally:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;runApp(
  ChangeNotifierProvider(
    create: (_) {
      final service = MusicPlaybackService();
      service.loadSongs(); // Fetch once
      return service;
    },
    child: MyApp(),
  ),
);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Reuse anywhere without re-querying:&lt;/p&gt;
&lt;p&gt;  final allSongs = context.watch().allSongs;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Benefits&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No repeated disk access.&lt;/li&gt;
&lt;li&gt;Faster UI transitions (playlist, search, player use the same list).&lt;/li&gt;
&lt;li&gt;Centralized state → easier maintenance.&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/4282/query-smarter-not-harder</guid>
<pubDate>Sat, 26 Jul 2025 19:58:21 +0000</pubDate>
</item>
</channel>
</rss>