<?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: Omotola Odumosu</title>
    <description>The latest articles on DEV Community by Omotola Odumosu (@yhoungbrown).</description>
    <link>https://dev.to/yhoungbrown</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3439053%2F773ade0d-0458-433f-8b97-bfa27469433e.png</url>
      <title>DEV Community: Omotola Odumosu</title>
      <link>https://dev.to/yhoungbrown</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yhoungbrown"/>
    <language>en</language>
    <item>
      <title>Implementing Retry Policy (React Native &amp; Beyond)</title>
      <dc:creator>Omotola Odumosu</dc:creator>
      <pubDate>Mon, 23 Mar 2026 07:38:32 +0000</pubDate>
      <link>https://dev.to/yhoungbrown/implementing-retry-policy-react-native-beyond-2lgb</link>
      <guid>https://dev.to/yhoungbrown/implementing-retry-policy-react-native-beyond-2lgb</guid>
      <description>&lt;p&gt;A seamless user experience is one of the biggest factors in determining whether users stick with your app or abandon it. One underrated technique that helps achieve this is &lt;strong&gt;retry policy.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is a Retry Policy?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A retry policy is a strategy where your application automatically retries a failed request instead of immediately showing an error to the user.&lt;/p&gt;

&lt;p&gt;From the user’s perspective, everything just works:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“I made a request → I saw a loader → I got my result.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But behind the scenes, your app may have retried that request multiple times before eventually succeeding.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Does This Matter?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Network calls are not always reliable. Failures can happen due to temporary server issues (5xx errors), network instability (timeouts, poor connectivity), rate limiting, or other transient failures.&lt;/p&gt;

&lt;p&gt;If you immediately show an error, you force users to retry manually, restart their whole flow again, and potentially lose trust in your app.&lt;/p&gt;

&lt;p&gt;A retry policy helps smooth over these temporary issues and improves overall user experience.&lt;/p&gt;

&lt;p&gt;Retry policies are widely used across backend services (e.g., microservices communication), Cloud systems, distributed systems, Message queues, etc.&lt;/p&gt;

&lt;p&gt;It’s a universal resilience pattern, used to improve performance and user experience. When implemented correctly, users never even know something went wrong, and that’s exactly the goal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Implementation&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const wait = (ms: number) =&amp;gt;
  new Promise((resolve) =&amp;gt; setTimeout(resolve, ms));

type RetryOptions = {
  retries: number;
  delay: number;
  factor?: number; // exponential multiplier
};

async function retryRequest&amp;lt;T&amp;gt;(
  fn: () =&amp;gt; Promise&amp;lt;T&amp;gt;,
  { retries, delay, factor = 2 }: RetryOptions
): Promise&amp;lt;T&amp;gt; {
  let attempt = 0;

  while (attempt &amp;lt;= retries) {
    try {
      return await fn();
    } catch (error: any) {
      const status = error?.response?.status;

      // Do not retry client errors (4xx)
      if (status &amp;gt;= 400 &amp;amp;&amp;amp; status &amp;lt; 500) {
        throw error;
      }

      if (attempt === retries) {
        throw error;
      }

      const backoff = delay * Math.pow(factor, attempt);
      await wait(backoff);

      attempt++;
    }
  }

  throw new Error("Unexpected error");
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Understanding The Implementation Above&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This implementation wraps any async operation (fn) with a retry mechanism.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;retryRequest&lt;/strong&gt; is a generic function, meaning it works with any return type while preserving type safety.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;RetryOptions&lt;/strong&gt; defines how many times to retry, the initial delay, and an optional exponential multiplier (factor).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;strong&gt;wait&lt;/strong&gt; function simply pauses execution for a given time before retrying.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Implementation In Plain English&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The function attempts to execute the provided operation, which is &lt;strong&gt;"fn"&lt;/strong&gt;. If it succeeds, the result is returned immediately, but if it fails, the error is inspected:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;4xx errors are not retried (these are client-side issues), e.g., the client input wrong details.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Other errors (e.g., 5xx or network failures) are retried.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before each retry, the function waits using exponential backoff, where the delay increases after every attempt. This is to ensure we don't overburden the server. Exponential backoff helps give the server the time it needs to respond:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;delay × factor^attempt&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The process continues until the request succeeds or the maximum number of retries is reached. If all retries fail, the error is thrown.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Usage&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const fetchUser = async () =&amp;gt; {
  const response = await fetch("https://api.example.com/user");

  if (!response.ok) {
    const error: any = new Error("Request failed");
    error.response = { status: response.status };
    throw error;
  }

  return response.json();
};

const getUserWithRetry = async () =&amp;gt; {
  try {
    const data = await retryRequest(fetchUser, {
      retries: 3,
      delay: 1000,
    });

    console.log("Success:", data);
  } catch (error) {
    console.log("Final failure:", error);
  }
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this setup, you call the &lt;strong&gt;getUserWithRetry()&lt;/strong&gt; function within your application whenever you need to fetch the user. If the request fails due to a transient issue, the retry policy automatically kicks in and attempts the request again behind the scenes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In Conclusion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Retry policies are a simple but powerful way to improve reliability, reduce user frustration, and handle transient failures gracefully&lt;/p&gt;

&lt;p&gt;It’s a small addition, but it can make your application feel significantly more stable and production-ready.&lt;/p&gt;




&lt;h2&gt;
  
  
  Did this post help simplify things for you?
&lt;/h2&gt;

&lt;p&gt;If yes, drop a ❤️ or 🦄 reaction and follow me here on dev.to. I share more practical, plain-English breakdowns like this.&lt;/p&gt;

&lt;p&gt;You can also connect with me on social media. I’d love to learn, share, and grow together with you!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LinkedIn:&lt;/strong&gt; &lt;a href="https://www.linkedin.com/in/omotola-odumosu-10a4971b2" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Twitter:&lt;/strong&gt; &lt;a href="https://x.com/yhoungbrown" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Instagram:&lt;/strong&gt; &lt;a href="https://www.instagram.com/yhoungbrownn" rel="noopener noreferrer"&gt;Instagram&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Graphics Credit: Daniel Gustaw&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>performance</category>
      <category>architecture</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Building and Signing Android Apps Locally with Gradle (React Native &amp; Expo)</title>
      <dc:creator>Omotola Odumosu</dc:creator>
      <pubDate>Sat, 24 Jan 2026 16:42:27 +0000</pubDate>
      <link>https://dev.to/yhoungbrown/building-and-signing-android-apps-locally-with-gradle-react-native-expo-3n9n</link>
      <guid>https://dev.to/yhoungbrown/building-and-signing-android-apps-locally-with-gradle-react-native-expo-3n9n</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;A practical, step-by-step walkthrough of generating debug and release builds, configuring keystores, fixing common Gradle errors, and preparing your app for Google Play.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you’ve been using EAS Build, you already know how convenient it is. EAS automatically creates your keystore and signs your app for you, which makes building Android apps feel almost effortless.&lt;/p&gt;

&lt;p&gt;However, you also discover the downside pretty quickly: long queues. Sometimes your build sits for hours before it even starts. That’s usually the moment you realize that building locally is not optional; it’s a skill you need.&lt;/p&gt;

&lt;p&gt;When I attempted my first Android release build locally, I ran into a lot of confusion, debugging, research, and errors like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;debug vs release signing&lt;/li&gt;
&lt;li&gt;keystore vs signingReport&lt;/li&gt;
&lt;li&gt;Why my APK worked but wasn’t Play Store–ready&lt;/li&gt;
&lt;li&gt;random Gradle errors that made no sense at first&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And many more that I can’t fully cover here.&lt;/p&gt;

&lt;p&gt;Even if the exact issue you’re facing isn’t listed above, don’t worry, just follow along, and we’ll get to the destination together.&lt;/p&gt;

&lt;p&gt;For this article, I’ll be using React Native with Expo on Windows, but most of the concepts apply generally across platforms.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prerequisites&lt;/strong&gt;&lt;br&gt;
Before anything else, make sure you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JDK 17 installed&lt;/li&gt;
&lt;li&gt;Android Studio

&lt;ul&gt;
&lt;li&gt;Android SDK&lt;/li&gt;
&lt;li&gt;Android SDK Build Tools&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Node.js + npm&lt;/li&gt;
&lt;li&gt;A React Native or Expo project&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Generate the Android Folder (Expo users)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you’re using Expo (managed workflow), you first need the native Android files. From your project root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;npx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;expo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;prebuild&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--platform&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;android&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This generates an Android folder in your project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Move into the Android Directory&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;android&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All Gradle commands are run inside the Android folder, not the project root.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Clean the Project (Important)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Before building anything, always clean once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;/gradlew&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;clean&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command clears old build artifacts and ensures Gradle starts from a clean state. You usually run this once per project, unless &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you hit strange build errors, &lt;/li&gt;
&lt;li&gt;you change signing configs, or &lt;/li&gt;
&lt;li&gt;You change native dependencies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Common Windows Issue&lt;/strong&gt;&lt;br&gt;
If you come across this error, &lt;strong&gt;"Filename longer than 260 characters."&lt;/strong&gt; It means the path to your project is too long.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt;&lt;br&gt;
Move your project closer to the drive root, e.g.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;C:\my-project&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 4: Understanding Build Types&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Gradle can generate different build types, such as:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Development build&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A development build can be generated using the command below&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;/gradlew&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;assembleDebug&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Development builds are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Signed with debug keystore&lt;/li&gt;
&lt;li&gt;Used for local testing while coding, just like Expo Go&lt;/li&gt;
&lt;li&gt;Not for Play Store&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Release APK&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A release build can be generated with the code below&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;/gradlew&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;assembleRelease&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This produces a release APK that can be installed on your device and used. It is fully functional, but not necessarily Play Store ready.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Play Store Bundle (Recommended)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Play Store doesn't accept APK files for app uploads anymore, and as such, an AAB file is required as per Play Store's requirements. The command below generates a .aab file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;/gradlew&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;bundleRelease&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 5: Where Your Builds Are Located&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After a successful build, your apps are located in the directory below:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;APKs&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;android/app/build/outputs/apk/debug/app-debug.apk&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;android/app/build/outputs/apk/release/app-release.apk&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you initiated a debug APK build, you'll find your app in the first path above, but if you initiated a release build, you'll find your app in the second path above.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Play Store Bundle&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;android/app/build/outputs/bundle/release/app-release.aab&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Google Play Store builds (.aab), you'll find your app in the directory above&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 6: Understanding signingReport&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To see how your app is signed, run this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;/gradlew&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;signingReport&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This shows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Variant (debug / release)&lt;/li&gt;
&lt;li&gt;Config (which keystore was used)&lt;/li&gt;
&lt;li&gt;Store file&lt;/li&gt;
&lt;li&gt;Alias&lt;/li&gt;
&lt;li&gt;SHA1 / SHA256 fingerprints&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Important Discovery 💡&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you ran a release build in step 4 above, and after running the command in step 6, you see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;variant:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;release&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;config:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Config tells you which keystore was actually used, not the build type you intended to use. Seeing the above means that your release APK is still signed with the debug keystore. The implication of this is that your app will work normally and will be fully functional; you can share it with friends or anyone, and it will still work, but you can't upload it to the Google Play Store. It will get rejected because the app was signed with a debug keystore. If your intention wasn’t to upload to the Play Store but maybe to just have the app and test that it works fine, or share it with friends, then you have no issues to bother about. However, if you plan to upload to the store, you need to generate a release keystore so your app can be signed with that release keystore.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 7: Generating a Release Keystore (Windows)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Open PowerShell as administrator, and navigate to the directory below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;C:\Program&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Files\Java\jdkX.X.X\bin&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;\keytool.exe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-genkeypair&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;-storetype&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PKCS12&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;-keystore&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;my-upload-key.keystore&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;-alias&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;my-key-alias&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;-keyalg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;RSA&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;-keysize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;2048&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;-validity&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;10000&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You’ll be asked to set the following:&lt;/p&gt;

&lt;p&gt;Name, organization, country, Keystore password, Key password, etc.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Important&lt;/strong&gt;&lt;br&gt;
Save your passwords and aliases. You can’t recover them later. And also, while typing the passwords, your terminal won't show the letters you're typing at all; that's a security measure put in place to secure your password, so be very careful when typing&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 8: Back Up Your Keystore (Very Important)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Create a backup folder with this code :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;mkdir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;USERPROFILE&lt;/span&gt;&lt;span class="s2"&gt;\Documents\android-keys"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then use this code to duplicate the keystore to the folder created&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;copy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;my-upload-key.keystore&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;USERPROFILE&lt;/span&gt;&lt;span class="s2"&gt;\Documents\android-keys"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;copy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;my-upload-key.keystore&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"my-upload-key.BACKUP.keystore"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code also renames the duplicated version to this name &lt;strong&gt;"my-upload-key.BACKUP.keystore"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To verify if the duplication was successfully performed, run this code&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;dir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;USERPROFILE&lt;/span&gt;&lt;span class="s2"&gt;\Documents\android-keys"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code will show the directory to the duplicated keystore&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Important&lt;/strong&gt;&lt;br&gt;
If you lose this keystore, you permanently lose the ability to update your app on the Play Store, so you can always copy your keystore to an external hard-drive or flash.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 9: Verify Keystore and Alias&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To verify the existence of your keystore and key-alias, run this code&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;\keytool.exe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-keystore&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;my-upload-key.keystore&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code lists the aliases available. It will ask for your keystore password. if password is correct, you should see something like this &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"my-key-alias, PrivateKeyEntry"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This confirms your alias exists. However, this won't show the full details of the key-alias; it just confirms whether your alias actually exists. its basically a confirmation check.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;To View full details of your alias&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To see the full details of your key-alias and other credentials, run this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;\keytool.exe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;-keystore&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;my-upload-key.keystore&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;-alias&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;my-key-alias&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This shows your:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Alias name, &lt;/li&gt;
&lt;li&gt;Creation date, &lt;/li&gt;
&lt;li&gt;Certificate fingerprints (SHA1, SHA256), &lt;/li&gt;
&lt;li&gt;Validity period. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It will never show passwords (that’s correct).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 10: Configure Gradle Properties&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Edit &lt;strong&gt;"android/gradle.properties"&lt;/strong&gt; by adding this at the bottom of the file :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;MYAPP_UPLOAD_STORE_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;my-upload-key.keystore&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;MYAPP_UPLOAD_KEY_ALIAS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;my-key-alias&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;MYAPP_UPLOAD_STORE_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=*****&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;MYAPP_UPLOAD_KEY_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=*****&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Important&lt;/strong&gt;&lt;br&gt;
If you followed step 7 without changing anything there, then all you need to add above is your password that you set when creating your release keystore in step 7. But if you changed the alias name template or any other field there, then you need to put whatever name you used.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 11: Configure Release Signing in build.gradle&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Navigate into "android/app/build.gradle" file and add this part :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;android {
    ...

    signingConfigs {
        release {
            if (project.hasProperty('MYAPP_UPLOAD_STORE_FILE')) {
                storeFile file(MYAPP_UPLOAD_STORE_FILE)
                storePassword MYAPP_UPLOAD_STORE_PASSWORD
                keyAlias MYAPP_UPLOAD_KEY_ALIAS
                keyPassword MYAPP_UPLOAD_KEY_PASSWORD
            }
        }
    }

    buildTypes {
        release {
// Important: ensure ONLY ONE signingConfig exists here.
// If debug is present, remove it and leave only release.

            signingConfig signingConfigs.release
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 12: Build Your App Again (This Time Properly Signed)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Since you have correctly set up the release configuration, building your app again will now use the release configuration to sign your app, making it acceptable by playstore&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;/gradlew&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;clean&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;/gradlew&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;assembleRelease&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After your build has completed, let's check which signing configuration was used just to be sure everything works as it should. Run this code&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;/gradlew&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;signingReport&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;variant:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;release&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;config:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;release&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This confirms the correct keystore is being used. SigningReport always reflects the current Gradle configurations used for our most recent build, not past builds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 13: Generate Upload Certificate for Play Store (Final Step)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Google Play requires your upload certificate (done once per app). Run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;\keytool.exe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-rfc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;-keystore&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;my-upload-key.keystore&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;-alias&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;my-key-alias&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;-file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;upload_certificate.pem&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To verify the upload certificate was created successfully, Run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;notepad&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;upload_certificate.pem&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see something like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="o"&gt;---&lt;/span&gt;&lt;span class="nt"&gt;--BEGIN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CERTIFICATE-----&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;MIIF...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;---&lt;/span&gt;&lt;span class="nt"&gt;--END&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CERTIFICATE-----&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Upload this file to Google Play Console → App Signing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Common Error I Faced (And the Fix)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Error&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Execution failed for task ':app:mergeReleaseNativeLibs'
2 files found with path 'lib/arm64-v8a/libworklets.so'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Cause&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;react-native-reanimated&lt;/li&gt;
&lt;li&gt;react-native-worklets
were installed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In modern React Native, react-native-worklets should NOT be installed separately&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;npm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;uninstall&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;react-native-worklets&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;android&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;/gradlew&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;clean&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;/gradlew&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;assembleRelease&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;strong&gt;In Conclusion&lt;/strong&gt;&lt;br&gt;
This process felt confusing at first, but if you made it here, I am sure you now have an app ready for Google Play Store upload. Take note that you don’t need to manually delete old APKs before building a new one; Gradle overwrites them automatically. SigningReport reflects current Gradle config, not past builds. Google Play prefers .aab files, not APKs. Back up your keystore like your career depends on it &lt;strong&gt;(because it does)&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Did this post help simplify things for you?
&lt;/h2&gt;

&lt;p&gt;If yes, drop a ❤️ or 🦄 reaction and follow me here on dev.to. I share more practical, plain-English breakdowns like this.&lt;/p&gt;

&lt;p&gt;You can also connect with me on social media. I’d love to learn, share, and grow together with you!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LinkedIn:&lt;/strong&gt; &lt;a href="https://www.linkedin.com/in/omotola-odumosu-10a4971b2" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Twitter:&lt;/strong&gt; &lt;a href="https://x.com/yhoungbrown" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Instagram:&lt;/strong&gt; &lt;a href="https://www.instagram.com/yhoungbrownn" rel="noopener noreferrer"&gt;Instagram&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Graphics Credit: Torcheux Frédéric&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>mobile</category>
      <category>frontend</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Google Sign-In in React Native (Expo): A Practical, Production-Ready Guide</title>
      <dc:creator>Omotola Odumosu</dc:creator>
      <pubDate>Wed, 24 Dec 2025 14:40:32 +0000</pubDate>
      <link>https://dev.to/yhoungbrown/google-sign-in-in-react-native-expo-a-practical-production-ready-guide-5g48</link>
      <guid>https://dev.to/yhoungbrown/google-sign-in-in-react-native-expo-a-practical-production-ready-guide-5g48</guid>
      <description>&lt;p&gt;Google authentication is one of the most common login methods in modern mobile apps. However, despite how common it is, developers still find it difficult to implement correctly. In this article, I’ll walk you through how to set up Google Sign-In in a React Native app using Expo, explaining every step in simple terms. This guide covers the complete setup, common pitfalls, and production considerations.&lt;/p&gt;

&lt;p&gt;For simplicity, this guide assumes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You are using React Native with Expo&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You want Google Sign-In to work on Android and iOS&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You are not using Expo Go (Google Sign-In does not work on Expo Go app, so you need a development build, but dont worry, everything will be covered in this guide)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why Google Sign-In Doesn’t Work in Expo Go&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Expo Go is a generic app. Google Sign-In requires native configuration, which means you must use a development build or a production build.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Configure the OAuth Consent Screen&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Go to Google Cloud Console&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create a new project (or select an existing one)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Navigate to APIs &amp;amp; Services → OAuth consent screen&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click Get Started on the consent screen dashboard&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Then fill in the required information. This screen tells Google who you are and why you’re requesting user data&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Under App Information, fill&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   - App name
   - User support email
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Under Audience, choose&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   - External
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Under Contact Information, fill&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   - your email address
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Then, click Create. Your OAuth consent configuration has been created.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Create OAuth Client IDs&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;From the OAuth consent dashboard, you can just click the create OAuth Client button, or go to &lt;strong&gt;APIs &amp;amp; Services → Credentials → Create Credentials → OAuth Client ID&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Create three client IDs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Web Client ID&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Android Client ID&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;iOS Client ID&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even though we have three client IDs, we will only use the Web Client ID in the app. However, having the Android Client ID and iOS Client ID allows the Google Auth to work without error in our Android and iOS applications. Without them, Google authentication may fail or behave inconsistently on Android or iOS.&lt;/p&gt;

&lt;p&gt;After creating the Web Client ID, copy it. It looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;
&lt;span class="mi"&gt;432096137304&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;vj6daolt1t0c7ta233ql9avcum9brt6q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;googleusercontent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;com&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 3: Store the Client ID in an Environment Variable&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In the root of your project, create either&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;.env&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;or .env.local&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Note:&lt;/strong&gt; You do NOT need to install dotenv package; Expo already supports this.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Add your web client ID to the env file. It'll look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;
&lt;span class="nx"&gt;EXPO_PUBLIC_CLIENT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;432096137304&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;vj6daolt1t0c7ta233ql9avcum9brt6q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;googleusercontent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;com&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Important&lt;/strong&gt;&lt;br&gt;
Take note of the prefix &lt;strong&gt;EXPO_PUBLIC_&lt;/strong&gt; , it is mandatory. This prefix tells Expo to expose the variable to the client at build time. Without it, Expo will NOT expose the variable to your app&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4: Install Google Sign-In Package&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Run this in your terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;npm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="nx"&gt;react-native-google-signin/google-signin&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This installs the Google sign-in package required for our authentication to work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5: Create an Expo Development Build&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As mentioned earlier, Expo Go does not support Google Sign-In, so now we need to run a development build following Expo's official documentation&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.expo.dev/develop/development-builds/create-a-build/" rel="noopener noreferrer"&gt;https://docs.expo.dev/develop/development-builds/create-a-build/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the build is installed on your device/emulator, it is what we will use for testing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 6: Configure Google Sign-In (Root Layout)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In your root layout or root component, configure Google Sign-In once when the app loads.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;GoogleSignin&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@react-native-google-signin/google-signin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;GoogleSignin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;webClientId&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;EXPO_PUBLIC_CLIENT_ID&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;span class="p"&gt;[]);&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells Google which app is requesting authentication and which client ID to trust.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 7: Create the Google Sign-In Function&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In your sign-in screen, create the function that triggers Google authentication.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;GoogleSignin&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@react-native-google-signin/google-signin&lt;/span&gt;&lt;span class="dl"&gt;"&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;signInWithGoogle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;GoogleSignin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hasPlayServices&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;user&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;GoogleSignin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;signIn&lt;/span&gt;&lt;span class="p"&gt;();&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;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&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="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&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;log&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;message&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;p&gt;&lt;strong&gt;What’s happening here?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;hasPlayServices() checks if Google services are available&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;signIn() opens the Google account picker&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Google returns a user object after successful login&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 8: Use the User Object&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After sign-in, Google returns a user object containing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Email&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Name&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;ID token&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Profile picture&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With this returned user object, you can do whatever you like with it. you can &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Send the token to your backend&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create or authenticate users&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Store the data securely, etc.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, for the purpose of this guide, we only logged the user object in the console so you can always have a look at the returned structure. For now, logging it helps you confirm everything works.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Additional Tip:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;While this guide uses Expo, the same Google Sign-In flow applies to bare React Native apps. The JavaScript logic remains the same, but bare React Native requires additional native configuration for Android and iOS, such as manual native setup and environment variable handling, which don’t come preconfigured in bare React Native.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Google authentication can seem tricky and hard when you don't know the right approach to implementing it. But I believe I've shown you that with the right guidance, Google authentication in React Native (Expo) isn't hard; you just need to follow the right setup. Once configured, it provides a fast, secure, and familiar login experience for users.&lt;/p&gt;

&lt;p&gt;If you follow each step carefully, you’ll have Google Sign-In working smoothly in your app right now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Have you run into Google Sign-In issues before? And did this post help simplify things for you?
&lt;/h2&gt;

&lt;p&gt;If yes, drop a ❤️ or 🦄 reaction and follow me here on dev.to. I share more practical, plain-English breakdowns like this.&lt;/p&gt;

&lt;p&gt;You can also connect with me on social media. I’d love to learn, share, and grow together with you!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LinkedIn:&lt;/strong&gt; &lt;a href="https://www.linkedin.com/in/omotola-odumosu-10a4971b2" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Twitter:&lt;/strong&gt; &lt;a href="https://x.com/yhoungbrown" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Instagram:&lt;/strong&gt; &lt;a href="https://www.instagram.com/yhoungbrownn" rel="noopener noreferrer"&gt;Instagram&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Graphics Credit: LeadSync&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>oauth</category>
      <category>reactnative</category>
      <category>frontend</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Omotola Odumosu</dc:creator>
      <pubDate>Sat, 20 Dec 2025 17:24:18 +0000</pubDate>
      <link>https://dev.to/yhoungbrown/-e5b</link>
      <guid>https://dev.to/yhoungbrown/-e5b</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/iamcymentho" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1126949%2F4b0e70aa-41cc-4934-af8a-602120489e35.JPG" alt="iamcymentho"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/iamcymentho/nigerias-tax-reform-2025-what-it-means-for-you-and-why-taxengine-changes-everything-fll" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Nigeria's Tax Reform 2025: What It Means for You (And Why TaxEngine Changes Everything)&lt;/h2&gt;
      &lt;h3&gt;Odumosu Matthew ・ Nov 20&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#nigeria&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#tax&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#fintech&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#automation&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>nigeria</category>
      <category>tax</category>
      <category>fintech</category>
      <category>automation</category>
    </item>
    <item>
      <title>Easily Convert your React Native Project To Desktop App</title>
      <dc:creator>Omotola Odumosu</dc:creator>
      <pubDate>Thu, 06 Nov 2025 10:49:57 +0000</pubDate>
      <link>https://dev.to/yhoungbrown/easily-convert-your-react-native-project-to-desktop-app-4l8l</link>
      <guid>https://dev.to/yhoungbrown/easily-convert-your-react-native-project-to-desktop-app-4l8l</guid>
      <description>&lt;p&gt;As someone who has gone through the emotional turmoil you are currently experiencing when it comes to building your react native projects to a desktop app, I totally understand your frustration, and because I've been in that position at one point in my life and made it out, I'm telling you, you will too, so just stick with me.&lt;/p&gt;

&lt;p&gt;Now popularly React Native is popularly known for mobile development as it is a framework for React, which is a web development tool. Here comes the huddle using react native to build a Windows desktop (.exe or MSI) app. React Native has a broader library, which is "React native for Windows", which is basically for building desktop apps, but as you can testify to it, just like I did, the documentation is a little too thin, leaving us with more questions and confusion as opposed to solutions. Now here comes the approach I used to solve this big issue, and it got me unstuck and believing again. All the built-up tension from trying too many things and endless failed debugging mode came to an end, so I'll let you in on it if you're still with me as we go.&lt;/p&gt;

&lt;p&gt;First things first, my approach is built on you developing your mobile apps as you normally would countless number of times, up to the point that you are ready to deploy as a desktop app in production or as a desktop app in development mode, but keep in mind, while you are building your app, make sure it works on the web as well. I mean, as you test your app in development mode on your physical devices or emulator, test on the web as well to ensure your app scales to the screen, as how your app views on the web is exactly how your desktop app will view. This is a prerequisite, so do it before coming to this post, so I can literally walk you through it myself. Now I will use an Expo managed project to explain this because I make use of Expo myself, but that doesn't mean this approach doesn't work on a bare react native workflow. I just haven't tried it yet, but I believe it should, so you can give it a try.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you get to this paragraph, that means you're ready for the next step (building your desktop app). Now, since your app works on the web in development mode, run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;npx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;expo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;export&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command will build your app for Android, iOS, and the web. If this doesn't work for you, you can try&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;npx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;expo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;export:web&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; This command requires having webpack installed in your project, else it won't work. So to install webpack, run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;npm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="nx"&gt;expo/webpack-config&lt;/span&gt;&lt;span class="err"&gt;@^&lt;/span&gt;&lt;span class="nx"&gt;19.0.1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="nx"&gt;pmmmwh/react-refresh-webpack-plugin&lt;/span&gt;&lt;span class="err"&gt;@^&lt;/span&gt;&lt;span class="nx"&gt;0.5&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will install Webpack and make the build work successfully, but sometimes it might act up, so the first approach is easier.&lt;/p&gt;

&lt;p&gt;If you encounter any error or your build wasn't successful, debug the error, make necessary corrections to your project, and rebuild.&lt;/p&gt;

&lt;p&gt;If you got to this paragraph, it means your build was successful. Massive congratulations to you because you are 60% done already. Now let's proceed. Since your build was successful, check your project root folder. For people who used &lt;strong&gt;npx expo export&lt;/strong&gt;, you should find a &lt;strong&gt;dist&lt;/strong&gt; folder added to your folder structure, and for people who used &lt;strong&gt;npx expo export:web&lt;/strong&gt;, you will see a &lt;strong&gt;web-build&lt;/strong&gt; folder. These folders are important as they are the foundations of our desktop app, so confirm they exist in your project root directory.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Since we now have the dist or web-build folder, proceed, we need to install &lt;strong&gt;Tauri and Rust&lt;/strong&gt;. npm is also needed, but I don't think you can get to this stage if you don't have Node already working in your development environment. These are the packages that will make our journey seem like a walk in the park. It simplifies all the procedures. To download Rust, click the link below&lt;/p&gt;

&lt;p&gt;&lt;a href="https://rust-lang.org/tools/install/" rel="noopener noreferrer"&gt;click me to download Rust&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once downloaded, when you click on it to install, it opens a terminal with 3 options: &lt;strong&gt;choose the one that says install Rust with all of its dependencies&lt;/strong&gt;. This is usually the third option, but the arrangement might change. Once the installation is complete, run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;rustc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--version&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;cargo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--version&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your installation was successful, you should see their versions in your terminal; else try installing Rust again.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Since we now have Rust installed globally on our development environment, we will be using Tauri to convert our app to an exe, leveraging the &lt;strong&gt;dist&lt;/strong&gt; or &lt;strong&gt;web-build&lt;/strong&gt; folder we generated in stage 1. To install Rust globally, run this command&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;windows&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;npm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-g&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="nx"&gt;tauri-apps/cli&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;make sure you have Visual Studio Build Tools. I'm not sure if that is a requirement, but I have it on my device already sometimes back before running this Windows command, but you can try it without Visual Studio, and if it fails and the error message was relating to Visual Studio, then you can get it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Linux&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;cargo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tauri-cli&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And ensure you have dependencies like &lt;strong&gt;libwebkit2gtk&lt;/strong&gt; and &lt;strong&gt;build-essential&lt;/strong&gt; on your development environment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;macOS&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;brew&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tauri-cli&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Confirm if Tauri is installed successfully using this command&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;tauri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-V&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 4&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In your project directory, run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;npx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tauri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;init&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once it initialises, it will ask you a few questions like &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;What is your app name?&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;What should the window title be?&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(this means the name you want your exe file to be called)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Where are your web assets (HTML/CSS/JS) located, relative to the "/src-tauri/tauri.conf.json" file that will be created?&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(Now this is very important; it is asking for the name of the folder we generated in step 1.) This question usually has a placeholder in front of it like &lt;strong&gt;../build&lt;/strong&gt;. Just delete the "build" and replace it with your own folder name, eg &lt;strong&gt;dist&lt;/strong&gt; or &lt;strong&gt;web-build&lt;/strong&gt;&lt;br&gt;
so your answer should be like &lt;strong&gt;../dist&lt;/strong&gt; or &lt;strong&gt;../web-build&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;What is the URL of your dev server?&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(This means in development mode, when you try to run your project on the web, what was the host it loaded on). Your answer resembles something like &lt;strong&gt;&lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt;&lt;/strong&gt;. Just make sure the host you provide is where your project actually runs on&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;What is your frontend dev command?&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(meaning, when you want to run your project in development mode, what command do you use). For Expo users, your answer should be like &lt;strong&gt;npm start&lt;/strong&gt;&lt;br&gt;
 For bare react native users, just type the command you use&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;What is your frontend build command?&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(This means what command do you use to build your project for production. Now just stick with whichever command you used in stage 1 that gave you your dist or web-build file. Stick with it because that command already proved to be successful with your project, so when Tauri runs it, it won't fail. Your answer should be like &lt;strong&gt;npx expo export&lt;/strong&gt; or &lt;strong&gt;npx expo export:web&lt;/strong&gt; or whatever you used.&lt;/p&gt;

&lt;p&gt;After all these questions, check your project directory; you will see a newly added folder called &lt;strong&gt;src-tauri&lt;/strong&gt;. Open it and go to the file &lt;strong&gt;tauri.conf.json&lt;/strong&gt;. Look out for the key &lt;strong&gt;"identifier"&lt;/strong&gt; it will look like this &lt;strong&gt;"identifier": "com.tauri.dev"&lt;/strong&gt; the com.tauri.com is a placeholder, change it with your own. Your own identifier can be like &lt;strong&gt;com.yourname.yourappname&lt;/strong&gt;. &lt;em&gt;It wasn't that we generated this before, it is just a way of making our app unique, just like we are required to provide a packname for Android apps and bundleIdentifier for iOS&lt;/em&gt; . Save the file and click the &lt;strong&gt;Cargo.toml&lt;/strong&gt; file while still inside the src-tauri folder look for this &lt;strong&gt;tauri-plugin-log = "2"&lt;/strong&gt; and change it to &lt;strong&gt;tauri-plugin-log = "2.0.0"&lt;/strong&gt; save the file and lastly navigate to your &lt;strong&gt;package.json&lt;/strong&gt; file in your project root directory under the &lt;strong&gt;script key&lt;/strong&gt; add &lt;strong&gt;"tauri": "tauri"&lt;/strong&gt; so your package.json script should now look like this&lt;/p&gt;

&lt;p&gt;"scripts": {&lt;br&gt;
    "start": "expo start",&lt;br&gt;
    "reset-project": "node ./scripts/reset-project.js",&lt;br&gt;
    "android": "expo start --android",&lt;br&gt;
    "ios": "expo start --ios",&lt;br&gt;
    "web": "expo start --web",&lt;br&gt;
    "lint": "expo lint",&lt;br&gt;
    "tauri": "tauri"&lt;br&gt;
  },&lt;/p&gt;

&lt;p&gt;Now save the file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you make it here, congratulations, you're almost done. All that remains is to run the command.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;if you want to run in dev mode&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;npm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tauri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dev&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;if you want to run in Production mode&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;npm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tauri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;build&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, from here, your build should run successfully, and it might take a while for Rust to bundle all the required tools if you're using it for the first time. At this stage, I don't expect you to have any errors, but if you do, check the error message and try correcting whatever the process is complaining about. After a successful build, you should find your .exe and MSI path in this directory&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;(projectRootDirectory)\src-tauri\target\release\bundle\msi\wallora_0.1.0_x64_en-US.msi&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;(projectRootDirectory)\src-tauri\target\release\bundle\nsis\wallora_0.1.0_x64-setup.exe&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now I know it's taken a bit of our time getting here, but I'd say it's worth it, won't you agree? I know how frustrating the chaos, debugging, and not figuring out what to do to resolve a failure can be, which is why I took my time to take us through all the steps detailedly. Congratulations once again for reaching this part, and now that frustration is behind you. &lt;/p&gt;

&lt;h2&gt;
  
  
  Did this post help simplify things for you?
&lt;/h2&gt;

&lt;p&gt;I hope this helped you a lot. If yes, drop a ❤️ or 🦄 reaction and follow me here on dev.to. I share more practical, plain-English breakdowns like this.&lt;/p&gt;

&lt;p&gt;You can also connect with me on social media. I’d love to learn, share, and grow together with you!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LinkedIn:&lt;/strong&gt; &lt;a href="https://www.linkedin.com/in/omotola-odumosu-10a4971b2" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Twitter:&lt;/strong&gt; &lt;a href="https://x.com/yhoungbrown" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Instagram:&lt;/strong&gt; &lt;a href="https://www.instagram.com/yhoungbrownn" rel="noopener noreferrer"&gt;Instagram&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Graphics Credit: mobilecoderz.com&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>desktopapp</category>
      <category>frontend</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Build Your React Native Project into an APK</title>
      <dc:creator>Omotola Odumosu</dc:creator>
      <pubDate>Tue, 23 Sep 2025 09:09:10 +0000</pubDate>
      <link>https://dev.to/yhoungbrown/build-your-react-native-project-into-an-apk-59c</link>
      <guid>https://dev.to/yhoungbrown/build-your-react-native-project-into-an-apk-59c</guid>
      <description>&lt;p&gt;As a React Native Developer, some time back in the course of my career, I could spin up an app from scratch: designing, coding, debugging, and everything ran smoothly in development. But when it came time to ship it as an APK, so I could share it? Here comes the Roadblock.&lt;/p&gt;

&lt;p&gt;Honestly, it felt like building a whole house, installing a front door… and then realizing you can’t open it. Ridiculous, right? Yet, that’s exactly the challenge I faced.&lt;/p&gt;

&lt;p&gt;So if you’re stuck at this same stage, don’t worry, you’re not alone. In this post, I’ll walk you through the exact process I learned (step by step) to successfully build your React Native project into an APK using Expo CLI &amp;amp; EAS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Install EAS CLI
&lt;/h2&gt;

&lt;p&gt;First, install the Expo Application Services (EAS) CLI globally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; eas-cli

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;👉 Docs: &lt;a href="https://docs.expo.dev/build/setup/" rel="noopener noreferrer"&gt;https://docs.expo.dev/build/setup/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you’re on macOS and building for iOS, you’ll also need an Apple Developer account.&lt;/p&gt;

&lt;h1&gt;
  
  
  Step 2: Fix PowerShell Permissions (Windows Only)
&lt;/h1&gt;

&lt;p&gt;Before running builds on Windows, allow PowerShell to execute EAS commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Set-ExecutionPolicy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ExecutionPolicy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;RemoteSigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Scope&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Process&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command is only active for a session. Once you close your terminal/cmd prompt, you'll have to rerun the code to allow eas commands to execute. So you can run it in your project terminal&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Build with EAS
&lt;/h2&gt;

&lt;p&gt;Run this command inside your React Native project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
eas build &lt;span class="nt"&gt;--platform&lt;/span&gt; android

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For iOS, just replace Android with iOS (but it’ll prompt for your Apple Developer account).&lt;/p&gt;

&lt;p&gt;👉 If the build fails, check your terminal for the error message and fix whatever is causing the failure from within your code and retry. &lt;strong&gt;After the build succeeds&lt;/strong&gt;, your built file will be in &lt;strong&gt;.aab format&lt;/strong&gt;, downloadable from the Expo dashboard or via the download link displayed in your terminal after the successful build.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Download &amp;amp; Set Up BundleTool
&lt;/h2&gt;

&lt;p&gt;Google requires AAB files for Play Store uploads, but for direct installation, we’ll need an APK. That’s where BundleTool comes in.&lt;/p&gt;

&lt;p&gt;Download &lt;strong&gt;bundletool-all-x.x.x.jar&lt;/strong&gt; from the official GitHub releases. Create a folder (e.g. on Desktop) and put both the &lt;strong&gt;.aab file&lt;/strong&gt; and &lt;strong&gt;the BundleTool.jar&lt;/strong&gt; inside.&lt;/p&gt;

&lt;p&gt;👉 GitHub: &lt;a href="https://github.com/google/bundletool/releases" rel="noopener noreferrer"&gt;https://github.com/google/bundletool/releases&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Getting your Signing Credentials
&lt;/h2&gt;

&lt;p&gt;Unsigned APKs won’t install on devices. You’ll need your keystore credentials, but don't worry, while you were building your app with the &lt;strong&gt;"eas build"&lt;/strong&gt; command in &lt;strong&gt;step 3&lt;/strong&gt;, Expo already helped you create a keystore credential it used to convert our project into &lt;strong&gt;.aab file&lt;/strong&gt; , now we just need that same credential to convert the &lt;strong&gt;.aab file&lt;/strong&gt; to &lt;strong&gt;.apk file&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Run in project root directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
eas credentials &lt;span class="nt"&gt;--platform&lt;/span&gt; android

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Follow the prompts to download credentials.json (contains passwords) and keystore.jks (binary file) by using the arrow key to select &lt;strong&gt;"Download credentials from EAS to credentials.json"&lt;/strong&gt; from the available options. Once it is downloaded, &lt;strong&gt;keep these safe as they are very important.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now navigate back to the folder you created earlier holding both the &lt;strong&gt;.aab file&lt;/strong&gt; and the &lt;strong&gt;jar bundletool&lt;/strong&gt; via your cmd prompt/terminal, build your signed APK with the credential details in credential.json file by running the code below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;
&lt;span class="n"&gt;java&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;jar&lt;/span&gt; &lt;span class="n"&gt;bundletool&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;xx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;jar&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;apks&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;bundle&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="no"&gt;YOUR&lt;/span&gt; &lt;span class="no"&gt;APP&lt;/span&gt; &lt;span class="no"&gt;NAME&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;aab&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="no"&gt;YOUR&lt;/span&gt; &lt;span class="no"&gt;APP&lt;/span&gt; &lt;span class="no"&gt;NAME&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;apks&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;ks&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="no"&gt;PATH&lt;/span&gt; &lt;span class="no"&gt;TO&lt;/span&gt; &lt;span class="no"&gt;KEYSTORE&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;JKS&lt;/span&gt; &lt;span class="no"&gt;FILE&lt;/span&gt; &lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="no"&gt;PROJECT&lt;/span&gt; &lt;span class="no"&gt;ROOT&lt;/span&gt; &lt;span class="no"&gt;DIRECTORY&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;ks&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;alias&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="no"&gt;YOUR&lt;/span&gt; &lt;span class="no"&gt;KEYSTORE&lt;/span&gt; &lt;span class="no"&gt;ALIAS&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;ks&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;pass&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nl"&gt;pass:&lt;/span&gt;&lt;span class="no"&gt;YOUR&lt;/span&gt; &lt;span class="no"&gt;KEYSTORE&lt;/span&gt; &lt;span class="no"&gt;PASSWORD&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;pass&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nl"&gt;pass:&lt;/span&gt;&lt;span class="no"&gt;YOUR&lt;/span&gt; &lt;span class="no"&gt;KEY&lt;/span&gt; &lt;span class="no"&gt;PASSWORD&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;universal&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E.g.: your "ks path" in the code above should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nt"&gt;--ks&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"C:\Users\Young\My Files\Coding\React Native Projects\ProductionReady\credentials\android\keystore.jks"&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But ensure it is the correct path to your keystore.jks file in your project on your local device. &lt;/p&gt;

&lt;p&gt;👉 Replace &lt;strong&gt;bundletool-all-x.xx.x.jar&lt;/strong&gt; with your actual downloaded bundletool name up to its version &lt;strong&gt;e.g: bundletool-all-1.21.2.jar&lt;/strong&gt; , and &lt;strong&gt;YourAppName.aab&lt;/strong&gt; with your own AAB file name, the &lt;strong&gt;ks-key-alias&lt;/strong&gt; with your own key alias, &lt;strong&gt;which can be found in the credentials.json file we generated in step 5.&lt;/strong&gt; you should see the credentials.json file in your project root directory.&lt;/p&gt;

&lt;p&gt;This step creates an &lt;strong&gt;.apks file&lt;/strong&gt; (which is really just a ZIP file).&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Extract the Installable APK
&lt;/h2&gt;

&lt;p&gt;Since .apks is really a ZIP, rename and extract the .apk file from it by running this code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Rename-Item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;YourApp.apks&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-NewName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;YourApp.zip&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Expand-Archive&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;YourApp.zip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-DestinationPath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;extracted-apks&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside the new &lt;strong&gt;extracted-apks folder&lt;/strong&gt;, you’ll find &lt;strong&gt;universal.apk&lt;/strong&gt; 🎉. That’s your signed, installable APK, which you can install on your devices, share with friends, testers, etc.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 7: If You Update Your Code…
&lt;/h2&gt;

&lt;p&gt;Whenever you make changes, just:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Push code to GitHub (to always have an updated version remotely)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run &lt;strong&gt;eas build --platform android or ios&lt;/strong&gt;, as the case may be&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Convert the new AAB to APK following the same process&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  📌 Important Note on Play Store Uploads
&lt;/h2&gt;

&lt;p&gt;For Google Play Store, you must upload the AAB file, not the APK.&lt;/p&gt;

&lt;p&gt;The APK is mainly for testing, sharing, or sideloading.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;If you’ve ever felt stuck after building a full React Native project but not being able to generate an APK, trust me, I’ve been there. It’s frustrating, but the solution is straightforward once you know the steps.&lt;/p&gt;

&lt;p&gt;Now you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Build with EAS&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Convert AAB → APK with BundleTool&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Sign it properly with your keystore&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Share or install it directly &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;💬 Did you face the same issue at any point in your career? Drop your experience in the comments, I’d love to hear!&lt;/p&gt;

&lt;h2&gt;
  
  
  Did this post help simplify things for you?
&lt;/h2&gt;

&lt;p&gt;If yes, drop a ❤️ or 🦄 reaction and follow me here on dev.to. I share more practical, plain-English breakdowns like this.&lt;/p&gt;

&lt;p&gt;You can also connect with me on social media. I’d love to learn, share, and grow together with you!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LinkedIn:&lt;/strong&gt; &lt;a href="https://www.linkedin.com/in/omotola-odumosu-10a4971b2" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Twitter:&lt;/strong&gt; &lt;a href="https://x.com/yhoungbrown" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Instagram:&lt;/strong&gt; &lt;a href="https://www.instagram.com/yhoungbrownn" rel="noopener noreferrer"&gt;Instagram&lt;/a&gt;&lt;/p&gt;

</description>
      <category>mobile</category>
      <category>reactnative</category>
      <category>software</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Clean Architecture in C#</title>
      <dc:creator>Omotola Odumosu</dc:creator>
      <pubDate>Thu, 11 Sep 2025 07:29:18 +0000</pubDate>
      <link>https://dev.to/yhoungbrown/coding-architecture-layers-why-big-companies-use-it-in-c-and-why-you-should-too-1lgo</link>
      <guid>https://dev.to/yhoungbrown/coding-architecture-layers-why-big-companies-use-it-in-c-and-why-you-should-too-1lgo</guid>
      <description>&lt;p&gt;As I continue my journey into backend development, one question keeps bugging me: &lt;em&gt;Why can’t we just write all the logic inside the controller?&lt;/em&gt; It feels so simple. grabbing the data, applying some rules, and updating the database, all in one place. Oh, and I did, but I learnt it wasn't efficient for big companies, and you can't break into big companies with code like this, but I never really understood it or why&lt;/p&gt;

&lt;p&gt;But here’s the reality: while that might be fine for a small side project, in &lt;strong&gt;large-scale applications (like financial systems)&lt;/strong&gt; it quickly turns into chaos. That’s why big companies insist on using &lt;strong&gt;layered architecture&lt;/strong&gt;. Splitting responsibilities into &lt;strong&gt;Controllers, Business Logic (Services), and Repositories&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For the longest time, I didn’t fully grasp why this separation mattered. I researched, experimented, and honestly struggled until it finally &lt;em&gt;clicked&lt;/em&gt;. Once I understood, it completely changed how I think about structuring my software. And because I know many others are walking the same path, I want to share this knowledge in &lt;strong&gt;layman’s terms&lt;/strong&gt; so it finally makes sense.&lt;/p&gt;

&lt;p&gt;Let’s dive in with &lt;strong&gt;simple analogies, clear diagrams, and practical code&lt;/strong&gt; to make it stick.&lt;/p&gt;




&lt;h2&gt;
  
  
  Think of Your App Like a Company
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Who It Is&lt;/th&gt;
&lt;th&gt;What It Does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Controller&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Front Desk Clerk&lt;/td&gt;
&lt;td&gt;Takes in requests and passes them along&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Service&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Bank Manager&lt;/td&gt;
&lt;td&gt;Applies rules, makes decisions, enforces policy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Repository&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Records Clerk&lt;/td&gt;
&lt;td&gt;Fetches and updates records in the system&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ILogger&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Security Camera&lt;/td&gt;
&lt;td&gt;Keeps a history of what happened (audits/logs)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Serilog/NLog&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Storage Room&lt;/td&gt;
&lt;td&gt;Decides &lt;em&gt;where&lt;/em&gt; those logs are stored&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  The Flow (Diagram)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
[ User Request ]

      ↓

[ Controller ] → Receives request and passes it along to the Services

      ↓

[ Service ] → Applies business rules and logic

      ↓

[ Repository ] → Talks to the database (fetching and updating details only)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Controller: The Front Desk
&lt;/h2&gt;

&lt;p&gt;The controller is the &lt;strong&gt;entry point&lt;/strong&gt;. It doesn’t make decisions or talk to the database; it just receives requests and forwards them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ApiController&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"api/transfer"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TransferController&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ControllerBase&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;

   &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ITransferService&lt;/span&gt; &lt;span class="n"&gt;_service&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;



   &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;TransferController&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ITransferService&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

   &lt;span class="p"&gt;{&lt;/span&gt;

       &lt;span class="n"&gt;_service&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;service&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;span class="n"&gt;HttpPost&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

   &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IActionResult&lt;/span&gt; &lt;span class="nf"&gt;TransferMoney&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;FromBody&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;TransferRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

   &lt;span class="p"&gt;{&lt;/span&gt;

       &lt;span class="n"&gt;_service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Transfer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FromAccount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ToAccount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

       &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Transfer completed"&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;
  
  
  Service: The Bank Manager
&lt;/h2&gt;

&lt;p&gt;The service (business logic layer) applies &lt;strong&gt;rules&lt;/strong&gt;. For example, “Does the sender have enough money to transfer?”&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TransferService&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ITransferService&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;

   &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IAccountRepository&lt;/span&gt; &lt;span class="n"&gt;_repository&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

   &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TransferService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;



   &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;TransferService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IAccountRepository&lt;/span&gt; &lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TransferService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

   &lt;span class="p"&gt;{&lt;/span&gt;

       &lt;span class="n"&gt;_repository&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

       &lt;span class="n"&gt;_logger&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

   &lt;span class="p"&gt;}&lt;/span&gt;



   &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Transfer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;fromAcc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;toAcc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

   &lt;span class="p"&gt;{&lt;/span&gt;

       &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAccount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fromAcc&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

       &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAccount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;toAcc&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;



       &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Balance&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

       &lt;span class="p"&gt;{&lt;/span&gt;

           &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogWarning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Insufficient funds for account {Account}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fromAcc&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="nf"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Insufficient funds"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

       &lt;span class="p"&gt;}&lt;/span&gt;



       &lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Balance&lt;/span&gt; &lt;span class="p"&gt;-=&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

       &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Balance&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;



       &lt;span class="n"&gt;_repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UpdateAccount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

       &lt;span class="n"&gt;_repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UpdateAccount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;



       &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Transferred {Amount} from {From} to {To}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fromAcc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;toAcc&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;
  
  
  Repository: The Records Clerk
&lt;/h2&gt;

&lt;p&gt;The repository is where &lt;strong&gt;data access happens&lt;/strong&gt;. No rules, no decisions, just reading and writing data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AccountRepository&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IAccountRepository&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;

   &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;BankingDbContext&lt;/span&gt; &lt;span class="n"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

   &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AccountRepository&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;



   &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;AccountRepository&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BankingDbContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AccountRepository&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

   &lt;span class="p"&gt;{&lt;/span&gt;

       &lt;span class="n"&gt;_context&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

       &lt;span class="n"&gt;_logger&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

   &lt;span class="p"&gt;}&lt;/span&gt;



   &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Account&lt;/span&gt; &lt;span class="nf"&gt;GetAccount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

   &lt;span class="p"&gt;{&lt;/span&gt;

       &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Fetching account {AccountId}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

       &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Accounts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FirstOrDefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

   &lt;span class="p"&gt;}&lt;/span&gt;



   &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;UpdateAccount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Account&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

   &lt;span class="p"&gt;{&lt;/span&gt;

       &lt;span class="n"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Accounts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

       &lt;span class="n"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveChanges&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

       &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Updated account {AccountId}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&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;
  
  
  Logging: The Security Camera
&lt;/h2&gt;

&lt;p&gt;In finance, logging is crucial. Every transaction, success, or error must be tracked. That’s where &lt;strong&gt;ILogger&lt;/strong&gt; comes in.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;
&lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Transaction {TransactionId} completed at {Time}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UtcNow&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to process transaction {TransactionId}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But ILogger is just an &lt;strong&gt;interface&lt;/strong&gt;. You still need a logging provider like &lt;strong&gt;Serilog&lt;/strong&gt; or &lt;strong&gt;NLog&lt;/strong&gt; to decide &lt;em&gt;where&lt;/em&gt; to store those logs (file, cloud, DB, etc.).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;
&lt;span class="c1"&gt;// Program.cs setup example with Serilog&lt;/span&gt;

&lt;span class="n"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Logger&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;LoggerConfiguration&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

   &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteTo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;File&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"logs/transactions.txt"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

   &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateLogger&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;



&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseSerilog&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Summary: Who Does What
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Responsibility&lt;/th&gt;
&lt;th&gt;Analogy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Controller&lt;/td&gt;
&lt;td&gt;Accepts requests and returns responses&lt;/td&gt;
&lt;td&gt;Front Desk Clerk&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Service&lt;/td&gt;
&lt;td&gt;Applies business rules&lt;/td&gt;
&lt;td&gt;Bank Manager&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Repository&lt;/td&gt;
&lt;td&gt;Handles data access&lt;/td&gt;
&lt;td&gt;Records Clerk&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ILogger&lt;/td&gt;
&lt;td&gt;Records actions, errors, and audits&lt;/td&gt;
&lt;td&gt;Security Camera&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Serilog/NLog&lt;/td&gt;
&lt;td&gt;Stores logs (file, DB, cloud, etc.)&lt;/td&gt;
&lt;td&gt;Storage Room&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Big Companies vs Small Projects
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Small projects:&lt;/strong&gt; You &lt;em&gt;might&lt;/em&gt; mix everything in the controller. It works for a while.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Big companies:&lt;/strong&gt; This structure is &lt;strong&gt;mandatory&lt;/strong&gt;. It makes apps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Easier to test&lt;/li&gt;
&lt;li&gt;Easier to scale&lt;/li&gt;
&lt;li&gt;Easier to maintain&lt;/li&gt;
&lt;li&gt;Safer (especially for money-related systems)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;View it this way:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If it’s about handling API requests → &lt;strong&gt;Controller&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;If it’s about rules, logic, or decisions → &lt;strong&gt;Service&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;If it’s about fetching or saving data → &lt;strong&gt;Repository&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;If it’s about tracking activity → &lt;strong&gt;ILogger&lt;/strong&gt; (with Serilog/NLog to store it)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;If you’re hacking together a side project, you can cut corners. But if you’re aiming to work in big companies or build software that can grow and last, this &lt;strong&gt;layered architecture&lt;/strong&gt; is the foundation you &lt;em&gt;must&lt;/em&gt; follow.&lt;/p&gt;

&lt;p&gt;Always Think About: Front desk clerk, bank manager, records clerk, and security camera, each with their own role. This helps you remember and know under which classification/section a code is supposed to be. Keeping them separate makes your system stay clean, scalable, and professional.&lt;/p&gt;

&lt;h2&gt;
  
  
  Did this post help simplify things for you?
&lt;/h2&gt;

&lt;p&gt;If yes, drop a ❤️ or 🦄 reaction and follow me here on dev.to. I share more practical, plain-English breakdowns like this.&lt;/p&gt;

&lt;p&gt;You can also connect with me on social media. I’d love to learn, share, and grow together with you!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LinkedIn:&lt;/strong&gt; &lt;a href="https://www.linkedin.com/in/omotola-odumosu-10a4971b2" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Twitter:&lt;/strong&gt; &lt;a href="https://x.com/yhoungbrown" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Instagram:&lt;/strong&gt; &lt;a href="https://www.instagram.com/yhoungbrownn" rel="noopener noreferrer"&gt;Instagram&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Graphics Credit: Ramesh Fadatare&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>backend</category>
      <category>csharp</category>
      <category>dotnet</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Understanding SOLID Principles in Layman’s Terms</title>
      <dc:creator>Omotola Odumosu</dc:creator>
      <pubDate>Fri, 29 Aug 2025 08:12:07 +0000</pubDate>
      <link>https://dev.to/yhoungbrown/understanding-solid-principles-in-laymans-terms-2h3e</link>
      <guid>https://dev.to/yhoungbrown/understanding-solid-principles-in-laymans-terms-2h3e</guid>
      <description>&lt;p&gt;If you’ve ever felt like the SOLID principles sound too abstract, don’t worry; in this article we’ll break them down into simple, relatable examples with analogies. By the end, you’ll understand each one in a way you’ll never forget. Let’s dive in!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Single Responsibility Principle (SRP)&lt;/strong&gt;&lt;br&gt;
This means one class should only do one job. Think of your phone. You have a camera app to take pictures, you have a messaging app to send messages, and you have a music app to play songs.&lt;/p&gt;

&lt;p&gt;Each app does one job well. If your camera app also tried to send messages and play music, it would become messy and hard to maintain.&lt;/p&gt;

&lt;p&gt;In code terms, this means:&lt;/p&gt;

&lt;p&gt;❌ Don’t do this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcnw3vfusyi3mdpzs7rjn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcnw3vfusyi3mdpzs7rjn.png" alt=" " width="655" height="194"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;✅ Do this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv535dcxi52vfkv3l3sui.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv535dcxi52vfkv3l3sui.png" alt=" " width="655" height="359"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each class does one thing well, making it easier to maintain, test, and reuse.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Open/Closed Principle (OCP)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This means classes should be open for extension but closed for modification. In the sense that you should be able to add new features without changing existing code.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
Imagine you have a payment system. Initially, it only supports credit cards.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuojiajzf7vnqoi5jmefp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuojiajzf7vnqoi5jmefp.png" alt=" " width="662" height="188"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, if you want to add PayPal, you’d need to edit the same class, which risks breaking existing code.&lt;/p&gt;

&lt;p&gt;✅ Better approach:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F861dzhr14lv21bqdb6zs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F861dzhr14lv21bqdb6zs.png" alt=" " width="665" height="341"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now you can extend by adding new classes instead of editing old ones.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Liskov Substitution Principle (LSP)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is only saying that a subclass (child class) should be able to replace its parent class without breaking things.&lt;/p&gt;

&lt;p&gt;Example with birds:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd4uy1i7cquuhsqckacek.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd4uy1i7cquuhsqckacek.png" alt=" " width="660" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This breaks LSP. Even though Penguin is a bird, substituting it as a flying bird is wrong.&lt;/p&gt;

&lt;p&gt;✅ Fix: Split responsibilities&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhbaze20h0ajc23sb2iej.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhbaze20h0ajc23sb2iej.png" alt=" " width="661" height="274"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Solution:&lt;/p&gt;

&lt;p&gt;Bird = general parent class&lt;br&gt;
FlyingBird = parent class for birds that fly&lt;br&gt;
Eagle = subclass (child class) that can fly&lt;br&gt;
Penguin = subclass (child class) that doesn’t fly&lt;/p&gt;

&lt;p&gt;Now we can substitute any subclass correctly without confusion.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Interface Segregation Principle (ISP)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here, although the principle name is like a combination of big words, here is what it simply means. Don’t force a class to implement things it doesn’t need.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
Let’s say we have a Worker interface:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc8uc7qit5h6ok87gulpc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc8uc7qit5h6ok87gulpc.png" alt=" " width="663" height="109"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, we create a Robot class:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffc2q33lh42lnzw7hr8uv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffc2q33lh42lnzw7hr8uv.png" alt=" " width="658" height="125"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The robot is forced to implement Eat() even though it doesn’t need it.&lt;/p&gt;

&lt;p&gt;✅ Fix: split into smaller interfaces:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0hpjb2b507fxsvhwfn34.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0hpjb2b507fxsvhwfn34.png" alt=" " width="664" height="367"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now a robot who is also a worker doesn’t need to implement Eat().&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Dependency Inversion Principle (DIP)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This principle is saying we should rely on abstractions, not on concrete implementations, as it makes it easier to add features without breaking or modifying our existing code and ensures testing works flawlessly.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
Imagine your phone and apps again. Your phone doesn’t care if you install WhatsApp, Telegram, or Signal. It just says, “I’ll allow any messaging app as long as it follows the rules.”&lt;/p&gt;

&lt;p&gt;In code:&lt;/p&gt;

&lt;p&gt;❌ Without DIP:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6o61vly214oul8568qvo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6o61vly214oul8568qvo.png" alt=" " width="658" height="112"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here Notification is stuck with Email.&lt;/p&gt;

&lt;p&gt;✅ With DIP:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft5b1hbipnqf87mcpv7bz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft5b1hbipnqf87mcpv7bz.png" alt=" " width="663" height="464"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now you can swap email, SMS, or even WhatsApp without changing notifications.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bonus: Why SOLID Matters So Much&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It’s important to know that &lt;strong&gt;SOLID&lt;/strong&gt; isn’t just about writing “good code.” This is like saying, "Your smartphone, even though it has multiple features like browsing, playing music, watching movies, etc., you just want to restrict to making/receiving calls." &lt;strong&gt;SOLID&lt;/strong&gt; is more than that. It’s about writing maintainable, scalable, and flexible code that doesn’t break when you add new features. You might not know the magnitude of this when you're writing your code from the initial start, but a few years down the line when you have to maintain or add features to code another developer wrote, or even if you wrote it, if &lt;strong&gt;SOLID&lt;/strong&gt; was applied at the inception of the code, you'll appreciate it massively because it'll make your work a lot easier. &lt;/p&gt;

&lt;p&gt;By following &lt;strong&gt;SOLID&lt;/strong&gt;, your code becomes easier to test, you reduce bugs, and you can adapt faster to changes&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SOLID&lt;/strong&gt; might look intimidating at first, but when you think about apps on your phone, birds that may or may not fly, and robots that don’t eat, it becomes much easier to digest.&lt;/p&gt;

&lt;p&gt;Once you practice applying it, you’ll never forget it.&lt;/p&gt;

&lt;p&gt;This is the layman’s guide to &lt;strong&gt;SOLID&lt;/strong&gt; I promised. What do you think? Did I break it down enough for your easy assimilation? Drop your opinions in the comment section.&lt;/p&gt;

&lt;p&gt;I remain Omotola Odumosu. Catch me on my socials below.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LinkedIn Account:&lt;/strong&gt; &lt;a href="https://www.linkedin.com/in/omotola-odumosu-10a4971b2/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Twitter(X) Account:&lt;/strong&gt; &lt;a href="https://x.com/yhoungbrown?s=21" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>backenddevelopment</category>
      <category>solidprinciples</category>
    </item>
    <item>
      <title>🔐 ASP.NET Core Identity vs JWT (Without Identity)</title>
      <dc:creator>Omotola Odumosu</dc:creator>
      <pubDate>Tue, 19 Aug 2025 10:42:42 +0000</pubDate>
      <link>https://dev.to/yhoungbrown/aspnet-core-identity-vs-jwt-without-identity-53hm</link>
      <guid>https://dev.to/yhoungbrown/aspnet-core-identity-vs-jwt-without-identity-53hm</guid>
      <description>&lt;p&gt;Over the past few projects, I’ve had to make this exact choice: Do I go with ASP.NET Identity or roll my own JWT solution? Each comes with its pros and cons, and I’ve learned it really depends on the project and here’s how I usually view and explain it in simple terms&lt;/p&gt;

&lt;p&gt;Imagine you’ve just moved into a new house. You need locks for your doors, and you have two options:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 1: ASP.NET Core Identity (The Professional Locksmith)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You call a locksmith (ASP.NET Identity).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;They bring the lock, install it, and ensure it’s tamper-proof.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Every time you use the door, the lock automatically checks the key.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You don’t worry about the details, it just works, and it’s secure.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Less work for you, industry best practices, and battle-tested security.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 2: JWT Without Identity (Build Your Own Lock)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You decide to build your own lock (JWT without Identity).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You design how the keys are cut (hashing &amp;amp; storing passwords).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You decide how the lock works (generating JWTs).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Every time someone uses the door, you must check if the key really fits (manual validation).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;More flexibility, but you carry the full responsibility for security.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Trade-off&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use ASP.NET Identity if you want a secure, ready-made solution.&lt;/p&gt;

&lt;p&gt;Use JWT without Identity if you need full control, but be prepared to handle every detail carefully.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;💡 One lesson I’ve learned about ASP.NET Identity&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It doesn’t always provide clear error handling out-of-the-box when signup or login fails. That leaves the client guessing what went wrong.&lt;/p&gt;

&lt;p&gt;Here’s a better approach: Create a custom login or signup endpoint that wraps UserManager or SignInManager calls in a try-catch and returns clear error messages:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwgh8cqyjp81y5nh3yuff.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwgh8cqyjp81y5nh3yuff.png" alt=" " width="630" height="486"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6ty4scqkxu6104f250xb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6ty4scqkxu6104f250xb.png" alt=" " width="615" height="212"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This way, your API communicates exactly why a login/signup failed instead of leaving the client in the dark.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example of JWT without Identity (build-your-own lock)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;On the flip side, if you choose the JWT without Identity route, you have to manually handle things like token creation and signing.&lt;/p&gt;

&lt;p&gt;Here’s a simple example of generating a JWT:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx4tdws1twoofnl6kwi26.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx4tdws1twoofnl6kwi26.png" alt=" " width="619" height="309"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the DIY (Do It Yourself) lock-making approach: you’re in control of how tokens are created, but you’re also responsible for keeping everything secure (keys, expiration, claims, etc.).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Industry Insight&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I’ve noticed that larger enterprises often lean towards the JWT without Identity approach, since it gives them full control and the ability to fine-tune authentication to their unique business logic.&lt;/p&gt;

&lt;p&gt;But at the end of the day, both approaches can keep your house (app) safe 🏡. The difference is whether you’d rather trust the locksmith or build your own lock system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LinkedIn Account:&lt;/strong&gt; &lt;a href="https://www.linkedin.com/in/omotola-odumosu-10a4971b2/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Twitter(X) Account:&lt;/strong&gt; &lt;a href="https://x.com/yhoungbrown?s=21" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>authentication</category>
      <category>backenddevelopment</category>
    </item>
  </channel>
</rss>
