<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: G Ghuman</title>
    <description>The latest articles on DEV Community by G Ghuman (@g_ghuman_8989).</description>
    <link>https://dev.to/g_ghuman_8989</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4077314%2Face7c364-a215-4f77-b0f8-b58c258108bb.jpg</url>
      <title>DEV Community: G Ghuman</title>
      <link>https://dev.to/g_ghuman_8989</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/g_ghuman_8989"/>
    <language>en</language>
    <item>
      <title>Understanding OAuth2 the Simple Way</title>
      <dc:creator>G Ghuman</dc:creator>
      <pubDate>Fri, 14 Aug 2026 11:32:49 +0000</pubDate>
      <link>https://dev.to/g_ghuman_8989/understanding-oauth2-the-simple-way-l4n</link>
      <guid>https://dev.to/g_ghuman_8989/understanding-oauth2-the-simple-way-l4n</guid>
      <description>&lt;h1&gt;
  
  
  Understanding OAuth2 the Simple Way
&lt;/h1&gt;

&lt;p&gt;You are building a side project and want users to log in with their Google accounts instead of making up yet another password they'll forget. So you open the OAuth2 spec. Ten minutes later, you are staring blankly at a wall of terminology about "Resource Owners," "Authorization Grants," and "Bearer Tokens," wondering if you should just use a plaintext database table and call it a day.&lt;/p&gt;

&lt;p&gt;Every tutorial on the internet tries to explain OAuth2 with an analogy about valet keys at a restaurant. I don't want a valet key. I want to know why my redirect URI is returning a 400 Bad Request and how to get an access token without losing my sanity.&lt;/p&gt;

&lt;p&gt;Let's skip the car analogies and look at how this actually works.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Core Concept: Delegation, Not Authentication
&lt;/h2&gt;

&lt;p&gt;Here is the first trap everyone falls into: treating OAuth2 as a login system. &lt;/p&gt;

&lt;p&gt;It isn't one. OAuth2 is an authorization framework. It is designed to answer one question: &lt;em&gt;Can application X access resource Y on behalf of user Z?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Authentication answers "Who are you?" (That's OpenID Connect, which is built on top of OAuth2, but we'll ignore that for a second). OAuth2 answers "What are you allowed to do?" &lt;/p&gt;

&lt;p&gt;Imagine you built a little CLI tool that automatically backs up your GitHub repositories to a local drive. Your script needs access to your private repos, but you definitely don't want to hardcode your GitHub password into a Python file. &lt;/p&gt;

&lt;p&gt;Instead, you want to go to GitHub, log in yourself, look at a scary warning screen that says &lt;em&gt;"This app wants read access to your repositories,"&lt;/em&gt; and click "Authorize." GitHub then hands your script a temporary backstage pass (an access token). Your script uses that pass to grab your code. &lt;/p&gt;

&lt;p&gt;That handshake is OAuth2.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Authorization Code Flow (The One You'll Actually Use)
&lt;/h2&gt;

&lt;p&gt;There are several "grant types" in the OAuth2 spec. Ignore all of them except the &lt;strong&gt;Authorization Code Flow&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;If you are building a traditional web app with a backend server, this is the flow you need. It keeps your app's secret key safe on the server where users can't see it in their browser dev tools.&lt;/p&gt;

&lt;p&gt;Here is how the dance goes step by step:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The Redirect:&lt;/strong&gt; Your app sends the user to the identity provider (like Google or GitHub) with a URL that says, "Hey, send this user back to &lt;code&gt;/callback&lt;/code&gt; when they log in, and tell them I need &lt;code&gt;read:user&lt;/code&gt; permissions."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Consent:&lt;/strong&gt; The user logs into Google, sees the consent screen, and clicks "Allow."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Code:&lt;/strong&gt; Google redirects the user back to your app's &lt;code&gt;/callback&lt;/code&gt; route with a temporary &lt;code&gt;code&lt;/code&gt; stuck in the query string. This code is &lt;em&gt;not&lt;/em&gt; the access token. It's a single-use receipt.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Exchange:&lt;/strong&gt; Your backend takes that receipt, walks up to Google's token endpoint behind the scenes (along with your client secret), and trades it for the actual Access Token.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's look at what that token exchange looks like in practice. Here is a quick Node.js snippet using &lt;code&gt;axios&lt;/code&gt; hitting a mock provider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;axios&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;exchangeCodeForToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;authCode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://oauth.example.com/oauth/token&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;client_secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CLIENT_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;authCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;grant_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;authorization_code&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;redirect_uri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://localhost:3000/callback&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// This is what you actually care about&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expires_in&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Spoiler: You will hit this catch block a lot at first&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Token exchange failed:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;OAuth dance failed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Making the Authenticated Request
&lt;/h2&gt;

&lt;p&gt;Once you have that &lt;code&gt;access_token&lt;/code&gt; safely stored in a secure, HTTP-only cookie or a session, using it is anti-climactic. You just slap it into the HTTP headers of your API requests.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_user_profile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.example.com/v1/user/profile&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;access_token&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Accept&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Token expired or invalid. Time to use that refresh token.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;Bearer&lt;/code&gt; prefix trips people up. It literally means "Hand this token to whoever is bearing it." If someone steals your access token, they can impersonate the user until it expires. That is why keeping tokens out of &lt;code&gt;localStorage&lt;/code&gt; in single-page apps matters so much—any rogue XSS script can read &lt;code&gt;localStorage&lt;/code&gt; and steal your bearer tokens.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Gotchas That Will Waste Your Afternoon
&lt;/h2&gt;

&lt;p&gt;When I first wired this up, I spent three hours debugging a &lt;code&gt;redirect_uri_mismatch&lt;/code&gt; error from Google. Everything looked identical. The string in my code matched the string in the Google Cloud Console down to the last slash.&lt;/p&gt;

&lt;p&gt;Except it didn't. I had &lt;code&gt;http://localhost:3000/callback&lt;/code&gt; in my code, but &lt;code&gt;http://localhost:3000/callback/&lt;/code&gt; (with a trailing slash) registered in the dashboard. OAuth implementations are aggressively literal. If a single character is off, they lock the door and give you zero helpful context.&lt;/p&gt;

&lt;p&gt;Another common pitfall is ignoring token expiration. Access tokens are designed to be short-lived—sometimes expiring in 15 minutes. If your app crashes because a user's token expired while they were filling out a form, your UX is broken. You have to implement the refresh token cycle, which means storing that second token securely and asking the auth server for a fresh access token behind the scenes when the first one dies.&lt;/p&gt;

&lt;p&gt;Finally, don't write your own OAuth2 server unless you are doing it purely for fun on a weekend. Use Auth0, Keycloak, Supabase, or Firebase Auth. Implementing the spec securely—handling state parameters to prevent CSRF, managing PKCE for mobile apps, handling token rotation—is tedious security plumbing that has already been solved a thousand times.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;Open up your terminal, pick an API you use daily (GitHub or Spotify have great, developer-friendly docs), and register a new developer application in their dashboard. Don't write any code yet—just use Postman or curl to manually trigger the authorization URL in your browser, grab the code from the redirect, and paste it into a manual POST request to get your first token. &lt;/p&gt;

&lt;p&gt;Once you trace the round-trip manually with your own eyes, the magic trick loses its mystery.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Step-by-Step: Setting Up a Mobile App Development Environment</title>
      <dc:creator>G Ghuman</dc:creator>
      <pubDate>Fri, 14 Aug 2026 08:31:05 +0000</pubDate>
      <link>https://dev.to/g_ghuman_8989/step-by-step-setting-up-a-mobile-app-development-environment-3jbm</link>
      <guid>https://dev.to/g_ghuman_8989/step-by-step-setting-up-a-mobile-app-development-environment-3jbm</guid>
      <description>&lt;h1&gt;
  
  
  Step-by-Step: Setting Up a Mobile App Development Environment
&lt;/h1&gt;

&lt;p&gt;Getting a mobile development environment running on your machine is a rite of passage, mostly because it usually takes three hours and involves downloading about fifteen gigabytes of software you didn't know existed. &lt;/p&gt;

&lt;p&gt;If you're coming from web development, the shift is jarring. In web dev, you save a file and your browser refreshes. In mobile dev, you're compiling native binaries, managing virtual devices that eat your RAM for breakfast, and dealing with two completely different ecosystems that actively try not to get along. &lt;/p&gt;

&lt;p&gt;I recently had to set up a fresh MacBook for React Native and Flutter work. Here is the exact path through the setup swamp, minus the dead ends I hit along the way.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The Command Line Foundation (Homebrew and Node)
&lt;/h2&gt;

&lt;p&gt;Don't download installers from random websites if you can avoid it. You want a package manager so you can actually update your tools next year without crying. &lt;/p&gt;

&lt;p&gt;On macOS, Homebrew is non-negotiable. If you're on Linux, your native package manager handles most of this, and if you're on Windows, you should probably be using WSL2, though that's a whole separate therapy session.&lt;/p&gt;

&lt;p&gt;Open your terminal and install Homebrew if you haven't already:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/bin/bash &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once that finishes, the installer usually prints a couple of "Next steps" lines to add Brew to your PATH. Do not ignore them, or your terminal will pretend it has no idea what &lt;code&gt;brew&lt;/code&gt; is the second you close the window.&lt;/p&gt;

&lt;p&gt;Next, grab Node.js. Even if you aren't building a React Native app, almost every mobile toolchain relies on Node somewhere under the hood for build scripts or CLI runners.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;node
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify it worked. If &lt;code&gt;node -v&lt;/code&gt; spits out a version number, you're ready for the heavy machinery.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Android Studio and the SDK Monster
&lt;/h2&gt;

&lt;p&gt;You need Android Studio even if you plan to build iOS apps later. Why? Because you need the Android SDK, the build tools, and the emulator manager. You don't actually have to write code in Android Studio—you can use VS Code or whatever editor you prefer—but you cannot escape installing it.&lt;/p&gt;

&lt;p&gt;Download Android Studio from the official site and run the installer. When you get to the setup wizard, choose &lt;strong&gt;Custom&lt;/strong&gt; setup. Do not just click Next on everything. You want to make sure the following components are checked:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Android SDK&lt;/li&gt;
&lt;li&gt;  Android SDK Platform&lt;/li&gt;
&lt;li&gt;  Performance (Intel HAXM or AMD equivalent, depending on your chip)&lt;/li&gt;
&lt;li&gt;  Android Virtual Device&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once it installs, open the SDK Manager (it's hidden in the settings menus). Go to the &lt;strong&gt;SDK Platforms&lt;/strong&gt; tab and make sure you have at least one recent version installed—Android 14 (API 34) is a safe bet right now. &lt;/p&gt;

&lt;p&gt;Then switch to the &lt;strong&gt;SDK Tools&lt;/strong&gt; tab and check these boxes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Android SDK Build-Tools&lt;/li&gt;
&lt;li&gt;  Android Emulator&lt;/li&gt;
&lt;li&gt;  Android SDK Platform-Tools&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is the gotcha that wasted an hour of my life: your terminal doesn't know where any of this lives by default. You have to wire up your environment variables. &lt;/p&gt;

&lt;p&gt;Open your shell configuration file (&lt;code&gt;~/.zshrc&lt;/code&gt; or &lt;code&gt;~/.bash_profile&lt;/code&gt;) in your text editor and paste these lines at the bottom:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANDROID_HOME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;/Library/Android/sdk
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;:&lt;span class="nv"&gt;$ANDROID_HOME&lt;/span&gt;/emulator
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;:&lt;span class="nv"&gt;$ANDROID_HOME&lt;/span&gt;/platform-tools
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;:&lt;span class="nv"&gt;$ANDROID_HOME&lt;/span&gt;/cmdline-tools/latest/bin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save the file, then run &lt;code&gt;source ~/.zshrc&lt;/code&gt; to apply the changes. If you type &lt;code&gt;adb&lt;/code&gt; into your terminal now, it should print a list of commands instead of saying "command not found."&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Xcode (Mac Only) and iOS Simulator Quirks
&lt;/h2&gt;

&lt;p&gt;If you're on a Mac and want to build for iOS, you have to deal with Xcode. Head over to the Mac App Store and download it. &lt;/p&gt;

&lt;p&gt;Fair warning: Xcode is massive—well over 30GB depending on what components it decides to pull down. Start the download, go make a sandwich, watch an episode of something, and come back. It takes a while.&lt;/p&gt;

&lt;p&gt;Once Xcode is installed, open it at least once. It needs to prompt you to install additional required components and agree to the license agreement. If you skip this and try to run a build from the command line, Xcode will fail silently or throw an obscure exit code that means nothing.&lt;/p&gt;

&lt;p&gt;Next, install the command-line tools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;xcode-select &lt;span class="nt"&gt;--switch&lt;/span&gt; /Applications/Xcode.app/Contents/Developer
&lt;span class="nb"&gt;sudo &lt;/span&gt;xcodebuild &lt;span class="nt"&gt;-runFirstLaunch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll also want to install CocoaPods, which is the dependency manager for iOS. Don't use &lt;code&gt;sudo gem install cocoapods&lt;/code&gt; if you can avoid it—it messes with system ruby permissions. Use homebrew instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;cocoapods
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To test if your iOS simulator is working without spinning up a whole project yet, you can boot one directly from the terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;xcrun simctl boot &lt;span class="s2"&gt;"iPhone 15"&lt;/span&gt;
open &lt;span class="nt"&gt;-a&lt;/span&gt; Simulator
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a shiny virtual iPhone pops up on your screen, you're in business.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Creating and Running Your First Test Project
&lt;/h2&gt;

&lt;p&gt;Let's verify everything actually talks to each other. We'll use React Native's CLI for this test because it touches both Android and iOS toolchains. &lt;/p&gt;

&lt;p&gt;Create a blank sandbox project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @react-native-community/cli init TestEnvironmentApp
&lt;span class="nb"&gt;cd &lt;/span&gt;TestEnvironmentApp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, open two separate terminal tabs.&lt;/p&gt;

&lt;p&gt;In tab one, start the Metro bundler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In tab two, try launching the Android build:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run android
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your Android emulator spins up, builds the app, and displays the default React Native welcome screen without throwing a Gradle build error, your environment is solid. &lt;/p&gt;

&lt;p&gt;If it fails with a Gradle error about Java versions, don't panic. Mobile tools are notoriously picky about Java Development Kit (JDK) versions. Android Studio installs its own JDK, but sometimes your system points to an older global version. You can check your Java version with &lt;code&gt;java -version&lt;/code&gt;. Most modern mobile frameworks expect JDK 17.&lt;/p&gt;

&lt;p&gt;To test iOS (assuming you're on a Mac), run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;ios &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; pod &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; ..
npm run ios
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The simulator should automatically compile the native iOS binary and load the app. &lt;/p&gt;

&lt;h2&gt;
  
  
  Next Step
&lt;/h2&gt;

&lt;p&gt;Open your terminal right now, type &lt;code&gt;adb devices&lt;/code&gt; or spin up an iOS simulator to verify your paths are still working after closing your terminal window. Once you confirm a virtual device boots up cleanly, pick a framework—React Native, Flutter, or native Swift/Kotlin—and run their official "Hello World" tutorial while your environment is still fresh.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
