<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Threadripper Publication]]></title><description><![CDATA[Threadripper Publication]]></description><link>https://blogs.ijlalahmad.dev</link><generator>RSS for Node</generator><lastBuildDate>Mon, 27 Apr 2026 15:49:08 GMT</lastBuildDate><atom:link href="https://blogs.ijlalahmad.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Generate Apk using Github Actions]]></title><description><![CDATA[Overview
When developing Android apps, it's easy to overlook the fact that the APK installed via Android Studio is specific to your device. While Android Studio provides an option to manually generate an APK for distribution, sharing it with others c...]]></description><link>https://blogs.ijlalahmad.dev/generate-apk-using-github-actions</link><guid isPermaLink="true">https://blogs.ijlalahmad.dev/generate-apk-using-github-actions</guid><category><![CDATA[Android]]></category><category><![CDATA[android app development]]></category><category><![CDATA[Android Studio]]></category><category><![CDATA[GitHub]]></category><category><![CDATA[github-actions]]></category><category><![CDATA[ci-cd]]></category><category><![CDATA[automation]]></category><category><![CDATA[android apps]]></category><dc:creator><![CDATA[Ijlal Ahmad]]></dc:creator><pubDate>Thu, 20 Mar 2025 22:16:51 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1742423593563/a5e6bccc-d25e-4fb8-ada0-97596e3d02fc.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-overview">Overview</h1>
<p>When developing Android apps, it's easy to overlook the fact that the APK installed via Android Studio is specific to your device. While Android Studio provides an option to manually generate an APK for distribution, sharing it with others can be cumbersome—especially for personal projects or portfolio apps that you want to showcase alongside your GitHub repository.</p>
<p>Web developers can conveniently share their hosted projects via a simple link in their README or sidebar. But for Android developers, the process of manually generating an APK, uploading it to GitHub Releases, and writing release notes every time you update your project can be tedious and time-consuming.</p>
<p>What if I told you this entire process could be automated? Imagine just pushing your code to GitHub, and everything else—APK generation, release creation, and release notes—happens automatically.</p>
<p>In this article, we’ll explore how to achieve this using <strong>GitHub Actions</strong>, a built-in automation tool within GitHub. No additional tools are required—just pure automation.</p>
<p>Let's get started! 🚀</p>
<h1 id="heading-what-is-github-actions">What is Github Actions?</h1>
<p>GitHub Actions is a <strong>CI/CD (Continuous Integration/Continuous Deployment) platform</strong> that enables you to automate your software development workflows—right from your GitHub repository. With GitHub Actions, you can automate tasks such as building, testing, and deploying your code without relying on external CI/CD tools.</p>
<p>It provides <strong>full-fledged virtual machines</strong> where you can run virtually any automation script, making it highly flexible and easily integrable with various tools and services. Whether you need to build an APK, deploy a website, or run scheduled tasks, GitHub Actions allows you to streamline your workflow efficiently.</p>
<h1 id="heading-maintaining-a-constant-app-fingerprint-for-oauth-google-sign-in-etc">Maintaining a Constant App Fingerprint for OAuth (Google Sign-In, etc.)</h1>
<p>If our Android app integrates with services like <strong>Google OAuth</strong>, we’re required to provide the <strong>SHA1 fingerprint</strong> of our app’s signing key. When running the app in <strong>debug mode</strong>, this fingerprint is generated from our local debug keystore.</p>
<p>However, when the app is built on a different machine—such as a <strong>GitHub Actions CI runner</strong>—a new debug keystore is created, resulting in a <strong>different SHA1 fingerprint</strong>. This mismatch can cause OAuth-based authentication (like Google Sign-In) to <strong>fail</strong> on CI-generated APKs.</p>
<h3 id="heading-solution-use-a-dedicated-release-keystore">Solution: Use a Dedicated Release Keystore</h3>
<p>To ensure a <strong>consistent SHA1 fingerprint</strong> across all builds (local, CI, production):</p>
<ul>
<li><p>Create a <strong>dedicated release keystore</strong>.</p>
</li>
<li><p>Use this keystore to <strong>sign our release builds</strong>, both locally and in CI.</p>
</li>
<li><p>Share the keystore <strong>securely</strong> with GitHub Actions (e.g., use <strong>GitHub Secrets</strong> and encode it in <strong>base64</strong>).</p>
</li>
<li><p>Register the <strong>SHA1 fingerprint</strong> of this keystore with our OAuth providers (Google, Facebook, etc.).</p>
</li>
</ul>
<blockquote>
<p><strong>Important:</strong> The keystore is a <strong>critical asset</strong>. It’s used to sign APKs for the <strong>Google Play Store</strong>, and losing access can prevent future updates. If leaked, attackers could impersonate our app.<br /><strong>We must never commit it to our repository. It should be stored securely.</strong></p>
</blockquote>
<h2 id="heading-create-a-signed-release-build">Create a Signed Release Build</h2>
<p>To generate a signed release build, follow these steps:</p>
<h3 id="heading-step-1-create-a-keystore">Step 1: Create a Keystore</h3>
<ol>
<li><p>Open Android Studio.</p>
</li>
<li><p>Go to <strong>Build → Generate Signed Bundle / APK</strong>.</p>
</li>
<li><p>Choose <strong>APK</strong> or <strong>App Bundle</strong>, then click <strong>Next</strong>.</p>
</li>
<li><p>Click <strong>Create new</strong> to generate a new keystore.</p>
</li>
<li><p>Save the keystore file (<code>.jks</code>) securely.</p>
</li>
<li><p>Set a <strong>key alias</strong>, <strong>key password</strong>, and <strong>keystore password</strong> — and make sure to <strong>remember</strong> them.</p>
</li>
</ol>
<h3 id="heading-step-2-configure-buildgradle">Step 2: Configure <code>build.gradle</code></h3>
<p>In your <strong>module-level</strong> <code>build.gradle</code> file, add the <code>signingConfigs</code> and <code>buildTypes</code> blocks inside the <code>android {}</code> section:</p>
<pre><code class="lang-bash">signingConfigs {
    release {
        def localProps = new Properties()
        def localPropsFile = rootProject.file(<span class="hljs-string">"local.properties"</span>)
        <span class="hljs-keyword">if</span> (localPropsFile.exists()) {
            localProps.load(localPropsFile.newDataInputStream())
        }

        def getEnvOrLocalProp = { key -&gt;
            System.getenv(key) ?: localProps.getProperty(key)
        }

        def keystorePath = getEnvOrLocalProp(<span class="hljs-string">"RELEASE_KEYSTORE_PATH"</span>) ?: file(<span class="hljs-string">"./keystore.jks"</span>)
        <span class="hljs-keyword">if</span> (keystorePath != null) {
            storeFile file(keystorePath)
            storePassword getEnvOrLocalProp(<span class="hljs-string">"RELEASE_STORE_PASSWORD"</span>)
            keyAlias getEnvOrLocalProp(<span class="hljs-string">"RELEASE_KEY_ALIAS"</span>)
            keyPassword getEnvOrLocalProp(<span class="hljs-string">"RELEASE_KEY_PASSWORD"</span>)
        } <span class="hljs-keyword">else</span> {
            println(<span class="hljs-string">"⚠️ RELEASE_KEYSTORE_PATH is missing — skipping release signing"</span>)
        }
    }
}

buildTypes {
    debug {
        versionNameSuffix <span class="hljs-string">"-debug"</span>
        minifyEnabled <span class="hljs-literal">false</span>
        signingConfig signingConfigs.debug
        proguardFiles getDefaultProguardFile(<span class="hljs-string">'proguard-android-optimize.txt'</span>), <span class="hljs-string">'proguard-rules.pro'</span>
    }
    release {
        minifyEnabled <span class="hljs-literal">false</span>
        signingConfig signingConfigs.release
        proguardFiles getDefaultProguardFile(<span class="hljs-string">'proguard-android-optimize.txt'</span>), <span class="hljs-string">'proguard-rules.pro'</span>
    }
}
</code></pre>
<h3 id="heading-securely-handle-credentials">Securely Handle Credentials</h3>
<p>Instead of hardcoding sensitive credentials like keystore passwords in <code>build.gradle</code>, we load them from:</p>
<ul>
<li><p><strong>Environment variables</strong> (CI/CD pipelines)</p>
</li>
<li><p>Or from a <code>local.properties</code> file (for local development)</p>
</li>
</ul>
<blockquote>
<p><strong>Important:</strong> Never commit <code>local.properties</code> to version control. It contains sensitive data.</p>
</blockquote>
<h3 id="heading-step-3-add-keystore-to-github-actions">Step 3: Add Keystore to GitHub Actions</h3>
<p>Since the keystore is a <strong>binary file</strong>, we can't directly store it in GitHub Secrets. Instead, we:</p>
<h4 id="heading-convert-keystore-to-base64">Convert Keystore to Base64</h4>
<pre><code class="lang-bash">base64 -w 0 keystore.jks &gt; keystore.jks.base64
</code></pre>
<blockquote>
<p>On macOS (or if <code>-w</code> flag fails), use:</p>
<pre><code class="lang-bash">base64 keystore.jks &gt; keystore.jks.base64
</code></pre>
</blockquote>
<h4 id="heading-add-to-github-secrets">Add to GitHub Secrets</h4>
<ol>
<li><p>Go to your repository on GitHub.</p>
</li>
<li><p>Navigate to <strong>Settings → Secrets → Actions</strong>.</p>
</li>
<li><p>Create a new secret called <code>RELEASE_KEYSTORE_BASE64</code>.</p>
</li>
<li><p>Paste the contents of <code>keystore.jks.base64</code>.</p>
</li>
</ol>
<h4 id="heading-convert-base64-back-to-keystore-in-ci">Convert Base64 Back to Keystore in CI</h4>
<p>In your GitHub Actions workflow, decode the base64 back into a <code>.jks</code> file:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">echo</span> <span class="hljs-string">"<span class="hljs-variable">${{ secrets.RELEASE_KEYSTORE_BASE64 }</span>}"</span> | base64 -d &gt; app/keystore.jks
</code></pre>
<p>Now, your CI/CD pipeline can generate signed APKs or App Bundles with a consistent fingerprint and secure key management.</p>
<h1 id="heading-setting-up-your-workflow">Setting Up Your Workflow</h1>
<p>Workflows are automation scripts that allow us to define and execute specific tasks automatically. In GitHub, these workflows are stored inside a special folder called <code>.github/workflows</code> at the root level of your repository.</p>
<p>Workflows are written in <strong>YAML</strong>, a widely used language for CI/CD pipelines. In this section, we’ll create a workflow file to automate the APK generation process.</p>
<h2 id="heading-creating-the-workflow-file">Creating the Workflow File</h2>
<p>To get started, create a new file named <code>build.yml</code> inside the <code>.github/workflows</code> folder of your repository.</p>
<h2 id="heading-overview-of-the-workflow-file">Overview of the Workflow File</h2>
<pre><code class="lang-yaml"><span class="hljs-comment"># Name of the workflow</span>
<span class="hljs-attr">name:</span> <span class="hljs-string">Build</span> <span class="hljs-string">APK</span>

<span class="hljs-comment"># Triggers</span>
<span class="hljs-attr">on:</span>
  <span class="hljs-attr">push:</span>
    <span class="hljs-attr">branches:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">main</span>
  <span class="hljs-attr">workflow_dispatch:</span>

<span class="hljs-comment"># Permissions for this workflow</span>
<span class="hljs-attr">permissions:</span>
  <span class="hljs-attr">contents:</span> <span class="hljs-string">write</span>
  <span class="hljs-attr">pull-requests:</span> <span class="hljs-string">write</span>
  <span class="hljs-attr">packages:</span> <span class="hljs-string">write</span>

<span class="hljs-comment"># Task jobs</span>
<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">setup:</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">Setup</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">steps:</span>

  <span class="hljs-attr">build:</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">Build</span> <span class="hljs-string">APK</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">needs:</span> <span class="hljs-string">setup</span>
    <span class="hljs-attr">steps:</span>

  <span class="hljs-attr">create-release:</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">Create</span> <span class="hljs-string">Release</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">needs:</span> [<span class="hljs-string">setup</span>, <span class="hljs-string">build</span>]
    <span class="hljs-attr">steps:</span>
</code></pre>
<p>This workflow is configured to run on two triggers:</p>
<ul>
<li><p><strong>Push to the</strong> <code>main</code> branch</p>
</li>
<li><p><strong>Manual trigger (</strong><code>workflow_dispatch</code>)</p>
</li>
</ul>
<p>If you’re unfamiliar with these configurations, don’t worry! The key part to focus on is the <code>jobs</code> section. These jobs define the different tasks that will be executed on a virtual machine (in this case, <code>ubuntu-latest</code>). We will use artifacts to share the context of one job to the next one</p>
<p>Each job consists of <strong>steps</strong>, where we write Linux terminal commands to perform specific tasks.</p>
<h2 id="heading-defining-task-jobs"><strong>Defining Task Jobs</strong></h2>
<p>To automate the APK generation and release process, we’ll define three jobs:</p>
<h3 id="heading-1-setup"><strong>1. Setup</strong></h3>
<ul>
<li>Generates required secret files inside the workflow (such as <code>google-services.json</code> for Firebase) and (<code>keystore.jks</code> for keystore for signing release version of application).</li>
</ul>
<h3 id="heading-2-build"><strong>2. Build</strong></h3>
<ul>
<li><p>Prepares the release keystore and its secrets in the current session.</p>
</li>
<li><p>Compiles and builds the APK using Gradle.</p>
</li>
</ul>
<h3 id="heading-3-create-release"><strong>3. Create Release</strong></h3>
<ul>
<li><p>Generates release notes.</p>
</li>
<li><p>Uploads the generated APK to <strong>GitHub Releases</strong> automatically.</p>
</li>
</ul>
<p>By structuring our workflow this way, we ensure a smooth and automated process for generating, managing, and publishing APK files—without manual intervention! 🚀</p>
<h1 id="heading-configuring-secrets-for-secure-builds"><strong>Configuring Secrets for Secure Builds</strong></h1>
<p>To keep sensitive information safe, GitHub provides a way to store secrets securely within your repository. These secrets can then be accessed within your workflows without exposing them in your code.</p>
<h3 id="heading-adding-secrets-to-your-repository"><strong>Adding Secrets to Your Repository</strong></h3>
<ol>
<li><p><strong>Navigate to your repository settings:</strong></p>
<ul>
<li><p>Go to <strong>Settings → Secrets and Variables → Actions</strong>.</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742424215837/66a0ac95-030a-4caf-916a-bdc4b6fec75f.png" alt class="image--center mx-auto" /></p>
</li>
</ul>
</li>
<li><p><strong>Create a new secret:</strong></p>
<ul>
<li><p>Under <strong>Repository Secrets</strong>, click <strong>New repository secret</strong>.</p>
</li>
<li><p>Enter a <strong>name</strong> for the secret (e.g., <code>GOOGLE_SERVICES_JSON</code>).</p>
</li>
<li><p>Paste the content of your secret (e.g., the entire <code>google-services.json</code> file) into the <strong>Value</strong> field.</p>
</li>
<li><p>Click <strong>Add secret</strong>.</p>
</li>
</ul>
</li>
<li><p><strong>Repeat for other secrets:</strong></p>
<ul>
<li><p>You can store API keys, authentication tokens, or any other sensitive data using the same process.</p>
</li>
<li><p>Give each secret a meaningful name, as we’ll refer to them later in the workflow.</p>
</li>
</ul>
</li>
</ol>
<h3 id="heading-accessing-secrets-in-the-workflow"><strong>Accessing Secrets in the Workflow</strong></h3>
<p>Once secrets are stored, they can be accessed inside the workflow using the following syntax:</p>
<pre><code class="lang-yaml"><span class="hljs-string">${{</span> <span class="hljs-string">secrets.SECRET_NAME</span> <span class="hljs-string">}}</span>
</code></pre>
<p>For example, to use the <code>GOOGLE_SERVICES_JSON</code> secret inside a GitHub Actions workflow, you can write:</p>
<pre><code class="lang-yaml"><span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Create</span> <span class="hljs-string">google-services.json</span>
  <span class="hljs-attr">run:</span> <span class="hljs-string">|
    mkdir -p app
    echo '${{ secrets.GOOGLE_SERVICES_JSON }}' &gt; app/google-services.json</span>
</code></pre>
<p>This ensures your secrets are securely injected into the build process without being hardcoded in your repository. 🚀</p>
<h1 id="heading-setup-and-build-apk-with-gradle"><strong>Setup and Build APK with Gradle</strong></h1>
<h2 id="heading-setup-job"><strong>Setup Job</strong></h2>
<p>The <code>setup</code> job is responsible for preparing the environment, generating necessary configuration files, and handling secrets securely.</p>
<pre><code class="lang-yaml">  <span class="hljs-attr">setup:</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">Setup</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">outputs:</span>
      <span class="hljs-attr">short_sha:</span> <span class="hljs-string">${{</span> <span class="hljs-string">steps.sha.outputs.sha</span> <span class="hljs-string">}}</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Create</span> <span class="hljs-string">google-services.json</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">|
          mkdir -p app
          echo '${{ secrets.GOOGLE_SERVICES_JSON }}' &gt; app/google-services.json
</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Create</span> <span class="hljs-string">secrets.xml</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">|
          mkdir -p app/src/main/res/values
          cat &lt;&lt; EOF &gt; app/src/main/res/values/secrets.xml
          &lt;?xml version="1.0" encoding="utf-8"?&gt;
          &lt;resources&gt;
              &lt;string name="fcm_server_key"&gt;${{ secrets.FCM_SERVER_KEY }}&lt;/string&gt;
          &lt;/resources&gt;
          EOF
</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Get</span> <span class="hljs-string">short</span> <span class="hljs-string">SHA</span>
        <span class="hljs-attr">id:</span> <span class="hljs-string">sha</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">echo</span> <span class="hljs-string">"sha=${GITHUB_SHA::7}"</span> <span class="hljs-string">&gt;&gt;</span> <span class="hljs-string">$GITHUB_OUTPUT</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Create</span> <span class="hljs-string">keystore</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">|
          echo "${{ secrets.RELEASE_KEYSTORE_BASE64 }}" | base64 -d &gt; app/keystore.jks
</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Upload</span> <span class="hljs-string">config</span> <span class="hljs-string">files</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/upload-artifact@v4</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">name:</span> <span class="hljs-string">config-files</span>
          <span class="hljs-attr">path:</span> <span class="hljs-string">|
            app/google-services.json
            app/src/main/res/values/secrets.xml
            app/keystore.jks
</span>          <span class="hljs-attr">retention-days:</span> <span class="hljs-number">1</span>
</code></pre>
<p>This job ensures that all required secret files and metadata are created within the GitHub worker before the build process begins. You can add more secrets as needed, making this job the central place for managing them dynamically.</p>
<h2 id="heading-setting-up-java-and-gradle-in-github-actions"><strong>Setting Up Java and Gradle in GitHub Actions</strong></h2>
<p>Before building the APK, we need to set up Java and Gradle in the GitHub Actions environment.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">build:</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">Build</span> <span class="hljs-string">APK</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">needs:</span> <span class="hljs-string">setup</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Checkout</span> <span class="hljs-string">code</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v4</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Set</span> <span class="hljs-string">up</span> <span class="hljs-string">JDK</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/setup-java@v4</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">distribution:</span> <span class="hljs-string">'oracle'</span>
          <span class="hljs-attr">java-version:</span> <span class="hljs-string">'17'</span>
          <span class="hljs-attr">cache:</span> <span class="hljs-string">gradle</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Download</span> <span class="hljs-string">config</span> <span class="hljs-string">files</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/download-artifact@v4</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">name:</span> <span class="hljs-string">config-files</span>
          <span class="hljs-attr">path:</span> <span class="hljs-string">./app</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Change</span> <span class="hljs-string">Gradle</span> <span class="hljs-string">Wrapper</span> <span class="hljs-string">Permissions</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">chmod</span> <span class="hljs-string">+x</span> <span class="hljs-string">gradlew</span>
</code></pre>
<p>This step ensures that the environment is ready with the correct Java version and Gradle configuration.</p>
<h3 id="heading-set-keystore-env-variables-in-github-environment">Set Keystore Env Variables in Github Environment</h3>
<pre><code class="lang-yaml">      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Export</span> <span class="hljs-string">signing</span> <span class="hljs-string">env</span> <span class="hljs-string">vars</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">|
          echo "RELEASE_STORE_PASSWORD=${{ secrets.RELEASE_STORE_PASSWORD }}" &gt;&gt; $GITHUB_ENV
          echo "RELEASE_KEY_ALIAS=${{ secrets.RELEASE_KEY_ALIAS }}" &gt;&gt; $GITHUB_ENV
          echo "RELEASE_KEY_PASSWORD=${{ secrets.RELEASE_KEY_PASSWORD }}" &gt;&gt; $GITHUB_ENV</span>
</code></pre>
<h2 id="heading-running-the-gradle-build-process"><strong>Running the Gradle Build Process</strong></h2>
<p>Once the environment is set up, we can proceed with building the APK using Gradle.</p>
<pre><code class="lang-yaml">      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Build</span> <span class="hljs-string">with</span> <span class="hljs-string">Gradle</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">./gradlew</span> <span class="hljs-string">assembleRelease</span>
</code></pre>
<blockquote>
<p>To ensure that the generated APK can be accessed in the next job, we upload it as an artifact.</p>
</blockquote>
<pre><code class="lang-yaml">      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Upload</span> <span class="hljs-string">APK</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/upload-artifact@v4</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">name:</span> <span class="hljs-string">app-debug</span>
          <span class="hljs-attr">path:</span> <span class="hljs-string">app/build/outputs/apk/debug/app-debug.apk</span>
          <span class="hljs-attr">retention-days:</span> <span class="hljs-number">1</span>
</code></pre>
<p>This ensures that the APK is stored temporarily and can be used in subsequent jobs, such as creating a GitHub release.</p>
<h1 id="heading-automating-github-releases"><strong>Automating GitHub Releases</strong></h1>
<p>Once the APK is built, the next step is to automate the release process on GitHub. This includes dynamically generating version tags, uploading the APK as a release asset, and automatically generating release notes.</p>
<h2 id="heading-generating-version-tags-dynamically"><strong>Generating Version Tags Dynamically</strong></h2>
<p>To track versions effectively, we generate a new tag based on the latest commit.</p>
<pre><code class="lang-yaml"><span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Get</span> <span class="hljs-string">Last</span> <span class="hljs-string">Tag</span>
  <span class="hljs-attr">id:</span> <span class="hljs-string">get_last_tag</span>
  <span class="hljs-attr">run:</span> <span class="hljs-string">|
    git fetch --tags
    LAST_TAG=$(git describe --abbrev=0 --tags || echo "none")
    echo "last_tag=${LAST_TAG}" &gt;&gt; $GITHUB_OUTPUT
</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Generate</span> <span class="hljs-string">New</span> <span class="hljs-string">Tag</span>
  <span class="hljs-attr">id:</span> <span class="hljs-string">generate_new_tag</span>
  <span class="hljs-attr">run:</span> <span class="hljs-string">|</span>
    <span class="hljs-string">echo</span> <span class="hljs-string">"new_tag=1.0-${GITHUB_SHA::7}"</span> <span class="hljs-string">&gt;&gt;</span> <span class="hljs-string">$GITHUB_OUTPUT</span>
</code></pre>
<blockquote>
<p>This retrieves the last version tag (if any) and generates a new tag based on the latest commit SHA.</p>
</blockquote>
<h2 id="heading-uploading-the-apk-to-a-github-release-and-generating-release-notes"><strong>Uploading the APK to a GitHub Release and Generating Release Notes</strong></h2>
<p>After generating the APK, we create a GitHub release, attach the APK, and automatically generate release notes.</p>
<pre><code class="lang-yaml"><span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Create</span> <span class="hljs-string">Release</span>
  <span class="hljs-attr">id:</span> <span class="hljs-string">create_release</span>
  <span class="hljs-attr">uses:</span> <span class="hljs-string">softprops/action-gh-release@v2</span>
  <span class="hljs-attr">env:</span>
    <span class="hljs-attr">GITHUB_TOKEN:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.GITHUB_TOKEN</span> <span class="hljs-string">}}</span>
  <span class="hljs-attr">with:</span>
    <span class="hljs-attr">tag_name:</span> <span class="hljs-string">${{</span> <span class="hljs-string">steps.generate_new_tag.outputs.new_tag</span> <span class="hljs-string">}}</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">"Release $<span class="hljs-template-variable">{{ steps.generate_new_tag.outputs.new_tag }}</span>"</span>
    <span class="hljs-attr">body:</span> <span class="hljs-string">|
      ## What's Changed
      **Full Changelog**: https://github.com/${{ github.repository }}/compare/${{ steps.get_last_tag.outputs.last_tag }}...${{ steps.generate_new_tag.outputs.new_tag }}
</span>    <span class="hljs-attr">draft:</span> <span class="hljs-literal">false</span>
    <span class="hljs-attr">prerelease:</span> <span class="hljs-literal">false</span>
    <span class="hljs-attr">files:</span> <span class="hljs-string">|</span>
      <span class="hljs-string">app/build/outputs/apk/debug/app-debug.apk</span>
</code></pre>
<p>Here’s what’s happening:</p>
<ul>
<li><p><strong>Tagging the release</strong> with the newly generated version.</p>
</li>
<li><p><strong>Attaching the APK file</strong> to the release.</p>
</li>
<li><p><strong>Generating a changelog</strong> using GitHub’s comparison link between the last release and the new one.</p>
</li>
</ul>
<blockquote>
<p>If you want more detailed release notes, you can integrate an LLM API to generate a summary of changes dynamically.</p>
</blockquote>
<h1 id="heading-enhancing-the-workflow"><strong>Enhancing the Workflow</strong></h1>
<h2 id="heading-signing-the-apk-for-production"><strong>Signing the APK for Production</strong></h2>
<p>For production builds, the APK must be signed before uploading to Google Play. To do this, store your keystore file as a GitHub secret (Base64 encoded) and use the following steps in your workflow:</p>
<pre><code class="lang-yaml"><span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Decode</span> <span class="hljs-string">Keystore</span>
  <span class="hljs-attr">run:</span> <span class="hljs-string">|
    echo "${{ secrets.RELEASE_KEYSTORE_BASE64 }}" | base64 --decode &gt; app/keystore.jks
</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Sign</span> <span class="hljs-string">APK</span>
  <span class="hljs-attr">run:</span> <span class="hljs-string">|
    jarsigner -verbose -sigalg SHA256withRSA -digestalg SHA-256 \
    -keystore app/keystore.jks -storepass ${{ secrets.KEYSTORE_PASSWORD }} \
    app/build/outputs/apk/release/app-release-unsigned.apk my-key-alias</span>
</code></pre>
<blockquote>
<p>This will sign the APK using the keystore stored in GitHub Secrets.</p>
</blockquote>
<h2 id="heading-uploading-the-apk-to-google-play-optional"><strong>Uploading the APK to Google Play (Optional)</strong></h2>
<p>If you want to automate the deployment to Google Play, you can use <strong>Google Play Developer API</strong> along with the <code>r0adkll/upload-google-play</code> GitHub Action.</p>
<pre><code class="lang-yaml"><span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Upload</span> <span class="hljs-string">to</span> <span class="hljs-string">Google</span> <span class="hljs-string">Play</span>
  <span class="hljs-attr">uses:</span> <span class="hljs-string">r0adkll/upload-google-play@v1</span>
  <span class="hljs-attr">with:</span>
    <span class="hljs-attr">serviceAccountJson:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.PLAY_STORE_SERVICE_ACCOUNT</span> <span class="hljs-string">}}</span>
    <span class="hljs-attr">packageName:</span> <span class="hljs-string">com.example.app</span>
    <span class="hljs-attr">releaseFiles:</span> <span class="hljs-string">app/build/outputs/apk/release/app-release.apk</span>
    <span class="hljs-attr">track:</span> <span class="hljs-string">production</span>
    <span class="hljs-attr">status:</span> <span class="hljs-string">completed</span>
</code></pre>
<blockquote>
<p>This will upload the signed APK directly to Google Play’s internal testing or production track.</p>
</blockquote>
<h1 id="heading-full-workflow-file"><strong>Full Workflow File</strong></h1>
<pre><code class="lang-yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">Build</span> <span class="hljs-string">Apk</span>

<span class="hljs-attr">on:</span>
  <span class="hljs-attr">push:</span>
    <span class="hljs-attr">branches:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">main</span>
  <span class="hljs-attr">workflow_dispatch:</span>

<span class="hljs-attr">concurrency:</span>
  <span class="hljs-attr">group:</span> <span class="hljs-string">${{</span> <span class="hljs-string">github.workflow</span> <span class="hljs-string">}}-${{</span> <span class="hljs-string">github.ref</span> <span class="hljs-string">}}</span>

<span class="hljs-attr">permissions:</span>
  <span class="hljs-attr">contents:</span> <span class="hljs-string">write</span>
  <span class="hljs-attr">pull-requests:</span> <span class="hljs-string">write</span>
  <span class="hljs-attr">packages:</span> <span class="hljs-string">write</span>

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">setup:</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">Setup</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">outputs:</span>
      <span class="hljs-attr">short_sha:</span> <span class="hljs-string">${{</span> <span class="hljs-string">steps.sha.outputs.sha</span> <span class="hljs-string">}}</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Create</span> <span class="hljs-string">google-services.json</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">|
          mkdir -p app
          echo '${{ secrets.GOOGLE_SERVICES_JSON }}' &gt; app/google-services.json
</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Create</span> <span class="hljs-string">secrets.xml</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">|
          mkdir -p app/src/main/res/values
          cat &lt;&lt; EOF &gt; app/src/main/res/values/secrets.xml
          &lt;?xml version="1.0" encoding="utf-8"?&gt;
          &lt;resources&gt;
              &lt;string name="fcm_server_key"&gt;${{ secrets.FCM_SERVER_KEY }}&lt;/string&gt;
          &lt;/resources&gt;
          EOF
</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Get</span> <span class="hljs-string">short</span> <span class="hljs-string">SHA</span>
        <span class="hljs-attr">id:</span> <span class="hljs-string">sha</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">echo</span> <span class="hljs-string">"sha=${GITHUB_SHA::7}"</span> <span class="hljs-string">&gt;&gt;</span> <span class="hljs-string">$GITHUB_OUTPUT</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Create</span> <span class="hljs-string">keystore</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">|
          echo "${{ secrets.RELEASE_KEYSTORE_BASE64 }}" | base64 -d &gt; app/keystore.jks
</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Upload</span> <span class="hljs-string">config</span> <span class="hljs-string">files</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/upload-artifact@v4</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">name:</span> <span class="hljs-string">config-files</span>
          <span class="hljs-attr">path:</span> <span class="hljs-string">|
            app/google-services.json
            app/src/main/res/values/secrets.xml
            app/keystore.jks
</span>          <span class="hljs-attr">retention-days:</span> <span class="hljs-number">1</span>

  <span class="hljs-attr">build:</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">Build</span> <span class="hljs-string">APK</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">needs:</span> <span class="hljs-string">setup</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Checkout</span> <span class="hljs-string">code</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v4</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Set</span> <span class="hljs-string">up</span> <span class="hljs-string">JDK</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/setup-java@v4</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">distribution:</span> <span class="hljs-string">'oracle'</span>
          <span class="hljs-attr">java-version:</span> <span class="hljs-string">'17'</span>
          <span class="hljs-attr">cache:</span> <span class="hljs-string">gradle</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Download</span> <span class="hljs-string">config</span> <span class="hljs-string">files</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/download-artifact@v4</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">name:</span> <span class="hljs-string">config-files</span>
          <span class="hljs-attr">path:</span> <span class="hljs-string">./app</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Change</span> <span class="hljs-string">Gradle</span> <span class="hljs-string">Wrapper</span> <span class="hljs-string">Permissions</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">chmod</span> <span class="hljs-string">+x</span> <span class="hljs-string">gradlew</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Export</span> <span class="hljs-string">signing</span> <span class="hljs-string">env</span> <span class="hljs-string">vars</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">|
          echo "RELEASE_STORE_PASSWORD=${{ secrets.RELEASE_STORE_PASSWORD }}" &gt;&gt; $GITHUB_ENV
          echo "RELEASE_KEY_ALIAS=${{ secrets.RELEASE_KEY_ALIAS }}" &gt;&gt; $GITHUB_ENV
          echo "RELEASE_KEY_PASSWORD=${{ secrets.RELEASE_KEY_PASSWORD }}" &gt;&gt; $GITHUB_ENV
</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Build</span> <span class="hljs-string">with</span> <span class="hljs-string">Gradle</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">./gradlew</span> <span class="hljs-string">assembleRelease</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Upload</span> <span class="hljs-string">APK</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/upload-artifact@v4</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">name:</span> <span class="hljs-string">app-release</span>
          <span class="hljs-attr">path:</span> <span class="hljs-string">app/build/outputs/apk/release/app-release.apk</span>
          <span class="hljs-attr">retention-days:</span> <span class="hljs-number">1</span>

  <span class="hljs-attr">create-release:</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">Create</span> <span class="hljs-string">Release</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">needs:</span> [ <span class="hljs-string">setup</span>,<span class="hljs-string">build</span> ]
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Checkout</span> <span class="hljs-string">code</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v4</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">fetch-depth:</span> <span class="hljs-number">0</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Download</span> <span class="hljs-string">APK</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/download-artifact@v4</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">name:</span> <span class="hljs-string">app-release</span>
          <span class="hljs-attr">path:</span> <span class="hljs-string">app/build/outputs/apk/release</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Get</span> <span class="hljs-string">Last</span> <span class="hljs-string">Tag</span>
        <span class="hljs-attr">id:</span> <span class="hljs-string">get_last_tag</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">|
          git fetch --tags
          LAST_TAG=$(git describe --abbrev=0 --tags || echo "none")
          echo "last_tag=${LAST_TAG}" &gt;&gt; $GITHUB_OUTPUT
</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Generate</span> <span class="hljs-string">New</span> <span class="hljs-string">Tag</span>
        <span class="hljs-attr">id:</span> <span class="hljs-string">generate_new_tag</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">|
          echo "new_tag=1.0-${GITHUB_SHA::7}" &gt;&gt; $GITHUB_OUTPUT
</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Create</span> <span class="hljs-string">Release</span>
        <span class="hljs-attr">id:</span> <span class="hljs-string">create_release</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">softprops/action-gh-release@v2</span>
        <span class="hljs-attr">env:</span>
          <span class="hljs-attr">GITHUB_TOKEN:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.GITHUB_TOKEN</span> <span class="hljs-string">}}</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">tag_name:</span> <span class="hljs-string">${{</span> <span class="hljs-string">steps.generate_new_tag.outputs.new_tag</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">name:</span> <span class="hljs-string">"Release $<span class="hljs-template-variable">{{ steps.generate_new_tag.outputs.new_tag }}</span>"</span>
          <span class="hljs-attr">body:</span> <span class="hljs-string">|
            ## What's Changed
            **Full Changelog**: https://github.com/${{ github.repository }}/compare/${{ steps.get_last_tag.outputs.last_tag }}...${{ steps.generate_new_tag.outputs.new_tag }}
</span>          <span class="hljs-attr">draft:</span> <span class="hljs-literal">false</span>
          <span class="hljs-attr">prerelease:</span> <span class="hljs-literal">false</span>
          <span class="hljs-attr">files:</span> <span class="hljs-string">|</span>
            <span class="hljs-string">app/build/outputs/apk/release/app-release.apk</span>
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742508753378/3494064e-08c8-4b74-b9b0-e59755fbc95d.png" alt class="image--center mx-auto" /></p>
<blockquote>
<p>I have omitted the signing and deployment part from this since they are use-case specific and do not apply to every hobbyist Android developer.</p>
</blockquote>
<h1 id="heading-conclusion"><strong>Conclusion</strong></h1>
<p>With this fully automated workflow, building, releasing, and publishing your APKs becomes effortless using GitHub Actions. This setup is perfect for hobby developers and students who want to showcase their projects without the hassle of manually generating and sharing APKs. Now, instead of scrambling to find the latest build on your PC when someone asks for your app, you can simply share a GitHub release link—just like web developers share their hosted sites.</p>
<p>Embrace automation and let GitHub Actions handle the heavy lifting, so you can focus on what truly matters—building great Android apps! 🚀</p>
]]></content:encoded></item><item><title><![CDATA[Notification Microservice using Kafka and FCM]]></title><description><![CDATA[Introduction
Notifications are a crucial part of any day-to-day application, including social media, finance, e-commerce, and many more. While they may seem simple, their underlying architecture is what makes them highly efficient. From chat applicat...]]></description><link>https://blogs.ijlalahmad.dev/notification-microservice-using-kafka-and-fcm</link><guid isPermaLink="true">https://blogs.ijlalahmad.dev/notification-microservice-using-kafka-and-fcm</guid><category><![CDATA[kafka]]></category><category><![CDATA[FCM]]></category><category><![CDATA[notifications]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[Queues]]></category><category><![CDATA[push notifications]]></category><category><![CDATA[emailnotification]]></category><category><![CDATA[kafka topic]]></category><category><![CDATA[zookeeper]]></category><dc:creator><![CDATA[Ijlal Ahmad]]></dc:creator><pubDate>Sat, 30 Nov 2024 17:20:24 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1717839880625/b4789677-89b7-4def-a4ac-411de36636ac.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-introduction">Introduction</h1>
<p>Notifications are a crucial part of any day-to-day application, including social media, finance, e-commerce, and many more. While they may seem simple, their underlying architecture is what makes them highly efficient. From chat applications and payment notifications to marketing alerts, notifications play a vital role in enhancing user experience and engagement.</p>
<p>One might think that receiving notifications is straightforward—just constantly ping the server and fetch any new notifications. However, this approach is highly inefficient, wasting resources and bandwidth. Such a method is impractical for large-scale applications. Instead, notifications require a constant socket connection as a background service. This service remains connected to the server, allowing the server to push notifications to the client as soon as they are generated.</p>
<p>While some providers like OneSignal and Firebase offer easy integration of notification services into applications, they come with hard limits. Although these limits are generous and can support a large number of users, they still exist. Imagine making a payment and not receiving a notification—that would be disastrous. This scenario can occur when the server is overwhelmed with notifications and cannot handle the load at a given time. Every server has resource constraints, so we need a solution that can manage high throughput and hold a large amount of data. This is where messaging queues, such as Apache Kafka, come into play.</p>
<p>In this blog, we will explore how to design and implement a notification microservice using Kafka and FCM in Node.js. We'll cover everything from setting up the development environment and understanding the key components to deploying and scaling the microservice. Let's dive in!</p>
<h1 id="heading-what-is-kafka-and-why-should-we-use-it">What is Kafka and Why Should We Use It?</h1>
<p>Before diving into Kafka, let’s first understand <strong>messaging queues</strong>. Messaging queues are like the classic FIFO (First In, First Out) queues we encounter in traditional programming. They are a crucial tool for <strong>decoupling services</strong>, especially when the sender (producer) and receiver (consumer) have varying speeds, efficiency, or throughput. In such scenarios, messaging queues act as an asynchronous bridge. Producers can push messages into the queue at their pace, while consumers can process them independently, at their own speed.</p>
<p><em>~ Back to Kafka</em></p>
<p>Kafka is a <strong>distributed messaging system</strong> designed for <strong>high throughput and scalability</strong>, capable of handling massive volumes of data effortlessly. Unlike traditional systems, Kafka is built for modern, large-scale applications. It ensures that critical messages are delivered promptly, even during peak loads, making it an ideal choice for notification systems and beyond.</p>
<p>But Kafka is not just limited to notifications. Its true strength lies in its ability to handle enormous throughput—processing trillions of messages with ease. Kafka serves as a buffer between producers and consumers, <strong>holding messages temporarily</strong> when downstream systems are overloaded. This allows servers to process data at their capacity while maintaining system efficiency and reliability.</p>
<h3 id="heading-why-kafka-over-other-messaging-queues">Why Kafka Over Other Messaging Queues?</h3>
<p>There are several messaging queue options available, such as RabbitMQ, BullMQ, and AWS SQS. So why choose Kafka? Let’s break it down:</p>
<h4 id="heading-1-rabbitmq"><strong>1. RabbitMQ</strong></h4>
<ul>
<li><p>RabbitMQ is a good alternative to Kafka for specific use cases.</p>
</li>
<li><p><strong>Limitations:</strong></p>
<ul>
<li><p>Consumers in RabbitMQ are socket-based, meaning <strong>multiple consumers cannot consume the same message</strong> simultaneously.</p>
</li>
<li><p>It’s better suited for tasks where immediate delivery and processing are required, but not for high-throughput, multi-consumer scenarios like Kafka excels in.</p>
</li>
</ul>
</li>
</ul>
<h4 id="heading-2-bullmq"><strong>2. BullMQ</strong></h4>
<ul>
<li><p>BullMQ is not a standalone queue but a <strong>library built on top of Redis</strong> to simulate queue-like behavior.</p>
</li>
<li><p><strong>Limitations:</strong></p>
<ul>
<li><p>Its performance is tied to Redis, which may not scale as well as Kafka for massive workloads.</p>
</li>
<li><p>Redis-based solutions work well for specific cases but lack Kafka's robust distributed nature.</p>
</li>
</ul>
</li>
</ul>
<h4 id="heading-3-aws-sqs"><strong>3. AWS SQS</strong></h4>
<ul>
<li><p>Amazon Simple Queue Service (SQS) is highly scalable and a solid contender for cloud-native applications.</p>
</li>
<li><p><strong>Limitations:</strong></p>
<ul>
<li><p>It’s a <strong>vendor-dependent service</strong>, locking you into AWS’s ecosystem.</p>
</li>
<li><p>SQS does not support Kafka’s <strong>consumer group model</strong>—a powerful feature for distributing messages among different types of consumers (e.g., notifications for emails, SMS, and push notifications). Instead, achieving similar functionality often requires combining multiple AWS services.</p>
</li>
</ul>
</li>
</ul>
<h3 id="heading-the-kafka-advantage">The Kafka Advantage</h3>
<p>Kafka’s <strong>consumer group model</strong> sets it apart. Here’s how it works:</p>
<ul>
<li><p>A <strong>consumer group</strong> is a logical grouping of consumers. Each consumer group can receive a copy of the same message, enabling flexible processing for different use cases.</p>
<ul>
<li>For instance, a Kafka topic for notifications could have separate consumer groups for <strong>push notifications</strong>, <strong>emails</strong>, and <strong>SMS</strong>.</li>
</ul>
</li>
<li><p>Within a consumer group, only <strong>one consumer processes a given message</strong>, ensuring no duplication of effort.</p>
</li>
<li><p>Kafka’s distributed nature allows you to <strong>scale consumer groups independently</strong>, making it a highly scalable and efficient messaging system.</p>
</li>
</ul>
<p>By leveraging these features, Kafka can seamlessly handle diverse, large-scale workloads while maintaining reliability, speed, and scalability.</p>
<h1 id="heading-fcm-overview">FCM Overview</h1>
<p>Firebase Cloud Messaging (FCM) is a powerful service provided by Firebase for delivering messages to client devices. It acts as the <strong>actual server</strong> that pushes notifications to devices, whether they’re Android, iOS, or web-based. While there are alternatives like <strong>OneSignal</strong> and <strong>Twilio</strong>, FCM stands out for its <strong>ease of integration</strong>, <strong>robust features</strong>, and the added bonus of being <strong>free to use</strong> (within generous limits).</p>
<h3 id="heading-how-fcm-works">How FCM Works</h3>
<p>FCM establishes a <strong>persistent connection</strong> between the server and client-side applications. This connection operates in the background, ensuring seamless delivery of notifications without constant polling or additional overhead. When a message is ready, FCM pushes it directly to the client device, making it incredibly efficient for real-time communication.</p>
<p>While FCM is highly efficient and supports high message rates, it has <strong>limits</strong> in terms of throughput. For small-scale applications, these limits are rarely a concern. However, in large-scale systems where notifications spike (e.g., flash sales, payment alerts), FCM alone may not suffice.</p>
<p>This is where <strong>messaging queues</strong> like Kafka come in:</p>
<ul>
<li><p>They act as a buffer between your application and FCM.</p>
</li>
<li><p>Kafka handles a <strong>high volume of messages</strong> from your application and feeds them to FCM at a manageable rate.</p>
</li>
<li><p>This integration ensures <strong>scalability</strong>, allowing you to handle spikes without dropping notifications or overwhelming the FCM servers.</p>
</li>
</ul>
<h1 id="heading-hands-on">Hands-On</h1>
<p>Enough theory—let’s get our hands dirty with code! We’ll build a <strong>notification microservice</strong> using <strong>Kafka</strong> as a message broker and <strong>Firebase Cloud Messaging (FCM)</strong> for sending push notifications. Additionally, we’ll use <strong>Nodemailer</strong> to send emails, demonstrating how multiple consumers can handle different tasks efficiently.</p>
<h3 id="heading-architecture-overview">Architecture Overview</h3>
<p>We’ll create two separate <strong>Node.js servers</strong>:</p>
<ol>
<li><p><strong>Producer</strong>: The backend application that generates messages (e.g., API requests, user actions) and pushes them to Kafka.</p>
</li>
<li><p><strong>Consumer</strong>: A worker application that listens to Kafka messages, processes them, and sends notifications (push notifications, emails, SMS, etc.).</p>
</li>
</ol>
<p>This design ensures the producer focuses on generating messages, while the consumer handles delivery tasks, making the system scalable and decoupled.</p>
<h3 id="heading-step-1-setting-up-the-producer">Step 1: Setting Up the Producer</h3>
<h4 id="heading-1-initialize-the-project">1. Initialize the Project</h4>
<pre><code class="lang-bash">mkdir kafka-producer &amp;&amp; <span class="hljs-built_in">cd</span> kafka-producer
npm init -y
npm i express kafkajs
</code></pre>
<h4 id="heading-2-create-the-producer-logic-producerjs">2. Create the Producer Logic (<code>producer.js</code>)</h4>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> { Kafka } = <span class="hljs-built_in">require</span>(<span class="hljs-string">"kafkajs"</span>);

<span class="hljs-keyword">const</span> kafka = <span class="hljs-keyword">new</span> Kafka({
  <span class="hljs-attr">clientId</span>: <span class="hljs-string">"kafka-producer"</span>,
  <span class="hljs-attr">brokers</span>: [<span class="hljs-string">"localhost:9092"</span>], <span class="hljs-comment">// Replace with your Kafka broker addresses</span>
});

<span class="hljs-keyword">const</span> producer = kafka.producer();

<span class="hljs-keyword">const</span> produceMessage = <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">await</span> producer.connect();

  <span class="hljs-keyword">const</span> message = {
    <span class="hljs-attr">key</span>: <span class="hljs-string">"key1"</span>,
    <span class="hljs-attr">value</span>: <span class="hljs-string">"Message triggered by API call"</span>,
  };

  <span class="hljs-keyword">await</span> producer.send({
    <span class="hljs-attr">topic</span>: <span class="hljs-string">"test-topic"</span>,
    <span class="hljs-attr">messages</span>: [message],
  });

  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Produced message:"</span>, message);

  <span class="hljs-keyword">await</span> producer.disconnect();
};

<span class="hljs-built_in">module</span>.exports = { produceMessage };
</code></pre>
<blockquote>
<p><strong>Note</strong>: Kafka topics act like message channels. A single Kafka instance can manage multiple topics, enabling the system to route messages based on their type.</p>
</blockquote>
<h4 id="heading-3-create-the-producer-server-appjs">3. Create the Producer Server (<code>app.js</code>)</h4>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">"express"</span>);
<span class="hljs-keyword">const</span> { produceMessage } = <span class="hljs-built_in">require</span>(<span class="hljs-string">"./producer"</span>);

<span class="hljs-keyword">const</span> app = express();

app.get(<span class="hljs-string">"/"</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  res.send(<span class="hljs-string">"Hello from Kafka Producer!"</span>);
});

app.get(<span class="hljs-string">"/produce"</span>, <span class="hljs-keyword">async</span> (req, res) =&gt; {
  <span class="hljs-keyword">await</span> produceMessage();
  res.send(<span class="hljs-string">"Message produced successfully."</span>);
});

app.listen(<span class="hljs-number">3000</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Producer server is running on port 3000"</span>);
});
</code></pre>
<p>Run the producer server:</p>
<pre><code class="lang-bash">node app.js
</code></pre>
<h3 id="heading-step-2-setting-up-the-consumer">Step 2: Setting Up the Consumer</h3>
<h4 id="heading-1-initialize-the-project-1">1. Initialize the Project</h4>
<pre><code class="lang-bash">mkdir kafka-consumer &amp;&amp; <span class="hljs-built_in">cd</span> kafka-consumer
npm init -y
npm i kafkajs firebase-admin nodemailer
</code></pre>
<h4 id="heading-2-configure-firebase-and-email-consumerjs">2. Configure Firebase and Email (<code>consumer.js</code>)</h4>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> { Kafka } = <span class="hljs-built_in">require</span>(<span class="hljs-string">"kafkajs"</span>);
<span class="hljs-keyword">const</span> admin = <span class="hljs-built_in">require</span>(<span class="hljs-string">"firebase-admin"</span>);
<span class="hljs-keyword">const</span> nodemailer = <span class="hljs-built_in">require</span>(<span class="hljs-string">"nodemailer"</span>);

<span class="hljs-comment">// Initialize Firebase Admin SDK</span>
admin.initializeApp({
  <span class="hljs-attr">credential</span>: admin.credential.applicationDefault(), <span class="hljs-comment">// Replace with your Firebase credentials</span>
});

<span class="hljs-comment">// Nodemailer transporter</span>
<span class="hljs-keyword">const</span> transporter = nodemailer.createTransport({
  <span class="hljs-attr">service</span>: <span class="hljs-string">"gmail"</span>,
  <span class="hljs-attr">auth</span>: {
    <span class="hljs-attr">user</span>: <span class="hljs-string">"your-email@gmail.com"</span>, <span class="hljs-comment">// Replace with your email</span>
    <span class="hljs-attr">pass</span>: <span class="hljs-string">"your-email-password"</span>, <span class="hljs-comment">// Replace with your email password</span>
  },
});

<span class="hljs-keyword">const</span> kafka = <span class="hljs-keyword">new</span> Kafka({
  <span class="hljs-attr">clientId</span>: <span class="hljs-string">"kafka-consumer"</span>,
  <span class="hljs-attr">brokers</span>: [<span class="hljs-string">"localhost:9092"</span>], <span class="hljs-comment">// Replace with your Kafka broker addresses</span>
});

<span class="hljs-keyword">const</span> consumer = kafka.consumer({ <span class="hljs-attr">groupId</span>: <span class="hljs-string">"test-group"</span> });

<span class="hljs-keyword">const</span> consumeMessages = <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">await</span> consumer.connect();
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Consumer connected to Kafka"</span>);

  <span class="hljs-keyword">await</span> consumer.subscribe({ <span class="hljs-attr">topic</span>: <span class="hljs-string">"test-topic"</span>, <span class="hljs-attr">fromBeginning</span>: <span class="hljs-literal">false</span> });

  <span class="hljs-keyword">await</span> consumer.run({
    <span class="hljs-attr">eachMessage</span>: <span class="hljs-keyword">async</span> ({ message }) =&gt; {
      <span class="hljs-keyword">const</span> value = message.value.toString();
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Consumed message:"</span>, value);

      <span class="hljs-comment">// Push notification via FCM</span>
      <span class="hljs-keyword">const</span> fcmMessage = {
        <span class="hljs-attr">notification</span>: {
          <span class="hljs-attr">title</span>: <span class="hljs-string">"New Message"</span>,
          <span class="hljs-attr">body</span>: value,
        },
        <span class="hljs-attr">token</span>: <span class="hljs-string">"recipient-device-token"</span>, <span class="hljs-comment">// Replace with an actual device token</span>
      };

      <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> admin.messaging().send(fcmMessage);
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Successfully sent FCM notification:"</span>, response);
      } <span class="hljs-keyword">catch</span> (error) {
        <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Error sending FCM notification:"</span>, error);
      }

      <span class="hljs-comment">// Email notification via Nodemailer</span>
      <span class="hljs-keyword">const</span> mailOptions = {
        <span class="hljs-attr">from</span>: <span class="hljs-string">"your-email@gmail.com"</span>,
        <span class="hljs-attr">to</span>: <span class="hljs-string">"recipient-email@gmail.com"</span>, <span class="hljs-comment">// Replace with the recipient's email</span>
        <span class="hljs-attr">subject</span>: <span class="hljs-string">"Kafka Notification"</span>,
        <span class="hljs-attr">text</span>: value,
      };

      transporter.sendMail(mailOptions, <span class="hljs-function">(<span class="hljs-params">err, info</span>) =&gt;</span> {
        <span class="hljs-keyword">if</span> (err) {
          <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Error sending email:"</span>, err);
        } <span class="hljs-keyword">else</span> {
          <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Email sent successfully:"</span>, info.response);
        }
      });
    },
  });
};

<span class="hljs-built_in">module</span>.exports = { consumeMessages };
</code></pre>
<h4 id="heading-3-run-the-consumer">3. Run the Consumer</h4>
<p>Create a script (<code>index.js</code>) to start the consumer:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> { consumeMessages } = <span class="hljs-built_in">require</span>(<span class="hljs-string">"./consumer"</span>);

consumeMessages().catch(<span class="hljs-built_in">console</span>.error);
</code></pre>
<p>Run the consumer:</p>
<pre><code class="lang-bash">node index.js
</code></pre>
<h3 id="heading-key-features-of-the-architecture">Key Features of the Architecture</h3>
<ol>
<li><p><strong>Multiple Consumer Groups</strong><br /> Kafka allows different consumer groups to read the same topic independently. For example:</p>
<ul>
<li><p>One group sends push notifications.</p>
</li>
<li><p>Another group sends emails.</p>
</li>
<li><p>Yet another handles SMS notifications.</p>
</li>
</ul>
</li>
<li><p><strong>Scalability</strong><br /> Kafka’s partitioning mechanism ensures that each consumer in a group processes only a subset of messages, allowing you to scale horizontally.</p>
</li>
<li><p><strong>Decoupling</strong><br /> Producers and consumers operate independently. Producers are unaware of consumer logic, enabling flexibility and modular design.</p>
</li>
</ol>
<h3 id="heading-testing-the-microservice">Testing the Microservice</h3>
<p>Running Kafka directly on your machine can be a bit tricky due to its dependencies, such as Zookeeper. To simplify the setup, we can use Docker Compose. Below is the Docker Compose configuration for setting up Kafka and Zookeeper.</p>
<h4 id="heading-docker-compose-file-docker-composeyml">Docker Compose File (<code>docker-compose.yml</code>)</h4>
<pre><code class="lang-yaml"><span class="hljs-attr">version:</span> <span class="hljs-string">"2"</span>

<span class="hljs-attr">services:</span>
  <span class="hljs-attr">zookeeper:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">wurstmeister/zookeeper:latest</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"2181:2181"</span> <span class="hljs-comment"># Zookeeper's default port</span>

  <span class="hljs-attr">kafka:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">wurstmeister/kafka:latest</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"9092:9092"</span> <span class="hljs-comment"># Kafka's external listener port</span>
    <span class="hljs-attr">expose:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"9093"</span> <span class="hljs-comment"># Kafka's internal listener port</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-attr">KAFKA_ADVERTISED_LISTENERS:</span> <span class="hljs-string">INSIDE://kafka:9093,OUTSIDE://localhost:9092</span>
      <span class="hljs-attr">KAFKA_LISTENER_SECURITY_PROTOCOL_MAP:</span> <span class="hljs-string">INSIDE:PLAINTEXT,OUTSIDE:PLAINTEXT</span>
      <span class="hljs-attr">KAFKA_LISTENERS:</span> <span class="hljs-string">INSIDE://0.0.0.0:9093,OUTSIDE://0.0.0.0:9092</span>
      <span class="hljs-attr">KAFKA_INTER_BROKER_LISTENER_NAME:</span> <span class="hljs-string">INSIDE</span>
      <span class="hljs-attr">KAFKA_ZOOKEEPER_CONNECT:</span> <span class="hljs-string">zookeeper:2181</span>
      <span class="hljs-attr">KAFKA_CREATE_TOPICS:</span> <span class="hljs-string">"test-topic:1:1"</span> <span class="hljs-comment"># Pre-create the topic 'test-topic' with 1 partition and replication factor 1</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">/var/run/docker.sock:/var/run/docker.sock</span> <span class="hljs-comment"># Required for Kafka to communicate with the host network</span>
</code></pre>
<h3 id="heading-how-to-use-the-compose-file">How to Use the Compose File</h3>
<ol>
<li><p><strong>Save the file</strong><br /> Save the above configuration as <code>docker-compose.yml</code> in your project directory.</p>
</li>
<li><p><strong>Start the services</strong><br /> Run the following command to start Kafka and Zookeeper:</p>
<pre><code class="lang-bash"> docker-compose up -d
</code></pre>
</li>
<li><p>Start the producer server:</p>
<pre><code class="lang-bash"> node app.js
</code></pre>
</li>
<li><p>Start the consumer server:</p>
<pre><code class="lang-bash"> node index.js
</code></pre>
</li>
<li><p>Trigger a message by visiting the producer endpoint:</p>
<pre><code class="lang-javascript"> http:<span class="hljs-comment">//localhost:3000/produce</span>
</code></pre>
</li>
<li><p>Observe the consumer processing the message and sending notifications.</p>
</li>
</ol>
<h1 id="heading-deployment">Deployment</h1>
<p>Kafka operates as a separate service from your producer and consumer servers and must be deployed independently. While Kafka can be installed on virtual machines, it's highly recommended to use <strong>managed Kafka services</strong> (e.g., AWS MSK, Confluent Cloud, or Azure Event Hubs). Managing a Kafka cluster yourself can be challenging due to the complexity of scaling, fault tolerance, and maintaining high availability.</p>
<h4 id="heading-deploying-kafka">Deploying Kafka</h4>
<ul>
<li><p><strong>Managed Kafka Services</strong><br />  Opting for managed services offloads the operational burden of maintaining Kafka clusters, including monitoring, scaling, and upgrades. These services ensure reliability and save time, allowing you to focus on application logic.</p>
</li>
<li><p><strong>Self-Managed Kafka</strong><br />  If you choose to deploy Kafka yourself, it can be installed on virtual machines or containerized using <strong>Docker Compose</strong> or <strong>Kubernetes</strong>. However, be prepared for added responsibilities like monitoring brokers, managing Zookeeper (if applicable), and handling failover scenarios.</p>
</li>
</ul>
<h4 id="heading-deploying-producers-and-consumers">Deploying Producers and Consumers</h4>
<ul>
<li><p><strong>Flexibility in Deployment</strong><br />  Producers and consumers can be deployed on:</p>
<ul>
<li><p>The same virtual machine.</p>
</li>
<li><p>Separate virtual machines.</p>
</li>
<li><p>Docker containers or Kubernetes pods.</p>
</li>
</ul>
</li>
<li><p><strong>Load Balancing and Scaling</strong><br />  Use <strong>load balancers</strong> to distribute traffic across multiple producer and consumer instances. Kafka's <strong>consumer group</strong> mechanism ensures that messages are evenly processed by available consumers, enabling horizontal scalability.</p>
</li>
<li><p><strong>Environment Considerations</strong></p>
<ul>
<li><p>Producers typically handle API logic and can scale to accommodate increased message traffic.</p>
</li>
<li><p>Consumers process messages and can scale based on message load and throughput requirements.</p>
</li>
</ul>
</li>
</ul>
<h3 id="heading-key-recommendations">Key Recommendations</h3>
<ul>
<li><p>Use managed Kafka services to simplify operations and ensure high availability.</p>
</li>
<li><p>Deploy producers and consumers as separate, scalable services, utilizing load balancers to handle traffic spikes.</p>
</li>
<li><p>Ensure proper resource allocation for your Kafka service and consumers to maintain consistent performance under load.</p>
</li>
</ul>
<h1 id="heading-conclusion">Conclusion</h1>
<p>Notifications are an integral feature for modern applications, providing real-time updates and enhancing user engagement. While seemingly simple, their underlying architecture requires careful design to handle scale, reliability, and performance. Traditional approaches like polling are inefficient and unsustainable for large-scale systems. Instead, solutions like Apache Kafka, paired with Firebase Cloud Messaging (FCM), offer robust architectures that support high-throughput, real-time notification delivery.</p>
<p>Kafka's distributed nature, scalability, and consumer group model make it a standout choice for decoupling producer and consumer responsibilities. This architecture ensures that notifications are delivered efficiently, even during peak loads. FCM complements Kafka by seamlessly delivering push notifications to client devices.</p>
<p>The hands-on implementation of a notification microservice demonstrates how Kafka and FCM can work together to process and deliver notifications across multiple channels, such as push, email, and SMS. By leveraging Docker Compose, the setup becomes accessible for development and testing, while deployment considerations highlight the importance of managed Kafka services for reliability and ease of scaling.</p>
<p>In conclusion, building a notification microservice with Kafka and FCM not only meets the demands of high-scale systems but also ensures reliability, flexibility, and efficiency. By adopting this approach, developers can focus on creating user-centric experiences without being limited by infrastructure constraints. Whether for social media, e-commerce, or finance, this architecture provides a solid foundation for handling notifications in any large-scale application.</p>
]]></content:encoded></item><item><title><![CDATA[Deeply Nested Comments Architecture]]></title><description><![CDATA[Overview
Ever wondered how nested comments are implemented? Some sites like YouTube or Instagram limit the level of nested comments to only one level. However, platforms like Reddit or Facebook took nested comments to a whole new level, allowing user...]]></description><link>https://blogs.ijlalahmad.dev/deeply-nested-comments-architecture</link><guid isPermaLink="true">https://blogs.ijlalahmad.dev/deeply-nested-comments-architecture</guid><category><![CDATA[comments]]></category><category><![CDATA[architecture]]></category><category><![CDATA[System Design]]></category><category><![CDATA[Databases]]></category><category><![CDATA[Relational Database]]></category><category><![CDATA[backend]]></category><category><![CDATA[SQL]]></category><category><![CDATA[nested]]></category><category><![CDATA[Complexity]]></category><dc:creator><![CDATA[Ijlal Ahmad]]></dc:creator><pubDate>Wed, 21 Feb 2024 17:53:57 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1708167583248/9f093cc2-d7e3-487a-b03b-4953e7746da1.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-overview">Overview</h2>
<p>Ever wondered how nested comments are implemented? Some sites like YouTube or Instagram limit the level of nested comments to only one level. However, platforms like Reddit or Facebook took nested comments to a whole new level, allowing users to reply deeply nested comments, providing a clear and precise context.</p>
<p>While platforms like YouTube or Instagram limit nested behavior to only one level, where replies to nested comments are simply appended by the username of the parent comment, deeply nested comments offer a more satisfying experience. Deeply nested comments ensure absolute context, allowing users to navigate through discussions with precision.</p>
<p>Despite the widespread interest in deeply nested comments, comprehensive tutorials covering frontend, backend, and database models are scarce. In this article, I aim to provide an overview of deeply nested comments architecture, covering the frontend, backend, and database implementation practices involved in this module.</p>
<blockquote>
<p>In this article, we'll focus solely on the theoretical aspects, omitting complex system design patterns like lazy loading, caching, and pagination for large comment threads. While these advanced concepts significantly enhance efficiency and performance, they require a solid understanding of backend foundations. Therefore, we'll reserve them for future articles. For now, let's delve into the fundamentals of implementing nested comments.</p>
</blockquote>
<h2 id="heading-database-design">Database Design</h2>
<p>In system design, the database design is the crucial starting point. When considering the implementation of nested behavior, the choice of database type becomes pivotal. While non-relational databases offer flexibility in schema and storage, relational databases outshine in both schema management and performance.</p>
<h3 id="heading-relational-vs-non-relational-databases">Relational vs. Non-Relational Databases</h3>
<p><img src="https://www.lytics.com/wp-content/uploads/2022/03/article-Relational-vs.-non-relational-databases_-Understanding-the-difference.jpg" alt="Relational vs. non-relational databases: Understanding the difference" /></p>
<h4 id="heading-non-relational-databases">Non-Relational Databases</h4>
<p>Non-relational databases, with their document and collection structure, seem like a natural fit for nested comments. However, limitations arise due to document size restrictions. Even though comments can be nested within a single document, scalability becomes an issue. Dividing comments across multiple documents or collections leads to performance degradation, especially when querying nested data.</p>
<h4 id="heading-relational-databases">Relational Databases</h4>
<p>Contrary to initial assumptions, relational databases excel in handling nested comments. Leveraging the concept of circular or self-referential models, a single model can accommodate both base and nested comments. This approach simplifies schema design and ensures optimal performance.</p>
<blockquote>
<p>Ok, Enough talk show me the Approaches</p>
</blockquote>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708535982651/eb7a3452-61a6-4d34-b7ca-803742d383b5.png" alt="Top Down Vs Bottom Up Approaches" /></p>
<h3 id="heading-approach-1-top-down-theoretical">Approach 1 (Top Down, Theoretical)</h3>
<pre><code class="lang-typescript">CommentModel {
    id: <span class="hljs-built_in">number</span>,
    postId: <span class="hljs-built_in">number</span>    <span class="hljs-comment">// foreign key to posts table</span>
    comment: <span class="hljs-built_in">string</span>,
    commentorId: <span class="hljs-built_in">number</span>,
    createdAt: <span class="hljs-built_in">Date</span>,
    replies: CommentModel[]
}
</code></pre>
<p>In this approach, each comment contains an array (<code>replies</code>) of nested comments of the same type. This design theoretically allows for infinite nesting, providing a clear hierarchy of comments within comments. However, while conceptually elegant, this approach faces practical limitations, especially when implemented in relational databases.</p>
<p>The primary drawback is that relational databases adhere to a flat schema structure, where each row represents a single entity or record. Storing arrays violates this principle and can lead to inefficiencies in querying and data retrieval. Additionally, relational databases are optimized for efficient JOIN operations, which are essential for querying nested data structures. Storing nested comments as arrays complicates JOIN operations and can significantly impact performance.</p>
<h3 id="heading-approach-2-bottom-up-practical">Approach 2 (Bottom Up, Practical)</h3>
<pre><code class="lang-typescript">CommentModel {
    id: <span class="hljs-built_in">number</span>,
    postId: <span class="hljs-built_in">number</span>    <span class="hljs-comment">// foreign key to posts table</span>
    comment: <span class="hljs-built_in">string</span>,
    commentorId: <span class="hljs-built_in">number</span>,
    createdAt: <span class="hljs-built_in">Date</span>,
    parentId: <span class="hljs-built_in">number</span> | <span class="hljs-literal">null</span> <span class="hljs-comment">// null for top level comments</span>
}
</code></pre>
<p>In this approach, each comment contains a reference (<code>parentId</code>) to its parent comment. The <code>parentId</code> establishes a hierarchical relationship between comments, enabling straightforward querying and navigation through nested comment threads. This approach aligns well with relational database principles and facilitates efficient data retrieval and storage.</p>
<p>By utilizing a self-referential model, the relational database can easily represent nested comments without violating the flat schema structure. Each comment exists as an independent entity, with its <code>parentId</code> linking it to its parent comment. This design simplifies querying, as we can fetch all the comments of a single level if we know the <code>parentId</code> without touching other comments. This facilitates batch processing on the network side allowing us to integrate pagination on the go.</p>
<h3 id="heading-implementation-benefits">Implementation Benefits</h3>
<ul>
<li><p><strong>Optimized Querying:</strong> Retrieving top-level comments and their replies is streamlined, enhancing database query efficiency.</p>
</li>
<li><p><strong>Scalability:</strong> Pagination can be seamlessly integrated, ensuring smooth navigation through large comment threads.</p>
</li>
<li><p><strong>Network Efficiency:</strong> Network traffic is minimized as only necessary comments are fetched, reducing load times for users.</p>
</li>
</ul>
<p>While the theoretical elegance of Approach 1 may seem appealing, the practical limitations it poses make it unsuitable for implementation in relational databases. Approach 2, with its bottom-up design and self-referential model, proves to be the pragmatic choice for efficiently implementing nested comments. Its alignment with relational database principles, along with its simplicity and scalability, makes it the ideal solution for managing nested comment threads.</p>
<h2 id="heading-backend">Backend</h2>
<p><img src="https://plopdo.com/wp-content/uploads/2021/10/What-is-back-end-development-2.jpg" alt="Backend Development : Understanding the basics - PloPdo" /></p>
<p>When designing the backend APIs for nested comments, the design pattern remains consistent regardless of the backend framework chosen, whether</p>
<p>it's Node.js, Django, Spring, or Golang. The key focus lies in optimizing the querying of the database to fetch comments efficiently, ensuring both performance and network optimization. As discussed earlier, the database design is already established, eliminating the need for additional consideration.</p>
<h3 id="heading-fetching-of-comments">Fetching of Comments</h3>
<p><strong>Manual Way</strong></p>
<ul>
<li><p><strong>Fetching Top-level Comments</strong></p>
<p>  To retrieve the top-level comments of a post, a simple query is executed to fetch all comments associated with the desired <code>postId</code> and where the <code>parentId</code> is <code>null</code>. Since top-level comments have no parent, this query efficiently retrieves all relevant comments for the post.</p>
</li>
<li><p><strong>Fetching 1st-level Nested Comments</strong></p>
<p>  These are typically the replies to top-level comments. To fetch 1st-level nested comments for a specific comment, a query retrieves all comments related to the post and with a <code>parentId</code> corresponding to the desired comment. While it may seem inefficient to query for each comment's replies individually, this on-demand fetching optimizes network usage.</p>
</li>
<li><p><strong>Fetching nth-level Nested Comments</strong></p>
<p>  Similar to fetching 1st-level nested comments, the process involves querying comments based on the <code>postId</code> and <code>parentId</code> of the desired comment. However, to optimize network usage, platforms like Facebook or Reddit typically limit the initial fetch to 1 or 2 levels of comments. Further levels or comments are fetched on demand, ensuring efficient network utilization.</p>
</li>
</ul>
<p><strong>MPTT Algorithm for Nested Comments</strong></p>
<p>An alternative approach to managing nested comments is the Modified Preorder Tree Traversal (MPTT) algorithm. This algorithm assigns each comment a left and right value, allowing for efficient querying of hierarchical data. While beyond the scope of this article, the MPTT algorithm provides another method for handling nested comments, offering advantages in certain scenarios such as heavy read operations or frequent tree manipulations.</p>
<p>By implementing these strategies, backend APIs can efficiently handle nested comments, providing users with a seamless and optimized commenting experience.</p>
<h3 id="heading-insertion-amp-deletion-of-comments">Insertion &amp; Deletion of Comments</h3>
<p>The circular models of relational databases result in an n-ary tree-based structure, typically associated with O(log(n)) time complexity for insertion and deletion operations. However, circular or self-referential models in relational databases offer optimized performance, allowing for insertion and deletion of comments in O(1) time at the database level.</p>
<ul>
<li><h4 id="heading-insertion-of-comments">Insertion of Comments</h4>
</li>
</ul>
<p>Inserting comments involves adding new comments to a thread or replying to existing comments. At the database level, only the <code>postId</code> and <code>parentId</code> are required for insertion into the table. When commenting on a post, the <code>postId</code> of the thread and the nested level are known. For top-level comments, the <code>parentId</code> is simply <code>null</code>, while for replies, the <code>parentId</code> corresponds to the <code>id</code> of the comment being replied to. This straightforward insertion process does not require traversing the tree and can be accomplished in constant time.</p>
<ul>
<li><h4 id="heading-deletion-of-comments">Deletion of Comments</h4>
</li>
</ul>
<p>Deletion operations exhibit similar performance characteristics. With knowledge of the <code>id</code> of the comment to be deleted, the comment is swiftly removed from the table without the need for tree traversal.</p>
<p>By leveraging the optimized performance of circular models in relational databases, insertion and deletion of comments can be executed efficiently, contributing to a seamless user experience.</p>
<h2 id="heading-frontend">Frontend</h2>
<p><img src="https://bs-uploads.toptal.io/blackfish-uploads/components/blog_post_page/content/cover_image_file/cover_image/1284735/retina_500x200_op-Ten-Front-End-Design-Rules-For-Developers_Luke-Newsletter-d3a7d3e7430ee224cab75104f11342a0.png" alt="Top Ten Front-end Design Tips | Toptal®" /></p>
<p>Frontend implementation of deeply nested comments varies across platforms, presenting unique challenges and requiring different approaches for web and mobile devices. Given the complexity involved, detailed discussion of frontend UI implementation will be covered in a separate blog. In web development, leveraging a declarative approach may aid in handling complex UI requirements, while in mobile development, especially Android, implementing deeply nested behavior within a RecyclerView poses significant challenges, which will be explored in the frontend blog.</p>
<p>I've implemented the deeply nested comments architecture using Django and Android, but the approach remains consistent across other backend frameworks in terms of API logic. Similarly, the declarative UI approach in web frontend development simplifies the implementation process.</p>
<p>By understanding the underlying principles and design considerations discussed in this blog, developers can effectively implement and optimize deeply nested comments architecture across various platforms and frameworks.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>This blog has aimed to delve into the architectural aspects of implementing deeply nested comments behavior, inspired by the structures found in platforms like Facebook or Reddit. By exploring the backend logic and considerations for database design, as well as discussing the challenges and strategies for frontend implementation, we've aimed to provide a comprehensive overview of the intricacies involved in handling nested comments.</p>
<p>Through this exploration, developers can gain insights into the complexities of managing hierarchical data structures and learn effective strategies for optimizing performance and user experience.</p>
<p>While this blog focuses primarily on the architecture behind deeply nested comments, it sets the stage for further discussions on frontend UI implementation and additional optimization techniques in future articles.</p>
<p>By understanding the fundamental principles discussed here, developers can embark on their journey to building robust and scalable comment systems, enriching the user experience in their applications.</p>
]]></content:encoded></item><item><title><![CDATA[Evolution of List Rendering in Android]]></title><description><![CDATA[Lists are integral components in Android applications, found across various functionalities like contacts, chat, social media, and music apps. Even seemingly simple apps, like calculators, incorporate lists for unit conversions. As an Android develop...]]></description><link>https://blogs.ijlalahmad.dev/evolution-of-list-rendering-in-android</link><guid isPermaLink="true">https://blogs.ijlalahmad.dev/evolution-of-list-rendering-in-android</guid><category><![CDATA[scrollview]]></category><category><![CDATA[listrendering]]></category><category><![CDATA[Android]]></category><category><![CDATA[android development]]></category><category><![CDATA[software development]]></category><category><![CDATA[liveview]]></category><category><![CDATA[RecyclerView  ]]></category><category><![CDATA[Pagination]]></category><category><![CDATA[Jetpack Compose]]></category><category><![CDATA[lazycolumn]]></category><category><![CDATA[optimization]]></category><category><![CDATA[performance]]></category><category><![CDATA[user experience]]></category><category><![CDATA[resources]]></category><category><![CDATA[memory-management]]></category><dc:creator><![CDATA[Ijlal Ahmad]]></dc:creator><pubDate>Tue, 06 Feb 2024 19:05:19 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1707141532791/544a6d63-d21c-4454-9c7f-0414242797e6.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Lists are integral components in Android applications, found across various functionalities like contacts, chat, social media, and music apps. Even seemingly simple apps, like calculators, incorporate lists for unit conversions. As an Android developer, understanding the history of list rendering and adopting best practices is crucial. In the past, developers employed diverse techniques for list implementation, each with its strengths and weaknesses. These methods have evolved, considering factors like performance and user experience. In this blog, we'll explore earlier approaches, evaluating their merits and drawbacks, leading to a discussion on contemporary best practices. By examining the trajectory of list rendering in Android, developers can make informed decisions to enhance their app's efficiency and user satisfaction.</p>
<h3 id="heading-scroll-view">Scroll View</h3>
<p>When delving into Android development, beginners often consider the <code>ScrollView</code> as an apparent solution for implementing scrolling behavior in applications with multiple elements. While it indeed facilitates vertical scrolling for pages with various components, relying on <code>ScrollView</code> for extensive lists, like those in contacts or social media apps, is considered a suboptimal approach.</p>
<p><code>ScrollView</code> exhibits significant drawbacks in terms of both performance and memory usage. Views rendered within a <code>ScrollView</code> are static, occupying space in memory and consuming resources even when not displayed on the screen. In the context of modern Android phones with ample resources, this may not pose an immediate problem. However, as the size of lists grows, issues can arise, particularly in applications with infinite scroll functionality, such as social media apps.</p>
<p>In this approach, each list item operates independently of others and the parent view, lacking dynamic behavior functionalities. Managing the state of each item in memory becomes explicit, leading to resource-intensive click listeners and potential app lagginess. Recognizing these limitations, it is crucial for developers to explore more efficient alternatives for implementing large lists in Android applications.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">🤮</div>
<div data-node-type="callout-text">Example :-</div>
</div>

<pre><code class="lang-kotlin"><span class="hljs-comment">// Assuming you have a ScrollView with id scrollView in your XML layout file</span>

<span class="hljs-keyword">val</span> scrollView = findViewById&lt;ScrollView&gt;(R.id.scrollView)
<span class="hljs-keyword">val</span> linearLayout = LinearLayout(<span class="hljs-keyword">this</span>)
linearLayout.orientation = LinearLayout.VERTICAL

<span class="hljs-comment">// Create and add TextViews dynamically</span>
<span class="hljs-keyword">for</span> (i <span class="hljs-keyword">in</span> <span class="hljs-number">1</span>..<span class="hljs-number">10</span>) {
    <span class="hljs-keyword">val</span> textView = TextView(<span class="hljs-keyword">this</span>)
    textView.layoutParams = LinearLayout.LayoutParams(
        LinearLayout.LayoutParams.MATCH_PARENT,
        LinearLayout.LayoutParams.WRAP_CONTENT
    )
    textView.text = <span class="hljs-string">"Item <span class="hljs-variable">$i</span>"</span>
    linearLayout.addView(textView)
}

<span class="hljs-comment">// Add the LinearLayout with TextViews to the ScrollView</span>
scrollView.addView(linearLayout)
</code></pre>
<h3 id="heading-listview-a-step-towards-optimization">ListView: A Step Towards Optimization</h3>
<p>Introduced as an improvement over the naive <code>ScrollView</code> approach, the <code>ListView</code> represents an early optimization for handling lists in Android applications. Unlike the static nature of <code>ScrollView</code>, <code>ListView</code> employs an adapter-based approach, efficiently rendering and reserving resources only for the visible list items on the screen. This adaptive behavior significantly enhances performance by avoiding unnecessary resource allocation for off-screen items. Developers can customize the adapter to meet specific requirements, making <code>ListView</code> a versatile solution for list implementation.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1707144139341/1d86e1a3-71a5-4628-b565-a31e8f6829c8.png" alt="ListView" class="image--center mx-auto" /></p>
<p>However, the Achilles' heel of <code>ListView</code> lies in its failure to deallocate resources for items that have been scrolled above, limiting its efficiency in scenarios with extensive lists or infinite scrolling requirements. While suitable for scenarios with a large but finite number of items, <code>ListView</code> faces challenges in meeting the demands of modern social media apps with infinite scroll functionality. As applications increasingly demand the ability to seamlessly scroll through extensive lists, the need for resource-friendly alternatives becomes apparent.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">🤓</div>
<div data-node-type="callout-text">Example :-</div>
</div>

<blockquote>
<p><strong>CustomAdapter.kt</strong></p>
</blockquote>
<pre><code class="lang-kotlin"><span class="hljs-comment">// Define a custom adapter for ListView</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CustomListAdapter</span></span>(<span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> context: Context, <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> itemList: List&lt;String&gt;) : BaseAdapter() {

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getCount</span><span class="hljs-params">()</span></span>: <span class="hljs-built_in">Int</span> {
        <span class="hljs-keyword">return</span> itemList.size
    }

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getItem</span><span class="hljs-params">(position: <span class="hljs-type">Int</span>)</span></span>: Any {
        <span class="hljs-keyword">return</span> itemList[position]
    }

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getItemId</span><span class="hljs-params">(position: <span class="hljs-type">Int</span>)</span></span>: <span class="hljs-built_in">Long</span> {
        <span class="hljs-keyword">return</span> position.toLong()
    }

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getView</span><span class="hljs-params">(position: <span class="hljs-type">Int</span>, convertView: <span class="hljs-type">View</span>?, parent: <span class="hljs-type">ViewGroup</span>?)</span></span>: View {
        <span class="hljs-keyword">var</span> view = convertView
        <span class="hljs-keyword">if</span> (view == <span class="hljs-literal">null</span>) {
            view = LayoutInflater.from(context).inflate(R.layout.list_item_layout, parent, <span class="hljs-literal">false</span>)
        }
        <span class="hljs-keyword">val</span> textView = view.findViewById&lt;TextView&gt;(R.id.textView)
        textView.text = itemList[position]
        <span class="hljs-keyword">return</span> view
    }
}
</code></pre>
<blockquote>
<p>list_item_layout.xml</p>
</blockquote>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">TextView</span> <span class="hljs-attr">xmlns:android</span>=<span class="hljs-string">"http://schemas.android.com/apk/res/android"</span>
    <span class="hljs-attr">android:id</span>=<span class="hljs-string">"@+id/textView"</span>
    <span class="hljs-attr">android:layout_width</span>=<span class="hljs-string">"match_parent"</span>
    <span class="hljs-attr">android:layout_height</span>=<span class="hljs-string">"wrap_content"</span>
    <span class="hljs-attr">android:padding</span>=<span class="hljs-string">"10dp"</span> /&gt;</span>
</code></pre>
<blockquote>
<p><strong>MainActivity.kt</strong></p>
</blockquote>
<pre><code class="lang-kotlin"><span class="hljs-comment">// Set up ListView and attach adapter</span>
<span class="hljs-keyword">val</span> listView = findViewById&lt;ListView&gt;(R.id.listView)
<span class="hljs-keyword">val</span> itemList = listOf(<span class="hljs-string">"Item 1"</span>, <span class="hljs-string">"Item 2"</span>, <span class="hljs-string">"Item 3"</span>, <span class="hljs-string">"Item 4"</span>, <span class="hljs-string">"Item 5"</span>) <span class="hljs-comment">// Example list of items</span>

<span class="hljs-keyword">val</span> adapter = CustomListAdapter(<span class="hljs-keyword">this</span>, itemList)
listView.adapter = adapter
</code></pre>
<h3 id="heading-recyclerview-arrayadapter-efficient-list-handling">RecyclerView + ArrayAdapter: Efficient List Handling</h3>
<p>Recognizing the limitations of <code>ListView</code> in terms of not deallocating resources for swiped items, the <code>RecyclerView</code> emerges as a robust and arguably the best approach in XML-based Android apps. It introduces the concept of view recycling, addressing the inefficiencies of its predecessor. The core functionality involves recycling swiped views, placing them at the opposite end while seamlessly filling with new data.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1707145324742/5c3d9ab8-b914-495d-8e42-e24bbe065d6d.png" alt="RecyclerView" class="image--center mx-auto" /></p>
<p><code>RecyclerView</code> incorporates an advanced <code>ArrayAdapter</code> that dynamically manages data based on the visible items on the screen. This optimized approach ensures that only a limited number of views reside in memory, making it exceptionally well-suited for scenarios involving extensive lists or infinite scrolling, where resource management is critical. Additionally, <code>RecyclerView</code> enables seamless implementation of features like swiping list items for specific actions (e.g., left swipe to delete, right swipe to archive) and efficient item rearrangement, accompanied by smooth animations.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">🤠</div>
<div data-node-type="callout-text">Example :-</div>
</div>

<blockquote>
<p>CustomAdapter.kt</p>
</blockquote>
<pre><code class="lang-kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CustomAdapter</span></span>(<span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> itemList: List&lt;String&gt;) :
    RecyclerView.Adapter&lt;CustomAdapter.CustomViewHolder&gt;() {

    <span class="hljs-keyword">inner</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CustomViewHolder</span></span>(itemView: View) : RecyclerView.ViewHolder(itemView) {
        <span class="hljs-keyword">val</span> textView: TextView = itemView.findViewById(R.id.textView)
    }

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onCreateViewHolder</span><span class="hljs-params">(parent: <span class="hljs-type">ViewGroup</span>, viewType: <span class="hljs-type">Int</span>)</span></span>: CustomViewHolder {
        <span class="hljs-keyword">val</span> view = LayoutInflater.from(parent.context)
            .inflate(R.layout.list_item_layout, parent, <span class="hljs-literal">false</span>)
        <span class="hljs-keyword">return</span> CustomViewHolder(view)
    }

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onBindViewHolder</span><span class="hljs-params">(holder: <span class="hljs-type">CustomViewHolder</span>, position: <span class="hljs-type">Int</span>)</span></span> {
        holder.textView.text = itemList[position]
    }

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getItemCount</span><span class="hljs-params">()</span></span>: <span class="hljs-built_in">Int</span> {
        <span class="hljs-keyword">return</span> itemList.size
    }
}
</code></pre>
<blockquote>
<p><strong>list_item_layout.xml</strong></p>
</blockquote>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">TextView</span> <span class="hljs-attr">xmlns:android</span>=<span class="hljs-string">"http://schemas.android.com/apk/res/android"</span>
    <span class="hljs-attr">android:id</span>=<span class="hljs-string">"@+id/textView"</span>
    <span class="hljs-attr">android:layout_width</span>=<span class="hljs-string">"match_parent"</span>
    <span class="hljs-attr">android:layout_height</span>=<span class="hljs-string">"wrap_content"</span>
    <span class="hljs-attr">android:padding</span>=<span class="hljs-string">"10dp"</span> /&gt;</span>
</code></pre>
<blockquote>
<p>MainActivity.kt</p>
</blockquote>
<pre><code class="lang-kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MainActivity</span> : <span class="hljs-type">AppCompatActivity</span></span>() {

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onCreate</span><span class="hljs-params">(savedInstanceState: <span class="hljs-type">Bundle</span>?)</span></span> {
        <span class="hljs-keyword">super</span>.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        <span class="hljs-keyword">val</span> recyclerView = findViewById&lt;RecyclerView&gt;(R.id.recyclerView)
        <span class="hljs-keyword">val</span> itemList = listOf(<span class="hljs-string">"Item 1"</span>, <span class="hljs-string">"Item 2"</span>, <span class="hljs-string">"Item 3"</span>, <span class="hljs-string">"Item 4"</span>, <span class="hljs-string">"Item 5"</span>) <span class="hljs-comment">// Example list of items</span>

        <span class="hljs-keyword">val</span> layoutManager = LinearLayoutManager(<span class="hljs-keyword">this</span>)
        recyclerView.layoutManager = layoutManager

        <span class="hljs-keyword">val</span> adapter = CustomAdapter(itemList)
        recyclerView.adapter = adapter
    }
}
</code></pre>
<h3 id="heading-recyclerview-listadapter-advanced-list-operations-made-efficient">RecyclerView + ListAdapter: Advanced List Operations Made Efficient</h3>
<p>While the combination of <code>RecyclerView</code> and <code>ArrayAdapter</code> proved efficient for many scenarios, certain high-level list operations exposed its limitations. Notably, operations like sorting and filtering, where the exact indices of items are unknown, can lead to inefficient complete list re-renders, causing flickering and suboptimal resource utilization.</p>
<p><code>RecyclerView</code> + <code>ListAdapter</code> introduces a solution to these challenges by leveraging the power of <code>DiffUtil</code>. The <code>ListAdapter</code> is built upon <code>DiffUtil</code>'s difference algorithm, designed by Eugene W. Myers. This algorithm calculates the minimum number of updates required to transform one list into another, enabling efficient handling of complex tasks such as sorting and filtering.</p>
<p>The key advantage lies in <code>DiffUtil</code>'s ability to identify changes in the underlying data and communicate these changes to the <code>ListAdapter</code>. This allows for precise updates, resulting in smooth animations and optimal resource usage. When the list undergoes modifications, <code>DiffUtil</code> calculates the differences, and <code>ListAdapter</code> intelligently applies these changes to the <code>RecyclerView</code>. Whether it's adding, removing, rearranging, sorting, or filtering items, <code>ListAdapter</code> excels in providing an efficient and visually pleasing user experience.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">😎</div>
<div data-node-type="callout-text">Example :-</div>
</div>

<blockquote>
<p>CustomAdapter.kt</p>
</blockquote>
<pre><code class="lang-kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CustomAdapter</span> : <span class="hljs-type">ListAdapter</span>&lt;<span class="hljs-type">String, CustomAdapter.CustomViewHolder</span>&gt;</span>(DiffCallback()) {

    <span class="hljs-keyword">inner</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CustomViewHolder</span></span>(itemView: View) : RecyclerView.ViewHolder(itemView) {
        <span class="hljs-keyword">val</span> textView: TextView = itemView.findViewById(R.id.textView)
    }

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onCreateViewHolder</span><span class="hljs-params">(parent: <span class="hljs-type">ViewGroup</span>, viewType: <span class="hljs-type">Int</span>)</span></span>: CustomViewHolder {
        <span class="hljs-keyword">val</span> view = LayoutInflater.from(parent.context)
            .inflate(R.layout.list_item_layout, parent, <span class="hljs-literal">false</span>)
        <span class="hljs-keyword">return</span> CustomViewHolder(view)
    }

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onBindViewHolder</span><span class="hljs-params">(holder: <span class="hljs-type">CustomViewHolder</span>, position: <span class="hljs-type">Int</span>)</span></span> {
        holder.textView.text = getItem(position)
    }

    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DiffCallback</span> : <span class="hljs-type">DiffUtil.ItemCallback</span>&lt;<span class="hljs-type">String</span>&gt;</span>() {
        <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">areItemsTheSame</span><span class="hljs-params">(oldItem: <span class="hljs-type">String</span>, newItem: <span class="hljs-type">String</span>)</span></span>: <span class="hljs-built_in">Boolean</span> {
            <span class="hljs-keyword">return</span> oldItem == newItem
        }

        <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">areContentsTheSame</span><span class="hljs-params">(oldItem: <span class="hljs-type">String</span>, newItem: <span class="hljs-type">String</span>)</span></span>: <span class="hljs-built_in">Boolean</span> {
            <span class="hljs-keyword">return</span> oldItem == newItem
        }
    }
}
</code></pre>
<blockquote>
<p><strong>list_item_layout.xml</strong></p>
</blockquote>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">TextView</span> <span class="hljs-attr">xmlns:android</span>=<span class="hljs-string">"http://schemas.android.com/apk/res/android"</span>
    <span class="hljs-attr">android:id</span>=<span class="hljs-string">"@+id/textView"</span>
    <span class="hljs-attr">android:layout_width</span>=<span class="hljs-string">"match_parent"</span>
    <span class="hljs-attr">android:layout_height</span>=<span class="hljs-string">"wrap_content"</span>
    <span class="hljs-attr">android:padding</span>=<span class="hljs-string">"10dp"</span> /&gt;</span>
</code></pre>
<blockquote>
<p><strong>MainActivity.kt</strong></p>
</blockquote>
<pre><code class="lang-kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MainActivity</span> : <span class="hljs-type">AppCompatActivity</span></span>() {

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onCreate</span><span class="hljs-params">(savedInstanceState: <span class="hljs-type">Bundle</span>?)</span></span> {
        <span class="hljs-keyword">super</span>.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        <span class="hljs-keyword">val</span> recyclerView = findViewById&lt;RecyclerView&gt;(R.id.recyclerView)
        <span class="hljs-keyword">val</span> itemList = listOf(<span class="hljs-string">"Item 1"</span>, <span class="hljs-string">"Item 2"</span>, <span class="hljs-string">"Item 3"</span>, <span class="hljs-string">"Item 4"</span>, <span class="hljs-string">"Item 5"</span>) <span class="hljs-comment">// Example list of items</span>

        <span class="hljs-keyword">val</span> layoutManager = LinearLayoutManager(<span class="hljs-keyword">this</span>)
        recyclerView.layoutManager = layoutManager

        <span class="hljs-keyword">val</span> adapter = CustomAdapter()
        recyclerView.adapter = adapter
        adapter.submitList(itemList)
    }
}
</code></pre>
<h3 id="heading-recyclerview-listadapter-databinding-simplifying-ui-binding-and-enhancing-developer-experience">RecyclerView + ListAdapter + DataBinding: Simplifying UI Binding and Enhancing Developer Experience</h3>
<p>While <code>ListAdapter</code> significantly optimizes list handling, simplifying UI binding can further enhance developer experience and runtime performance. Enter Android Jetpack's Data Binding Library. It revolutionizes UI development by allowing declarative binding of UI components in XML layouts to data sources in your app. This approach minimizes boilerplate code and improves code readability.</p>
<p>By incorporating Data Binding into our RecyclerView + ListAdapter setup, we can eliminate the need for XML <code>findViewById()</code> and related variables, reducing code complexity. With Data Binding, XML layout views are directly referenced as Kotlin objects generated by the library. This means we can integrate <code>list_item_layout.xml</code> views directly into our <code>CustomAdapter.kt</code> file, binding all item layout variables within the XML itself.</p>
<p>Additionally, Data Binding enables the creation of Binding Adapters, facilitating direct list assignment to RecyclerView. This eliminates the need for manually setting up RecyclerView adapters and simplifies the process of populating RecyclerView with data.</p>
<p>Although Data Binding introduces a tradeoff in terms of increased compile time, the benefits in terms of improved runtime performance and reduced code complexity outweigh this drawback. While initially complex to grasp, Data Binding becomes invaluable for enhancing developer productivity and maintaining clean, efficient codebases in the long run.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">🧐</div>
<div data-node-type="callout-text">Example :-</div>
</div>

<blockquote>
<p><strong>build.gradle</strong></p>
</blockquote>
<pre><code class="lang-makefile">android {
    ...
    dataBinding {
        enabled = true
    }
}
</code></pre>
<blockquote>
<p><strong>list_item_layout.xml</strong></p>
</blockquote>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">layout</span> <span class="hljs-attr">xmlns:android</span>=<span class="hljs-string">"http://schemas.android.com/apk/res/android"</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">data</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">variable</span>
            <span class="hljs-attr">name</span>=<span class="hljs-string">"item"</span>
            <span class="hljs-attr">type</span>=<span class="hljs-string">"String"</span> /&gt;</span>

    <span class="hljs-tag">&lt;/<span class="hljs-name">data</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">TextView</span>
        <span class="hljs-attr">android:layout_width</span>=<span class="hljs-string">"match_parent"</span>
        <span class="hljs-attr">android:layout_height</span>=<span class="hljs-string">"wrap_content"</span>
        <span class="hljs-attr">android:text</span>=<span class="hljs-string">"@{item}"</span> /&gt;</span>

<span class="hljs-tag">&lt;/<span class="hljs-name">layout</span>&gt;</span>
</code></pre>
<blockquote>
<p><strong>Binding Adapter</strong></p>
</blockquote>
<pre><code class="lang-kotlin"><span class="hljs-keyword">object</span> RecyclerViewBindingAdapters {

    <span class="hljs-meta">@JvmStatic</span>
    <span class="hljs-meta">@BindingAdapter(<span class="hljs-meta-string">"items"</span>)</span>
    <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">setItems</span><span class="hljs-params">(recyclerView: <span class="hljs-type">RecyclerView</span>, items: <span class="hljs-type">List</span>&lt;<span class="hljs-type">String</span>&gt;?)</span></span> {
        <span class="hljs-keyword">val</span> adapter = recyclerView.adapter <span class="hljs-keyword">as</span>? CustomAdapter
        adapter?.submitList(items)
    }
}
</code></pre>
<blockquote>
<p><strong>CustomAdapter.kt:</strong></p>
</blockquote>
<pre><code class="lang-kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CustomAdapter</span> : <span class="hljs-type">ListAdapter</span>&lt;<span class="hljs-type">String, CustomAdapter.CustomViewHolder</span>&gt;</span>(DiffCallback()) {

    <span class="hljs-keyword">inner</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CustomViewHolder</span></span>(<span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> binding: ListItemLayoutBinding) :
        RecyclerView.ViewHolder(binding.root) {

        <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">bind</span><span class="hljs-params">(item: <span class="hljs-type">String</span>)</span></span> {
            binding.item = item
            binding.executePendingBindings()
        }
    }

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onCreateViewHolder</span><span class="hljs-params">(parent: <span class="hljs-type">ViewGroup</span>, viewType: <span class="hljs-type">Int</span>)</span></span>: CustomViewHolder {
        <span class="hljs-keyword">val</span> inflater = LayoutInflater.from(parent.context)
        <span class="hljs-keyword">val</span> binding = ListItemLayoutBinding.inflate(inflater, parent, <span class="hljs-literal">false</span>)
        <span class="hljs-keyword">return</span> CustomViewHolder(binding)
    }

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onBindViewHolder</span><span class="hljs-params">(holder: <span class="hljs-type">CustomViewHolder</span>, position: <span class="hljs-type">Int</span>)</span></span> {
        holder.bind(getItem(position))
    }

    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DiffCallback</span> : <span class="hljs-type">DiffUtil.ItemCallback</span>&lt;<span class="hljs-type">String</span>&gt;</span>() {
        <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">areItemsTheSame</span><span class="hljs-params">(oldItem: <span class="hljs-type">String</span>, newItem: <span class="hljs-type">String</span>)</span></span>: <span class="hljs-built_in">Boolean</span> {
            <span class="hljs-keyword">return</span> oldItem == newItem
        }

        <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">areContentsTheSame</span><span class="hljs-params">(oldItem: <span class="hljs-type">String</span>, newItem: <span class="hljs-type">String</span>)</span></span>: <span class="hljs-built_in">Boolean</span> {
            <span class="hljs-keyword">return</span> oldItem == newItem
        }
    }
}
</code></pre>
<blockquote>
<p><strong>activity_main.xml:</strong></p>
</blockquote>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">layout</span> <span class="hljs-attr">xmlns:android</span>=<span class="hljs-string">"http://schemas.android.com/apk/res/android"</span>
    <span class="hljs-attr">xmlns:app</span>=<span class="hljs-string">"http://schemas.android.com/apk/res-auto"</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">data</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">variable</span>
            <span class="hljs-attr">name</span>=<span class="hljs-string">"viewModel"</span>
            <span class="hljs-attr">type</span>=<span class="hljs-string">"com.example.ViewModel"</span> /&gt;</span>

    <span class="hljs-tag">&lt;/<span class="hljs-name">data</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">androidx.recyclerview.widget.RecyclerView</span>
        <span class="hljs-attr">android:id</span>=<span class="hljs-string">"@+id/recyclerView"</span>
        <span class="hljs-attr">android:layout_width</span>=<span class="hljs-string">"match_parent"</span>
        <span class="hljs-attr">android:layout_height</span>=<span class="hljs-string">"match_parent"</span>
        <span class="hljs-attr">app:items</span>=<span class="hljs-string">"@{viewModel.items}"</span> /&gt;</span>

<span class="hljs-tag">&lt;/<span class="hljs-name">layout</span>&gt;</span>
</code></pre>
<blockquote>
<p><strong>MainActivity.kt:</strong></p>
</blockquote>
<pre><code class="lang-kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MainActivity</span> : <span class="hljs-type">AppCompatActivity</span></span>() {

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">lateinit</span> <span class="hljs-keyword">var</span> binding: ActivityMainBinding
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> viewModel: ViewModel <span class="hljs-keyword">by</span> viewModels()

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onCreate</span><span class="hljs-params">(savedInstanceState: <span class="hljs-type">Bundle</span>?)</span></span> {
        <span class="hljs-keyword">super</span>.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(<span class="hljs-keyword">this</span>, R.layout.activity_main)
        binding.lifecycleOwner = <span class="hljs-keyword">this</span>
        binding.viewModel = viewModel
    }
}
</code></pre>
<h3 id="heading-recyclerview-pagination-adapter-implementing-scrolling-pagination">RecyclerView + Pagination Adapter: Implementing Scrolling Pagination</h3>
<p>In many social media apps, you'll often encounter a loading indicator while scrolling, indicating that new content is being fetched. Implementing this behavior involves pagination, a crucial aspect of handling large datasets, especially in network-dependent applications like social media platforms. Pagination is essential because network-based apps receive data in parts or pages rather than as a complete dataset.</p>
<p>Unlike offline apps like contacts, which can provide all data at once, network-dependent apps fetch data incrementally. In this section, we'll provide a brief overview of pagination logic, as it warrants its own dedicated discussion. Implementing scrolling pagination in <code>RecyclerView</code> presents challenges due to its view recycling mechanism. Views in a <code>RecyclerView</code> are recycled, making it challenging to determine when the list ends based solely on the displayed data. However, RecyclerView offers Layout Managers, which enable us to calculate when the last item of the dataset is displayed. Leveraging this information, we can trigger the fetching of new data to provide a seamless scrolling experience. By understanding how Layout Managers work and utilizing them effectively, developers can implement scrolling pagination efficiently.</p>
<p>This approach ensures that new data is fetched dynamically as the user scrolls, providing a continuous stream of content without overwhelming the device's resources. Scrolling pagination is a fundamental aspect of modern app development, especially in scenarios where datasets are extensive or fetched from remote sources. By mastering pagination techniques in <code>RecyclerView</code>, developers can create responsive and user-friendly applications that handle large datasets gracefully.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">🌌</div>
<div data-node-type="callout-text">Example :-</div>
</div>

<pre><code class="lang-kotlin"><span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">setupPagination</span><span class="hljs-params">(recyclerView: <span class="hljs-type">RecyclerView</span>, viewModel: <span class="hljs-type">ViewModel</span>)</span></span> {
    <span class="hljs-keyword">val</span> layoutManager = recyclerView.layoutManager
    <span class="hljs-keyword">var</span> visibleThreshold = <span class="hljs-number">4</span>

    <span class="hljs-keyword">if</span> (layoutManager <span class="hljs-keyword">is</span> LinearLayoutManager) {
        recyclerView.addOnScrollListener(<span class="hljs-keyword">object</span> : RecyclerView.OnScrollListener() {
            <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onScrollStateChanged</span><span class="hljs-params">(recyclerView: <span class="hljs-type">RecyclerView</span>, newState: <span class="hljs-type">Int</span>)</span></span> {
                <span class="hljs-keyword">super</span>.onScrollStateChanged(recyclerView, newState)
                <span class="hljs-keyword">val</span> lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition()

                <span class="hljs-keyword">if</span> (viewModel.hasMoreItems.value == <span class="hljs-literal">true</span> &amp;&amp;
                    viewModel.paginationStatus.value == Constants.PAGE_IDLE &amp;&amp;
                    lastVisibleItemPosition != RecyclerView.NO_POSITION &amp;&amp;
                    lastVisibleItemPosition &gt;= layoutManager.itemCount - visibleThreshold
                ) {
                    viewModel.loadMoreItems()
                }
            }
        })
    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (layoutManager <span class="hljs-keyword">is</span> GridLayoutManager || layoutManager <span class="hljs-keyword">is</span> StaggeredGridLayoutManager) {
        <span class="hljs-keyword">if</span> (layoutManager <span class="hljs-keyword">is</span> GridLayoutManager) {
            visibleThreshold *= layoutManager.spanCount
        }

        recyclerView.addOnScrollListener(<span class="hljs-keyword">object</span> : RecyclerView.OnScrollListener() {
            <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onScrolled</span><span class="hljs-params">(recyclerView: <span class="hljs-type">RecyclerView</span>, dx: <span class="hljs-type">Int</span>, dy: <span class="hljs-type">Int</span>)</span></span> {
                <span class="hljs-keyword">super</span>.onScrolled(recyclerView, dx, dy)
                <span class="hljs-keyword">val</span> lastVisibleItemPosition = layoutManager.findLastVisibleItemPositions(<span class="hljs-literal">null</span>).maxOrNull() ?: <span class="hljs-number">0</span>

                <span class="hljs-keyword">if</span> (viewModel.hasMoreItems.value == <span class="hljs-literal">true</span> &amp;&amp;
                    viewModel.paginationStatus.value == Constants.PAGE_IDLE &amp;&amp;
                    lastVisibleItemPosition != RecyclerView.NO_POSITION &amp;&amp;
                    lastVisibleItemPosition &gt;= layoutManager.itemCount - visibleThreshold
                ) {
                    viewModel.loadMoreItems()
                }
            }
        })
    }
}
</code></pre>
<p>The <code>setupPagination</code> function configures pagination for a RecyclerView based on the provided layout manager and view model. If the layout manager is LinearLayoutManager, pagination triggers when the last visible item nears a predefined threshold. For GridLayoutManager or StaggeredGridLayoutManager, the threshold adapts based on the layout's span count.</p>
<p>The function adds an OnScrollListener to the RecyclerView, which listens for scroll events. Pagination occurs when certain conditions are met: more items are available to load, pagination isn't in progress, and the last visible item indicates the list's end is approaching.</p>
<p>Overall, this function offers a generic approach to implementing pagination for RecyclerViews with various layout managers, enhancing the user experience by loading additional items as the user scrolls. Additionally, displaying a loader ensures smooth scrolling and informs users of ongoing data loading. Pagination is a complex topic, and this explanation provides just a brief overview of how the logic works. Further details and nuances can be explored in dedicated articles or tutorials on pagination.</p>
<h3 id="heading-jetpack-compose-lazy-column">Jetpack Compose Lazy Column</h3>
<p>In the latest advancements of Android development, Jetpack Compose has emerged as a revolutionary UI toolkit, completely replacing the traditional XML-based approach with a declarative format akin to React. List rendering in Jetpack Compose introduces the concept of <code>LazyColumn</code>, which reimagines how lists are handled without the need for RecyclerView.</p>
<p>The <code>LazyColumn</code> in Jetpack Compose is a powerful component designed for efficiently displaying large lists of items. Unlike RecyclerView, LazyColumn adopts a lazy loading approach, meaning it only composes and lays out the items that are currently visible on the screen. As the user scrolls through the list, LazyColumn dynamically composes and renders new items, ensuring optimal performance and resource utilization.</p>
<p>LazyColumn is particularly well-suited for scenarios involving infinite lists or extensive data sets, as it efficiently manages memory by only rendering items when they are needed. This lazy loading behavior significantly enhances the user experience by ensuring smooth scrolling and fast responsiveness, even with large data sets.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">🚀</div>
<div data-node-type="callout-text">Example :-</div>
</div>

<pre><code class="lang-kotlin"><span class="hljs-meta">@Composable</span>
<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">MyList</span><span class="hljs-params">(items: <span class="hljs-type">List</span>&lt;<span class="hljs-type">String</span>&gt;)</span></span> {
    LazyColumn {
        items(items) { item -&gt;
            Text(text = item, modifier = Modifier.padding(<span class="hljs-number">16</span>.dp))
        }
    }
}

<span class="hljs-meta">@Preview</span>
<span class="hljs-meta">@Composable</span>
<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">PreviewMyList</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">val</span> items = listOf(<span class="hljs-string">"Item 1"</span>, <span class="hljs-string">"Item 2"</span>, <span class="hljs-string">"Item 3"</span>, <span class="hljs-string">"Item 4"</span>, <span class="hljs-string">"Item 5"</span>)
    MyList(items = items)
}
</code></pre>
<p>In this example, <code>MyList</code> is a composable function that takes a list of items as input and renders them within a LazyColumn. Each item is represented by a Text composable, and the LazyColumn efficiently composes and lays out these items as the user scrolls through the list.</p>
<p>Overall, LazyColumn in Jetpack Compose offers a modern and efficient solution for implementing lists in Android applications, showcasing the power and flexibility of declarative UI development.</p>
<h3 id="heading-conclusion">Conclusion</h3>
<p>In conclusion, mastering the evolution of list rendering in Android empowers developers to create efficient, user-friendly apps. By adopting best practices and leveraging advanced tools like RecyclerView and Jetpack Compose, developers can craft responsive, visually appealing experiences. Staying updated with the latest developments ensures apps meet modern user expectations, pushing the boundaries of Android development. Let's build innovative, impactful apps that shape the future of mobile.</p>
]]></content:encoded></item><item><title><![CDATA[Crafting Hybrid NPM Packages with TypeScript: A Comprehensive Guide]]></title><description><![CDATA[In this article, I'll walk you through the process of achieving a hybrid nature for npm packages, drawing from my own experiences and addressing potential challenges that may arise. I'll assume that you have a basic understanding of deploying npm pac...]]></description><link>https://blogs.ijlalahmad.dev/crafting-hybrid-npm-packages-with-typescript-a-comprehensive-guide</link><guid isPermaLink="true">https://blogs.ijlalahmad.dev/crafting-hybrid-npm-packages-with-typescript-a-comprehensive-guide</guid><category><![CDATA[npm]]></category><category><![CDATA[npm publish]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[commonjs]]></category><category><![CDATA[ES Modules]]></category><category><![CDATA[publishing]]></category><category><![CDATA[codebase]]></category><category><![CDATA[Open Source]]></category><dc:creator><![CDATA[Ijlal Ahmad]]></dc:creator><pubDate>Mon, 15 Jan 2024 10:21:08 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1705229598349/3a820992-e8ac-415c-a1b7-f81521506115.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this article, I'll walk you through the process of achieving a hybrid nature for npm packages, drawing from my own experiences and addressing potential challenges that may arise. I'll assume that you have a basic understanding of deploying npm packages to the npm registry, but don't worry—I'll provide guidance on that aspect as well. Let's dive in and get started.</p>
<h1 id="heading-overview">Overview</h1>
<p>Creating and deploying npm packages is generally a straightforward process, requiring minimal configuration in the <code>package.json</code> file and the creation of an account on npmjs. However, a challenge arises when using TypeScript to generate type definitions for your package. The complication arises during the compilation process, as TypeScript outputs only one codebase, either CommonJS or ES modules, depending on your <code>tsconfig.json</code> configuration. This limitation becomes significant when your npm package needs to support both codebases.</p>
<p>While compiling your code to ES modules might suffice for many cases, especially in modern frontend development where browsers inherently support ES modules, the situation becomes more intricate when developing backend-related npm packages. Backend scenarios often involve interactions with databases, file systems, and other components, each requiring different syntax for CommonJS and ES modules. Therefore, to create a versatile npm package compatible with both environments, it's crucial to generate and support two distinct codebases.</p>
<h1 id="heading-how-to-publish-to-npm-registry">How to Publish to NPM Registry</h1>
<p>Publishing an npm package is a straightforward process that even beginners can easily handle. Here's a concise guide to get you started:</p>
<h3 id="heading-high-level-steps">High-Level Steps</h3>
<ol>
<li><p><strong>Install Node:</strong> Ensure sure you have Node.js installed on your system. You can download it from <a target="_blank" href="https://nodejs.org/">nodejs.org</a>.</p>
</li>
<li><p><strong>Create an npm Account:</strong> If you don't have an npm account, create one by running:</p>
<pre><code class="lang-bash"> npm adduser
</code></pre>
<p> Follow the prompts to set up your account.</p>
</li>
<li><p><strong>Initialize a Git Repository:</strong> Set up a Git repository for your project:</p>
<pre><code class="lang-bash"> git init
</code></pre>
</li>
<li><p><strong>Write Your Code:</strong> Write your code in the main file, commonly named <code>index.js</code>.</p>
</li>
<li><p><strong>Configure</strong> <code>package.json</code>: Ensure your <code>package.json</code> file points to the correct entry file (e.g., <code>index.js</code>):</p>
<pre><code class="lang-json"> <span class="hljs-string">"main"</span>: <span class="hljs-string">"index.js"</span>
</code></pre>
</li>
<li><p><strong>Publish to npm:</strong> Finally, publish your package to the npm registry:</p>
<pre><code class="lang-bash"> npm publish
</code></pre>
<p> And there you have it! Your package is now live on the npm registry.</p>
</li>
</ol>
<h3 id="heading-additional-resource">Additional Resource</h3>
<p>For a more detailed walkthrough, you can refer to the <a target="_blank" href="https://www.freecodecamp.org/news/how-to-create-and-publish-your-first-npm-package/">freeCodeCamp guide</a>. This resource provides additional insights and tips for a seamless publishing experience.</p>
<p>Remember, npm publishing is a simple process, and with these high-level steps, you can share your packages with the broader developer community.</p>
<h1 id="heading-the-problem-mono-environment-code-generation">The Problem: Mono-Environment Code Generation</h1>
<p>The challenge arises when you generate code for only one environment, leading to an npm package that exclusively supports that specific environment, be it CommonJS or ES Modules. This limitation hinders the versatility of the package, restricting its usability in different contexts.</p>
<p>Notable libraries, such as RxJS, historically addressed this issue by maintaining two distinct npm packages—one for CommonJS and another for ES Modules. However, not all libraries have adopted this dual-package approach. A prominent example is the <code>chalk</code> library, which, at present, is only compatible with CommonJS codebases, highlighting the prevalence of this challenge in the npm ecosystem.</p>
<h1 id="heading-hybrid-package-practical-use-case">Hybrid package practical use case</h1>
<p>A couple of weeks ago, when I decided to create an npm package for backend development, I encountered the challenge of accommodating different environments—specifically, the distinction between <strong>CommonJS</strong> and the <strong>ES module system</strong>. In my use case, I aimed to leverage the dynamic import system in JavaScript, which allows importing from any file at runtime. However, the syntax for dynamic imports varies between <strong>CommonJS</strong> and <strong>ES Modules</strong>. In CommonJS, we use the <code>require()</code> function, while in ES Modules, we employ the <code>import()</code> function, distinct from the <code>import something from "somewhere"</code> syntax.</p>
<p>Upon compiling my code using <code>tsc</code>, only one codebase was generated—either <strong>CommonJS</strong> or <strong>ES Modules</strong>. When testing the package, it failed to function in the alternate environment. While this is one issue I addressed, there were several other potential complications. Notably, using the <code>dotenv</code> package, widely utilized in Node.js, presented a challenge due to its lack of defined ESM syntax. Another issue involved the <code>__dirname</code> variable, globally available in CommonJS but absent in ES Modules. The list of potential issues extends further.</p>
<p>It's essential to note that this issue primarily surfaces in the backend context, as browsers inherently support ES Modules, mitigating such complexities in frontend development.</p>
<p>Now, a solution is needed to create both <strong>CommonJS</strong> and <strong>ES Module</strong> codebases within a single npm package. Node.js needs to intelligently determine the suitable codebase based on the underlying environment utilizing the package. You might be wondering if I'm the only one grappling with this challenge. No, there is a tool designed explicitly for creating dual codebases called <code>tsup</code>. However, as it didn't work for my specific case, I had to make some tweaks to the <code>tsc</code> (TypeScript Compiler) itself.</p>
<p>I'll guide you through each step and detail the necessary changes to generate both <strong>CommonJS</strong> and <strong>ES Module</strong> codebases. You might be thinking:</p>
<blockquote>
<p>Enough talk, show me the code! 😄</p>
</blockquote>
<h1 id="heading-solution">Solution</h1>
<p>To generate two distinct codebases, we utilize two separate configuration files: <code>tsconfig.cjs.json</code> and <code>tsconfig.esm.json</code>. Each file is responsible for creating one environment—CommonJS and ES Modules, respectively. This approach involves changing the <code>target</code> and <code>module</code> settings in each file, along with specifying a different <code>outDir</code> for outputting the codebase in distinct folders.</p>
<p>Maintaining two separate configuration files might seem cumbersome at first, but TypeScript allows us to streamline this process using an extension mechanism. We create a common base configuration file named <code>tsconfig.json</code>, which includes all the shared settings between <code>tsconfig.cjs.json</code> and <code>tsconfig.esm.json</code>.</p>
<p>Here's my <code>tsconfig.json</code> file</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"compilerOptions"</span>: {
    <span class="hljs-attr">"module"</span>: <span class="hljs-string">"CommonJS"</span>,
    <span class="hljs-attr">"rootDir"</span>: <span class="hljs-string">"./"</span>,
    <span class="hljs-attr">"moduleResolution"</span>: <span class="hljs-string">"Node"</span>,
    <span class="hljs-attr">"baseUrl"</span>: <span class="hljs-string">"./src"</span>,
    <span class="hljs-attr">"paths"</span>: {
      <span class="hljs-attr">"*"</span>: [<span class="hljs-string">"*"</span>, <span class="hljs-string">"src/*"</span>]
    },
    <span class="hljs-attr">"resolveJsonModule"</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">"declaration"</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">"declarationMap"</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">"sourceMap"</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">"removeComments"</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">"preserveConstEnums"</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">"allowSyntheticDefaultImports"</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">"esModuleInterop"</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">"forceConsistentCasingInFileNames"</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">"strict"</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">"noImplicitAny"</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">"strictPropertyInitialization"</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">"noUnusedLocals"</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">"skipLibCheck"</span>: <span class="hljs-literal">true</span>
  }
}
</code></pre>
<p>The esModuleInterop option is set to true to enable ES module interoperability, allowing you to use the import() function for dynamic imports in ES modules. Notice that this configuration doesn't contain the most essential parts of <code>tsconfig.json</code> file i.e. <code>outdir</code>, <code>module</code> and <code>target</code>. Because we have to generate two more tsconfig files</p>
<p><code>tsconfig.cjs.json</code> file</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"extends"</span>: <span class="hljs-string">"./tsconfig.json"</span>,
  <span class="hljs-attr">"compilerOptions"</span>: {
    <span class="hljs-attr">"outDir"</span>: <span class="hljs-string">"dist/cjs"</span>,
    <span class="hljs-attr">"module"</span>: <span class="hljs-string">"commonjs"</span>,
    <span class="hljs-attr">"target"</span>: <span class="hljs-string">"ES2015"</span>
  }
}
</code></pre>
<p><code>tsconfig.esm.json</code> file</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"extends"</span>: <span class="hljs-string">"./tsconfig.json"</span>,
  <span class="hljs-attr">"compilerOptions"</span>: {
    <span class="hljs-attr">"outDir"</span>: <span class="hljs-string">"dist/esm"</span>,
    <span class="hljs-attr">"module"</span>: <span class="hljs-string">"ESNext"</span>,
    <span class="hljs-attr">"target"</span>: <span class="hljs-string">"ESNext"</span>
  }
}
</code></pre>
<blockquote>
<p>Notice how I extended <code>tsconfig.json</code> file into both <code>tsconfig.cjs.json</code> and <code>tsconfig.esm.json</code></p>
</blockquote>
<h3 id="heading-generating-type-definitions">Generating Type Definitions</h3>
<p>When creating an npm package, it is crucial to explicitly generate type definitions for the package. This allows the underlying projects to leverage and benefit from the specified types. Fortunately, TypeScript provides a dedicated key in the <code>tsconfig.json</code> file for this purpose.</p>
<p>For the sake of maintaining a modular structure, I have also defined a separate configuration file, <code>tsconfig.types.json</code>, specifically designed for generating type definitions.</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"extends"</span>: <span class="hljs-string">"./tsconfig.json"</span>,
  <span class="hljs-attr">"compilerOptions"</span>: {
    <span class="hljs-attr">"outDir"</span>: <span class="hljs-string">"./dist/types"</span>,
    <span class="hljs-attr">"declaration"</span>: <span class="hljs-literal">true</span>
  }
}
</code></pre>
<p>In this configuration, the outDir specifies the output directory for the generated type definitions, and the declaration option is set to true to instruct TypeScript to generate .d.ts files alongside the JavaScript output.</p>
<p>By maintaining a separate tsconfig.types.json file, we ensure clarity and modularity in the TypeScript configuration, specifically tailored for generating and distributing type definitions with our npm package.</p>
<p>By now, we have four <code>tsconfig</code> files, and it might seem a bit overwhelming. However, each file serves a specific purpose in achieving the desired hybrid nature for your npm package.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1705313127276/1d6d4fcf-ffce-4b01-acb0-20aa0c129c9e.png" alt="TsConfig Files" /></p>
<h3 id="heading-modifying-the-native-tsc-command">Modifying the Native <code>tsc</code> Command</h3>
<p>To accommodate the multiple TypeScript configuration files we've created, we need to make some adjustments to the <code>tsc</code> command. You can conveniently integrate these tweaks into your <code>package.json</code> file by adding the following script:</p>
<pre><code class="lang-json"><span class="hljs-string">"scripts"</span>: {
    <span class="hljs-attr">"transpile"</span>: <span class="hljs-string">"tsc --project tsconfig.esm.json &amp; tsc --project tsconfig.cjs.json &amp; tsc --project tsconfig.types.json"</span>
}
</code></pre>
<p>This script, named <code>transpile</code>, orchestrates the TypeScript compilation process by executing <code>tsc</code> with three different configuration files: one for ES Modules (<code>tsconfig.esm.json</code>), one for CommonJS (<code>tsconfig.cjs.json</code>), and another for generating type definitions (<code>tsconfig.types.json</code>). This ensures that all aspects of your codebase, including type definitions, are appropriately transpiled and ready for distribution.</p>
<p>Now run this command</p>
<pre><code class="lang-bash">npm run transpile
</code></pre>
<p>You should get dist folder like this</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1705247810186/5f7b379d-bb10-4a9a-9f0e-ef3ea27bcebb.png" alt="Generated dist folder" /></p>
<h3 id="heading-changes-in-packagejson">Changes in package.json</h3>
<p>The <code>dist</code> folder, which contains the separate codebases for CommonJS (CJS) and ES Modules (ESM), requires specific configurations in your <code>package.json</code> file. Follow these steps to update the relevant keys:</p>
<pre><code class="lang-json"><span class="hljs-string">"main"</span>: <span class="hljs-string">"dist/cjs/src/index.js"</span>,
<span class="hljs-string">"module"</span>: <span class="hljs-string">"dist/esm/src/index.js"</span>,
<span class="hljs-string">"types"</span>: <span class="hljs-string">"dist/types/src/index.d.ts"</span>,
</code></pre>
<ol>
<li><p><code>main</code> Key: Point the <code>main</code> key to the CommonJS (<code>CJS</code>) entry file. This is the primary entry point for packages using CommonJS.</p>
</li>
<li><p><code>module</code> Key: Set the <code>module</code> key to point to the ES Modules (<code>ESM</code>) entry file. This is crucial for packages using ES Modules to import your code.</p>
</li>
<li><p><code>types</code> Key: Specify the <code>types</code> key to point to the TypeScript definition file. This ensures that TypeScript projects consuming your package have access to the correct type declarations.</p>
</li>
</ol>
<p>These configurations ensure that your npm package provides the appropriate entry points for both CommonJS and ES Modules, along with proper type definitions for TypeScript users.</p>
<p>In the top-level of your <code>package.json</code> file, add an <code>exports</code> key to facilitate seamless usage of your package across different module systems. This is especially beneficial for projects that support both ES Modules (ESM) and CommonJS (CJS). The configuration looks like this:</p>
<pre><code class="lang-json"><span class="hljs-string">"exports"</span>: {
  <span class="hljs-attr">"."</span>: {
    <span class="hljs-attr">"import"</span>: <span class="hljs-string">"./dist/esm/src/index.js"</span>,
    <span class="hljs-attr">"require"</span>: <span class="hljs-string">"./dist/cjs/src/index.js"</span>
  }
}
</code></pre>
<p>Here's an explanation of the keys:</p>
<ul>
<li><p><code>.</code> (Dot): The dot (<code>.</code>) represents the entire repository. In this context, it specifies the main entry point when someone imports your package.</p>
</li>
<li><p><code>import</code> : Indicates the entry point for projects using ES Modules (<code>import</code> syntax). It points to the ESM version of your code.</p>
</li>
<li><p><code>require</code> : Specifies the entry point for projects using CommonJS (<code>require</code> syntax). It points to the CommonJS version of your code.</p>
</li>
</ul>
<p>This <code>exports</code> configuration enhances the compatibility of your package, ensuring it can be seamlessly consumed by projects using different module systems.</p>
<h3 id="heading-fixing-tsc-bug-for-esm-imports">Fixing <code>tsc</code> Bug for ESM Imports</h3>
<p>When working with ESM (ES Modules), it's crucial to ensure that file extensions are handled correctly, especially when it comes to imports. A known bug in <code>tsc</code> results in generated code lacking the necessary <code>.js</code> extension in imports. To address this issue, we can use the <a target="_blank" href="https://www.npmjs.com/package/tsc-esm-fix">tsc-esm-fix</a> package.</p>
<p>First, install the package using:</p>
<pre><code class="lang-bash">npm i tsc-esm-fix
</code></pre>
<p>Next, add the following script in your <code>package.json</code> file to fix the imports in the ESM codebase:</p>
<pre><code class="lang-json"><span class="hljs-string">"scripts"</span>: {
    <span class="hljs-attr">"fix"</span>: <span class="hljs-string">"tsc-esm-fix --src='dist/esm/src/**/*.js' --ext='.js'"</span>
}
</code></pre>
<p>Now, create a final <code>build</code> script by combining the existing <code>transpile</code> script and the newly added <code>fix</code> script:</p>
<pre><code class="lang-json"><span class="hljs-string">"scripts"</span>: {
    <span class="hljs-attr">"transpile"</span>: <span class="hljs-string">"tsc --project tsconfig.esm.json &amp; tsc --project tsconfig.cjs.json &amp; tsc --project tsconfig.types.json"</span>,
    <span class="hljs-attr">"fix"</span>: <span class="hljs-string">"tsc-esm-fix --src='dist/esm/src/**/*.js' --ext='.js'"</span>,
    <span class="hljs-attr">"build"</span>: <span class="hljs-string">"npm run transpile &amp;&amp; npm run fix"</span>
}
</code></pre>
<p>With these scripts in place, running <code>npm run build</code> will generate both the CommonJS and ES Modules codebases while fixing the ESM imports problem.</p>
<h3 id="heading-finalizing-the-hybrid-package">Finalizing the Hybrid Package</h3>
<p>Congratulations on reaching the final steps! To make your hybrid package seamlessly work with both CommonJS (CJS) and ES Modules (ESM) environments, we need to add specific <code>package.json</code> files in each of the <code>dist/cjs</code> and <code>dist/esm</code> directories.</p>
<p>In <code>dist/esm/package.json</code>:</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"type"</span>: <span class="hljs-string">"module"</span>
}
</code></pre>
<p>In <code>dist/cjs/package.json</code>:</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"type"</span>: <span class="hljs-string">"commonjs"</span>
}
</code></pre>
<p>To streamline this process and automate it each time before publishing, you can add a simple command in your <code>package.json</code> file. For example, in Linux, you can use:</p>
<pre><code class="lang-bash"><span class="hljs-string">"scripts"</span>: {
    <span class="hljs-string">"prebuild"</span>: <span class="hljs-string">"echo '{\"type\": \"commonjs\"}' &gt; dist/cjs/package.json &amp;&amp; echo '{\"type\": \"module\"}' &gt; dist/esm/package.json"</span>,
    <span class="hljs-string">"transpile"</span>: <span class="hljs-string">"tsc --project tsconfig.esm.json &amp; tsc --project tsconfig.cjs.json &amp; tsc --project tsconfig.types.json"</span>,
    <span class="hljs-string">"fix"</span>: <span class="hljs-string">"tsc-esm-fix --src='dist/esm/src/**/*.js' --ext='.js'"</span>,
    <span class="hljs-string">"build"</span>: <span class="hljs-string">"npm run prebuild &amp;&amp; npm run transpile &amp;&amp; npm run fix"</span>
}
</code></pre>
<p>Now, running <code>npm run build</code> will not only generate the codebases but also ensure the appropriate <code>package.json</code> files are in place for both CJS and ESM environments.</p>
<h3 id="heading-wrapping-it-up">Wrapping It Up</h3>
<p>And there you have it—your final <code>dist</code> folder, ready for a seamless publishing experience on npm. Your hybrid package now effortlessly supports both CommonJS (CJS) and ES Modules (ESM) environments, with ESM imports fixed and automated scripts to generate type definitions on the go. With this setup, you can confidently develop your TypeScript package without any worries.</p>
<p>Feel free to explore and experiment, knowing that your npm package is well-prepared for various project environments. Whether it's the dynamic world of ESM or the reliability of CJS, your package is ready to shine.</p>
<p>Now, go ahead, unleash your TypeScript skills, and contribute valuable packages to the developer community. Happy coding!</p>
]]></content:encoded></item><item><title><![CDATA[Why Developers Should Blog?]]></title><description><![CDATA[Knowledge Sharing
First and foremost, prioritizing knowledge sharing should be the primary goal when writing blogs. Developers are often known for their willingness to assist others, understanding that someone might have helped them in the past or th...]]></description><link>https://blogs.ijlalahmad.dev/why-developers-should-blog</link><guid isPermaLink="true">https://blogs.ijlalahmad.dev/why-developers-should-blog</guid><category><![CDATA[Developer]]></category><category><![CDATA[Blogging]]></category><category><![CDATA[community]]></category><category><![CDATA[careerchange]]></category><category><![CDATA[skills]]></category><category><![CDATA[networking]]></category><category><![CDATA[learning]]></category><category><![CDATA[sharing]]></category><category><![CDATA[portfolio]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Programming Tips]]></category><category><![CDATA[programming]]></category><dc:creator><![CDATA[Ijlal Ahmad]]></dc:creator><pubDate>Sun, 31 Dec 2023 18:30:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1704288041494/0465cc10-f78d-4ee8-af75-09afb986a33d.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-knowledge-sharing"><strong>Knowledge Sharing</strong></h3>
<p>First and foremost, prioritizing knowledge sharing should be the primary goal when writing blogs. Developers are often known for their willingness to assist others, understanding that someone might have helped them in the past or they've spent exhaustive hours figuring out a problem themselves, resonating with this quote:</p>
<blockquote>
<p>"6 hours of debugging can save you 5 minutes of reading documentation :)"</p>
</blockquote>
<p>Sharing knowledge not only benefits others but also the sharer. Sometimes, even when sharing your experiences, you might not know the complete truth. Others can provide corrections or additional insights, enriching the shared information.</p>
<p>In group discussions, a third person often sheds light on new perspectives. I've experienced this personally many times. Writing online blogs can attract highly skilled individuals in the developer community, leading to mutual learning experiences.</p>
<p>In conclusion, sharing knowledge is a simple yet powerful way to help a vast number of people effortlessly.</p>
<h3 id="heading-learning-and-reflection"><strong>Learning and Reflection</strong></h3>
<p>While sharing knowledge is crucial, the most significant benefit from writing blogs is for the writer themselves. Expressing thoughts and sharing ideas globally demands a deep understanding of concepts and fundamentals, ultimately contributing to personal learning. This aligns with Newton's Third Law:</p>
<blockquote>
<p>"Every Action has an Equal and Opposite Reaction"</p>
</blockquote>
<p>Often, when we share our insights with peers at similar professional levels, disagreements arise, which is a natural aspect of human nature. Engaging in lengthy debates frequently expands our pool of knowledge.</p>
<p>Another noteworthy aspect is the inherent need for structure and organization when articulating thoughts in writing. Writing blogs compels us to gather information systematically, prompting a professional and well-organized approach when conveying information to others.</p>
<h3 id="heading-building-a-portfolio"><strong>Building a Portfolio</strong></h3>
<p>As previously mentioned, writing blogs has the potential to capture the attention of professionals, companies, and clients. Building a collection of blogs not only keeps you updated within the industry but also serves as a means to showcase your expertise. Your blogs become your voice when applying for positions or handling clients, significantly increasing your chances of success.</p>
<p>Maintaining a blog helps you stay visible to individuals who may not regularly read your posts but keep an eye on your work. Remaining active and updated in the community is crucial in today's programming world. It's vital to recognize that assuming you're the sole efficient and perfect individual in your field is akin to being a frog in a well. As my teacher once wisely said:</p>
<blockquote>
<p>In any profession, there will always be someone better than you, regardless of your level of expertise.</p>
</blockquote>
<p>I can personally attest that in a scenario where two individuals possess identical skills, knowledge, and experience, the one who can articulate and express themselves effectively gains an edge. This is an undeniable FACT. Writing blogs assists in mastering this skill. This situation is particularly prevalent among engineers: while you may possess vast knowledge and problem-solving capabilities, if you lack the ability to express yourself, you might fall short and undermine your potential.</p>
<h3 id="heading-networking-and-community"><strong>Networking and Community</strong></h3>
<p>Blogging serves as an excellent tool for connecting within the expansive realm of developers and communities, offering insights into other developers' work. Prolonged discussions on specific topics foster strong networks, aiding in one's growth within the community.</p>
<p>Receiving feedback from fellow developers refines your knowledge pool, while actively engaging in discussions further hones your skills. Most developers are inclined towards giving back, often making their work public or open source. This practice exposes you to various approaches in tackling tasks, fostering a culture of continuous learning from others' insights and experiences.</p>
<p>Establishing a robust network holds paramount importance upon entering the programming world. A well-connected network not only opens doors to success but also facilitates growth within the tech industry, paving the way for numerous opportunities.</p>
<h3 id="heading-career-advancement"><strong>Career Advancement</strong></h3>
<p>Blogging doesn't just refine your writing skills; it also hones your ability to communicate effectively. Engaging with individuals who want to discuss your blogs enhances your communication skills, a pivotal trait that propels your career to new heights.</p>
<p>Mastering effective communication is a cornerstone skill that significantly contributes to career advancement. As you establish yourself as a helpful figure in the developer community through blogging, opportunities tend to gravitate towards you. This visibility and reputation can lead to exciting career prospects and opportunities you truly deserve.</p>
<p>Additionally, from my experience, I've observed that active participation in discussions or collaborations not only amplifies your visibility but also positions you as a thought leader in your niche. This leadership role often opens doors to speaking engagements, consulting opportunities, and connections with industry leaders, which can substantially elevate your career trajectory.</p>
<hr />
<p>Overall, writing blogs not only benefits others but also contributes to your growth and visibility within the developer community.</p>
]]></content:encoded></item></channel></rss>