<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ Firebase - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ Firebase - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Fri, 15 May 2026 17:23:40 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/firebase/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Create Role-Based Access Control (RBAC) with Custom Claims Using Firebase Rules ]]>
                </title>
                <description>
                    <![CDATA[ When you’re building an application, not all users should have the same level of access. For example, an admin might be able to update or delete some data (logs excluded), while a regular user should only be able to read it. This is where Role-Based ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/firebase-rbac-custom-claims-rules/</link>
                <guid isPermaLink="false">68effb6136e271937e23c188</guid>
                
                    <category>
                        <![CDATA[ Firebase ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Security ]]>
                    </category>
                
                    <category>
                        <![CDATA[ rbac ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Ayodele Aransiola ]]>
                </dc:creator>
                <pubDate>Wed, 15 Oct 2025 19:52:01 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1760557889448/ac51a7a3-cdd8-46c9-964d-a7e281e1affc.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>When you’re building an application, not all users should have the same level of access. For example, an admin might be able to update or delete some data (logs excluded), while a regular user should only be able to read it. This is where <strong>Role-Based Access Control (RBAC)</strong> comes in.</p>
<p><a target="_blank" href="https://firebase.google.com/">Firebase</a> makes this possible with <a target="_blank" href="https://firebase.google.com/docs/auth/admin/custom-claims">custom claims</a> and security rules. In this article, you’ll learn how to:</p>
<ul>
<li><p>Add custom claims to users with the Firebase Admin SDK.</p>
</li>
<li><p>Use Firebase Security Rules to enforce RBAC.</p>
</li>
<li><p>Test your rules with different roles.</p>
</li>
</ul>
<p>By the end, you’ll have a working setup where roles like <code>admin</code> and <code>user</code> are enforced directly in Firestore.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-step-1-understand-firebase-custom-claims">Step 1: Understand Firebase Custom Claims</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-2-assign-a-role-with-the-firebase-admin-sdk">Step 2: Assign a Role with the Firebase Admin SDK</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-3-write-and-enforce-firestore-security-rules">Step 3: Write and Enforce Firestore Security Rules</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-4-build-the-frontend-with-nextjs-and-firebase">Step 4: Build the Frontend with Next.js and Firebase</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-5-test-the-rbac-workflow">Step 5: Test the RBAC Workflow</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>To follow along, you should:</p>
<ul>
<li><p>Have a Firebase project set up with Authentication and Firestore enabled.</p>
</li>
<li><p>Be comfortable with JavaScript/Node.js.</p>
</li>
<li><p>Have the Firebase SDK and Admin SDK installed.</p>
</li>
</ul>
<p>If you’re new to Firebase, check out the <a target="_blank" href="https://firebase.google.com/docs/web/setup">official setup guide</a> before continuing.</p>
<h2 id="heading-step-1-understand-firebase-custom-claims">Step 1: Understand Firebase Custom Claims</h2>
<p>Firebase custom claims allow you to attach extra information (like a role) to a user’s authentication token. You set this information <strong>server-side</strong> using the Admin SDK. They are included in the user’s <code>request.auth.token</code>, and you can’t set them directly from the client (for security reasons).</p>
<p>Here’s an example: a user’s ID token might look like this after a claim is added:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"user_id"</span>: <span class="hljs-string">"abc123"</span>,
  <span class="hljs-attr">"email"</span>: <span class="hljs-string">"jane@example.com"</span>,
  <span class="hljs-attr">"role"</span>: <span class="hljs-string">"admin"</span>
}
</code></pre>
<p>In this example, the <code>role</code> field determines access privileges in your application. Firebase automatically includes this claim in the user’s ID token, so it can be securely validated both on the server and in Firestore rules.</p>
<h2 id="heading-step-2-assign-a-role-with-the-firebase-admin-sdk">Step 2: Assign a Role with the Firebase Admin SDK</h2>
<p>The Firebase Admin SDK allows you to manage users and assign roles securely from your backend (or through a script).</p>
<p>First, install the Admin SDK in a Node environment (not in your frontend app):</p>
<pre><code class="lang-bash">npm install firebase-admin
</code></pre>
<p>Then initialize it with your Firebase service account credentials:</p>
<pre><code class="lang-js"><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> serviceAccount = <span class="hljs-built_in">require</span>(<span class="hljs-string">"./service-account.json"</span>);

admin.initializeApp({
  <span class="hljs-attr">credential</span>: admin.credential.cert(serviceAccount),
});
</code></pre>
<p>To get your service-account.json file, navigate to your firebase settings &gt; project settings &gt; service account.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1760235082674/8f542b00-2246-4585-b267-f8cb663797ea.png" alt="an image showing the service account interface on firebase console" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Click on Generate private key, and it will automatically download the JSON file. You can rename the file or use it as it is.</p>
<p>You can now define a simple function to set a user’s role:</p>
<pre><code class="lang-js"><span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">setUserRole</span>(<span class="hljs-params">uid, role</span>) </span>{
  <span class="hljs-keyword">await</span> admin.auth().setCustomUserClaims(uid, { role });
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Role <span class="hljs-subst">${role}</span> assigned to user <span class="hljs-subst">${uid}</span>`</span>);
}
</code></pre>
<p>The <code>role</code> parameter can be anything you define, for example:</p>
<ul>
<li><p><code>"admin"</code>: Full read/write access.</p>
</li>
<li><p><code>"editor"</code>: Can create or modify limited content.</p>
</li>
<li><p><code>"user"</code>: Read-only access.</p>
</li>
</ul>
<p>The role you assign to a user depends on your app’s needs. In most applications, you’ll start simple, perhaps just <code>admin</code> and <code>user</code> and expand over time.</p>
<h3 id="heading-usage-example">Usage example:</h3>
<p>Once you’ve defined the function, call it with a user’s UID:</p>
<pre><code class="lang-js">setUserRole(<span class="hljs-string">"USER_UID_HERE"</span>, <span class="hljs-string">"admin"</span>);
</code></pre>
<p>This securely attaches a custom claim to the user.</p>
<p><strong>Note:</strong> The user must log out and log back in (or refresh their token) for the new claim to take effect.</p>
<h2 id="heading-step-3-write-firestore-security-rules-for-rbac">Step 3: Write Firestore Security Rules for RBAC</h2>
<p>Firestore <code>Security Rules</code> control how your data can be read or written. They are executed <strong>before</strong> any client request reaches your database, ensuring that your security logic isn’t bypassed.</p>
<p>Open your Firestore Rules (<code>firestore.rules</code>) and define role-based access like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1760234391658/9e9cf217-6291-4189-a135-8310c8905587.png" alt="image showing the firebase rules section" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<pre><code class="lang-js">rules_version = <span class="hljs-string">'2'</span>;
service cloud.firestore {
  match /databases/{database}/documents {

    match /posts/{postId} {
      <span class="hljs-comment">// Anyone logged in can read</span>
      allow read: <span class="hljs-keyword">if</span> request.auth != <span class="hljs-literal">null</span>;

      <span class="hljs-comment">// Only admins can write</span>
      allow write: <span class="hljs-keyword">if</span> request.auth.token.role == <span class="hljs-string">"admin"</span>;
    }
  }
}
</code></pre>
<p>Here’s what’s happening:</p>
<ul>
<li><p><code>request.auth != null</code>: ensures the user is logged in.</p>
</li>
<li><p><code>request.auth.token.role == "admin"</code>: Grants write access only to users with the admin role.</p>
</li>
</ul>
<p>You can expand this for multiple roles:</p>
<pre><code class="lang-js">allow write: <span class="hljs-keyword">if</span> request.auth.token.role <span class="hljs-keyword">in</span> [<span class="hljs-string">"admin"</span>, <span class="hljs-string">"editor"</span>];
</code></pre>
<h2 id="heading-quick-reference">Quick Reference</h2>
<p>Keep these points in mind when managing Firebase RBAC:</p>
<ul>
<li><p><strong>Keep your roles simple</strong> (for example, <code>admin</code>, <code>editor</code>, <code>user</code>). Don’t overcomplicate.</p>
</li>
<li><p><strong>Don’t store roles in Firestore documents</strong>. Enforce via custom claims instead.</p>
</li>
<li><p><strong>Always test rules</strong> locally before deploying.</p>
</li>
<li><p>Remember that users must <strong>refresh their tokens</strong> after claims are updated.</p>
</li>
</ul>
<h2 id="heading-step-4-build-the-frontend-with-nextjs-and-firebase">Step 4: Build the Frontend with Next.js and Firebase</h2>
<p>Let’s bring this to life with a <a target="_blank" href="https://github.com/CodeLeom/firebase-rbac">working demo</a> using Next.js and Firebase.</p>
<pre><code class="lang-bash">firebase-rbac/
├── firebase-admin-scripts/       <span class="hljs-comment"># Server-side scripts for setting user roles</span>
│   ├── assignRole.js             <span class="hljs-comment"># Uses Firebase Admin SDK to assign custom claims</span>
│   ├── .env                      <span class="hljs-comment"># Contains service account path and test UID</span>
│   └── fir-rbac-...json          <span class="hljs-comment"># Firebase Admin SDK service account json file</span>
│
├── src/
│   ├── app/
│   │   ├── page.js               <span class="hljs-comment"># Main Next.js page for login + post display</span>
│   │   ├── layout.js             <span class="hljs-comment"># Global layout</span>
│   │   └── globals.css           <span class="hljs-comment"># Tailwind global styles</span>
│   └── lib/
│       └── firebase.js           <span class="hljs-comment"># Firebase client initialization</span>
│
├── .env.local                    <span class="hljs-comment"># Firebase web config (NEXT_PUBLIC_ variables)</span>
├── package.json
└── README.md
</code></pre>
<p>In your <code>.env.local</code>, complete these variables with your Firebase project config information:</p>
<pre><code class="lang-bash">NEXT_PUBLIC_FIREBASE_API_KEY=your-api-key
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=your-project-id.firebaseapp.com
NEXT_PUBLIC_FIREBASE_PROJECT_ID=your-project-id
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=your-project-id.appspot.com
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=your-sender-id
NEXT_PUBLIC_FIREBASE_APP_ID=your-app-id
</code></pre>
<p>Firebase Initialization (src/lib/firebase.js):</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { initializeApp, getApps, getApp } <span class="hljs-keyword">from</span> <span class="hljs-string">"firebase/app"</span>;
<span class="hljs-keyword">import</span> { getAuth } <span class="hljs-keyword">from</span> <span class="hljs-string">"firebase/auth"</span>;
<span class="hljs-keyword">import</span> { getFirestore } <span class="hljs-keyword">from</span> <span class="hljs-string">"firebase/firestore"</span>;

<span class="hljs-keyword">const</span> firebaseConfig = {
  <span class="hljs-attr">apiKey</span>: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
  <span class="hljs-attr">authDomain</span>: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
  <span class="hljs-attr">projectId</span>: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
  <span class="hljs-attr">storageBucket</span>: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
  <span class="hljs-attr">messagingSenderId</span>: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
  <span class="hljs-attr">appId</span>: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
};

<span class="hljs-keyword">const</span> app = initializeApp(firebaseConfig);
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> auth = getAuth(app);
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> db = getFirestore(app);
</code></pre>
<p>Demo Component (src/app/page.js):</p>
<p>This component lets you log in, view posts, and, if you’re an admin, create new posts.</p>
<pre><code class="lang-javascript"><span class="hljs-string">"use client"</span>;

<span class="hljs-keyword">import</span> { useState, useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { auth, db } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/lib/firebase"</span>;
<span class="hljs-keyword">import</span> {
  signInWithEmailAndPassword,
  onAuthStateChanged,
  signOut,
} <span class="hljs-keyword">from</span> <span class="hljs-string">"firebase/auth"</span>;
<span class="hljs-keyword">import</span> { collection, getDocs, addDoc } <span class="hljs-keyword">from</span> <span class="hljs-string">"firebase/firestore"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Page</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [user, setUser] = useState(<span class="hljs-literal">null</span>);
  <span class="hljs-keyword">const</span> [email, setEmail] = useState(<span class="hljs-string">""</span>);
  <span class="hljs-keyword">const</span> [password, setPassword] = useState(<span class="hljs-string">""</span>);
  <span class="hljs-keyword">const</span> [posts, setPosts] = useState([]);
  <span class="hljs-keyword">const</span> [newPost, setNewPost] = useState(<span class="hljs-string">""</span>);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> unsubscribe = onAuthStateChanged(auth, <span class="hljs-keyword">async</span> (u) =&gt; {
      setUser(u);
      <span class="hljs-keyword">if</span> (u) <span class="hljs-keyword">await</span> loadPosts();
      <span class="hljs-keyword">else</span> setPosts([]);
    });
    <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> unsubscribe();
  }, []);

  <span class="hljs-keyword">const</span> loadPosts = <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">const</span> snapshot = <span class="hljs-keyword">await</span> getDocs(collection(db, <span class="hljs-string">"posts"</span>));
    setPosts(snapshot.docs.map(<span class="hljs-function">(<span class="hljs-params">doc</span>) =&gt;</span> ({ <span class="hljs-attr">id</span>: doc.id, ...doc.data() })));
  };

  <span class="hljs-keyword">const</span> handleLogin = <span class="hljs-keyword">async</span> (e) =&gt; {
    e.preventDefault();
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">await</span> signInWithEmailAndPassword(auth, email, password);
      alert(<span class="hljs-string">"Logged in!"</span>);
      setEmail(<span class="hljs-string">""</span>);
      setPassword(<span class="hljs-string">""</span>);
    } <span class="hljs-keyword">catch</span> (error) {
      <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Login failed:"</span>, error.message);
      alert(<span class="hljs-string">"Login failed: "</span> + error.message);
    }
  };

  <span class="hljs-keyword">const</span> handleLogout = <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">await</span> signOut(auth);
    setUser(<span class="hljs-literal">null</span>);
  };

  <span class="hljs-keyword">const</span> handleAddPost = <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">await</span> addDoc(collection(db, <span class="hljs-string">"posts"</span>), { <span class="hljs-attr">text</span>: newPost });
      setNewPost(<span class="hljs-string">""</span>);
      <span class="hljs-keyword">await</span> loadPosts();
      alert(<span class="hljs-string">"New Post added!"</span>);
    } <span class="hljs-keyword">catch</span> (e) {
      alert(<span class="hljs-string">"Opps!! Only admins can add posts."</span>);
      <span class="hljs-built_in">console</span>.error(e.message);
    }
  };

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">main</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"min-h-screen flex flex-col items-center justify-center bg-gray-900 text-gray-100 px-4"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"w-full max-w-md bg-gray-800 rounded-2xl shadow-lg p-8 space-y-6"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">h1</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-2xl font-bold text-center text-indigo-400"</span>&gt;</span>
          Firebase RBAC Demo (Next.js)
        <span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>

        {/* Login Form */}
        {!user ? (
          <span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">onSubmit</span>=<span class="hljs-string">{handleLogin}</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"space-y-4"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"block text-gray-300 text-sm mb-1"</span>&gt;</span>Email<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">input</span>
                <span class="hljs-attr">type</span>=<span class="hljs-string">"email"</span>
                <span class="hljs-attr">value</span>=<span class="hljs-string">{email}</span>
                <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setEmail(e.target.value)}
                placeholder="you@example.com"
                required
                className="w-full px-3 py-2 rounded-md bg-gray-700 border border-gray-600 text-gray-100 focus:outline-none focus:ring-2 focus:ring-indigo-400"
              /&gt;
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"block text-gray-300 text-sm mb-1"</span>&gt;</span>
                Password
              <span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">input</span>
                <span class="hljs-attr">type</span>=<span class="hljs-string">"password"</span>
                <span class="hljs-attr">value</span>=<span class="hljs-string">{password}</span>
                <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setPassword(e.target.value)}
                placeholder="••••••••"
                required
                className="w-full px-3 py-2 rounded-md bg-gray-700 border border-gray-600 text-gray-100 focus:outline-none focus:ring-2 focus:ring-indigo-400"
              /&gt;
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">button</span>
              <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span>
              <span class="hljs-attr">className</span>=<span class="hljs-string">"w-full px-6 py-2 rounded-lg bg-indigo-600 hover:bg-indigo-500 transition font-medium text-white"</span>
            &gt;</span>
              Login
            <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
        ) : (
          <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"space-y-6"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex flex-col items-center"</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-gray-300 mb-2"</span>&gt;</span>
                Logged in as{" "}
                <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"font-semibold text-indigo-400"</span>&gt;</span>
                  {user.email}
                <span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
              <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">button</span>
                <span class="hljs-attr">onClick</span>=<span class="hljs-string">{handleLogout}</span>
                <span class="hljs-attr">className</span>=<span class="hljs-string">"text-sm text-red-400 hover:text-red-300 underline"</span>
              &gt;</span>
                Logout
              <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">section</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"border-t border-gray-700 pt-4"</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">h2</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-lg font-semibold text-indigo-300 mb-3"</span>&gt;</span>
                Posts
              <span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>

              {posts.length &gt; 0 ? (
                <span class="hljs-tag">&lt;<span class="hljs-name">ul</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"space-y-2"</span>&gt;</span>
                  {posts.map((p) =&gt; (
                    <span class="hljs-tag">&lt;<span class="hljs-name">li</span>
                      <span class="hljs-attr">key</span>=<span class="hljs-string">{p.id}</span>
                      <span class="hljs-attr">className</span>=<span class="hljs-string">"bg-gray-700 rounded-md px-3 py-2 text-gray-200"</span>
                    &gt;</span>
                      {p.text}
                    <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
                  ))}
                <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
              ) : (
                <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-gray-400 italic"</span>&gt;</span>No posts yet.<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
              )}

              <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"mt-4 flex items-center gap-2"</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">input</span>
                  <span class="hljs-attr">value</span>=<span class="hljs-string">{newPost}</span>
                  <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setNewPost(e.target.value)}
                  placeholder="New post"
                  className="flex-1 px-3 py-2 rounded-md bg-gray-700 border border-gray-600 text-gray-100 focus:outline-none focus:ring-2 focus:ring-indigo-400"
                /&gt;
                <span class="hljs-tag">&lt;<span class="hljs-name">button</span>
                  <span class="hljs-attr">onClick</span>=<span class="hljs-string">{handleAddPost}</span>
                  <span class="hljs-attr">className</span>=<span class="hljs-string">"px-4 py-2 rounded-md bg-indigo-600 hover:bg-indigo-500 transition font-medium text-white"</span>
                &gt;</span>
                  Add
                <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
              <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">section</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        )}
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">footer</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"mt-8 text-gray-500 text-sm"</span>&gt;</span>
        Built with Next.js + Firebase | <span class="hljs-symbol">&amp;copy;</span> FreeCodeCamp 2025
      <span class="hljs-tag">&lt;/<span class="hljs-name">footer</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span></span>
  );
}
</code></pre>
<h2 id="heading-step-5-test-the-rbac-workflow">Step 5: Test the RBAC Workflow</h2>
<p>Now that everything is set up, it’s time to test the entire Role-Based Access Control flow to ensure your rules and roles are working correctly.</p>
<h3 id="heading-enable-authentication">Enable Authentication</h3>
<p>Head over to your Firebase Console, select your project, and navigate to Authentication then Sign-in method. Select Add New Provider. Then enable Email/Password authentication. This will let you create and sign in with test users directly from your app.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1760441502551/7ce05b9b-51f2-4704-83ed-1b62bf22ef2c.png" alt="an image of authentication section on firebase" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h3 id="heading-configure-firestore-rules">Configure Firestore Rules</h3>
<p>Next, you’ll need to update the Firestore rules. Navigate to Firestore Database, located in the build drop-down. Once you’re there, click on Rules where you will be able to update the rules.</p>
<p>Replace the default rules with the RBAC rules you defined earlier. These rules ensure that only authenticated users can read data, and only admins can create or modify posts.</p>
<p>Then publish the updated version and you are good to go.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1760441655143/9a34a908-0692-4f84-92c4-7526aafdbd51.png" alt="9a34a908-0692-4f84-92c4-7526aafdbd51" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h3 id="heading-assign-a-role-to-a-user">Assign a Role to a User</h3>
<p>To test admin permissions, assign an admin role to one of your test users. Open your terminal, change into the firebase-admin-scripts directory, and run:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> firebase-admin-scripts
node assignRole.js
</code></pre>
<p>This executes the Admin SDK script that adds a custom claim to your test user. Once the role is set, you’ll get a message confirming that the <code>admin</code> role has been assigned to the specified user ID.</p>
<p>If the user is logged in already, the user must log out and log back in for the new role to take effect.</p>
<h3 id="heading-run-the-app">Run the App</h3>
<p>Now you can start your Next.js development server:</p>
<pre><code class="lang-bash">npm run dev
</code></pre>
<p>Visit <a target="_blank" href="http://localhost:3000">http://localhost:3000</a> in your browser. You should find the Firebase RBAC demo app.</p>
<h3 id="heading-verify-role-based-access">Verify Role-Based Access</h3>
<p>Try logging in as the user who was assigned the <strong>admin</strong> role. Once logged in, you should be able to create new posts successfully. Next, log in as a regular user. You’ll notice that you can view existing posts, but any attempt to add a new post will fail with a “Permission denied” alert.</p>
<p>If you see these behaviors, then your RBAC system is working as intended!</p>
<p>By enforcing permissions at the Firestore layer, you ensure that security is handled centrally and can’t be bypassed by manipulating the client-side code. This approach keeps your app secure and scalable, even as your roles and data grow more complex.</p>
<p>Next steps:</p>
<ul>
<li><p>Add more roles (like editor, and more as you wish).</p>
</li>
<li><p>Combine RBAC with document-level validation for fine-grained control.</p>
</li>
<li><p>Explore Firebase’s <a target="_blank" href="https://firebase.google.com/docs/rules/">security rules</a>.</p>
</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>You just learned a simple but important <strong>role-based access control (RBAC)</strong> functionality in Firebase. In this guide, we covered custom claims and how to set roles using the Admin SDK. You also learned how to enforce those roles in Firestore security rules.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build an Upload Service in Flutter Web with Firebase ]]>
                </title>
                <description>
                    <![CDATA[ Uploading files is one of the most common requirements in modern web applications. Whether it’s profile pictures, documents, or bulk uploads, users expect a smooth and reliable experience. With Flutter Web and Firebase Storage, you can implement this... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-an-upload-service-in-flutter-web-with-firebase/</link>
                <guid isPermaLink="false">68bb0031e67497d3bf64ec82</guid>
                
                    <category>
                        <![CDATA[ Flutter ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Dart ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Firebase ]]>
                    </category>
                
                    <category>
                        <![CDATA[ firebase Storage ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Atuoha Anthony ]]>
                </dc:creator>
                <pubDate>Fri, 05 Sep 2025 15:22:25 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1757085725508/3ff2d66d-5b3b-4784-904b-de7b464de41b.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Uploading files is one of the most common requirements in modern web applications. Whether it’s profile pictures, documents, or bulk uploads, users expect a smooth and reliable experience. With Flutter Web and Firebase Storage, you can implement this functionality in a clean and scalable way.</p>
<p>In this article, you’ll learn how to build a reusable upload service that:</p>
<ol>
<li><p>Uploads single and multiple files to Firebase Storage</p>
</li>
<li><p>Returns file download URLs</p>
</li>
<li><p>Uses Dependency Injection (DI) with <code>injectable</code> to keep the code modular, testable, and easy to maintain</p>
</li>
</ol>
<p>By the end, you will have a production-ready upload service for your Flutter Web project.</p>
<h3 id="heading-table-of-contents">Table of Contents:</h3>
<ol>
<li><p><a class="post-section-overview" href="#heading-why-file-uploads-matter-in-flutter-web">Why File Uploads Matter in Flutter Web</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-upload-flow-overview">Upload Flow Overview</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-define-the-upload-data-model-and-service-interface">How to Define the Upload Data Model and Service Interface</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-implement-the-upload-service">How to Implement the Upload Service</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-handle-errors">How to Handle Errors</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-dependency-injection-with-injectable">Dependency Injection with injectable</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-use-the-upload-service">How to Use the Upload Service</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-best-practices">Best Practices</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-references">References</a></p>
</li>
</ol>
<h2 id="heading-why-file-uploads-matter-in-flutter-web">Why File Uploads Matter in Flutter Web</h2>
<p>When building for the web, users often expect features like uploading a profile picture, submitting documents, or sharing media. Unlike mobile, the web environment requires handling files via browser APIs, which then need to be integrated with backend services like Firebase for persistence.</p>
<h2 id="heading-upload-flow-overview">Upload Flow Overview</h2>
<p>Here’s a high-level look at how the upload process works:</p>
<ol>
<li><p>The user selects a file or image using a browser file picker.</p>
</li>
<li><p>Flutter reads the file as a <code>Uint8List</code>.</p>
</li>
<li><p>The file is uploaded to Firebase Storage.</p>
</li>
<li><p>A download URL is generated and stored in Firestore (or used directly).</p>
</li>
</ol>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before starting, ensure you have the following:</p>
<ol>
<li><p>A Flutter Web project</p>
<pre><code class="lang-bash"> flutter config --enable-web
 flutter create my_web_project
 <span class="hljs-built_in">cd</span> my_web_project
</code></pre>
</li>
<li><p>Firebase set up in your Flutter app: Follow <a target="_blank" href="https://firebase.google.com/docs/flutter/setup?platform=web">Add Firebase to your Flutter app (Web)</a> and include the Firebase SDK snippet in <code>index.html</code>.</p>
</li>
<li><p>Firebase Storage enabled in the Firebase Console: Go to Build &gt; Storage &gt; Get Started and allow read/write access for testing. Example rules:</p>
<pre><code class="lang-json"> service firebase.storage {
   match /b/{bucket}/o {
     match /{allPaths=**} {
       allow read, write: if <span class="hljs-literal">true</span>;
     }
   }
 }
</code></pre>
<p> Don’t use these rules in production.</p>
</li>
<li><p>Required dependencies in your <code>pubspec.yaml</code>:</p>
<pre><code class="lang-yaml"> <span class="hljs-attr">dependencies:</span>
   <span class="hljs-attr">firebase_core:</span> <span class="hljs-string">^3.13.0</span>
   <span class="hljs-attr">firebase_storage:</span> <span class="hljs-string">^12.4.2</span>
   <span class="hljs-attr">injectable:</span> <span class="hljs-string">^2.3.2</span>
   <span class="hljs-attr">get_it:</span> <span class="hljs-string">^8.0.3</span>

 <span class="hljs-attr">dev_dependencies:</span>
   <span class="hljs-attr">build_runner:</span> <span class="hljs-string">^2.4.13</span>
   <span class="hljs-attr">injectable_generator:</span> <span class="hljs-string">^2.4.1</span>
</code></pre>
<p> Run <code>flutter pub get</code> to install.</p>
</li>
</ol>
<h2 id="heading-how-to-define-the-upload-data-model-and-service-interface">How to Define the Upload Data Model and Service Interface</h2>
<p>We begin with a data model to represent the file and a service interface to define the upload contract.</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'dart:typed_data'</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UploadData</span> </span>{
  <span class="hljs-keyword">final</span> Uint8List fileData;   <span class="hljs-comment">// File in binary format</span>
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> folderName;    <span class="hljs-comment">// Folder path in Firebase Storage</span>
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> fileName;      <span class="hljs-comment">// File name</span>

  <span class="hljs-keyword">const</span> UploadData({
    <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.fileData,
    <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.fileName,
    <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.folderName,
  });
}
</code></pre>
<p>Next, create an abstract service that defines what the upload logic should do.</p>
<pre><code class="lang-dart"><span class="hljs-keyword">abstract</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">IUploadService</span> </span>{
  Future&lt;<span class="hljs-built_in">String</span>&gt; uploadDoc({
    <span class="hljs-keyword">required</span> UploadData file,
  });

  Future&lt;<span class="hljs-built_in">List</span>&lt;<span class="hljs-built_in">String</span>&gt;&gt; uploadMultipleDoc({
    <span class="hljs-keyword">required</span> <span class="hljs-built_in">List</span>&lt;UploadData&gt; files,
  });
}
</code></pre>
<p>Here’s what’s happening in this code:</p>
<ul>
<li><p><code>uploadDoc</code>: Uploads one file and returns its download URL</p>
</li>
<li><p><code>uploadMultipleDoc</code>: Uploads multiple files in parallel and returns a list of URLs</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756777666277/35243f4a-4f0f-40b5-92b1-f1f1a5b3474e.png" alt="Diagram: Interface Design" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h2 id="heading-how-to-implement-the-upload-service">How to Implement the Upload Service</h2>
<p>Now let’s implement the upload logic with Firebase Storage.</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:firebase_storage/firebase_storage.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/foundation.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:injectable/injectable.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'i_upload_service.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'custom_error.dart'</span>;

<span class="hljs-meta">@LazySingleton</span>(<span class="hljs-keyword">as</span>: IUploadService)
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UploadService</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">IUploadService</span> </span>{
  <span class="hljs-keyword">final</span> FirebaseStorage firebaseStorage;

  UploadService({<span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.firebaseStorage});

  <span class="hljs-meta">@override</span>
  Future&lt;<span class="hljs-built_in">String</span>&gt; uploadDoc({<span class="hljs-keyword">required</span> UploadData file}) <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">var</span> storageRef = firebaseStorage.ref(<span class="hljs-string">'<span class="hljs-subst">${file.folderName}</span>/<span class="hljs-subst">${file.fileName}</span>'</span>);
      <span class="hljs-keyword">var</span> uploadTask = storageRef.putData(file.fileData);
      TaskSnapshot snapshot = <span class="hljs-keyword">await</span> uploadTask;
      <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> snapshot.ref.getDownloadURL();
    } <span class="hljs-keyword">on</span> FirebaseException <span class="hljs-keyword">catch</span> (e) {
      <span class="hljs-keyword">throw</span> CustomError(
        errorMsg: <span class="hljs-string">"Firebase upload failed: <span class="hljs-subst">${e.message}</span>"</span>,
        code: e.code,
        plugin: e.plugin,
      );
    } <span class="hljs-keyword">catch</span> (e) {
      <span class="hljs-keyword">if</span> (kDebugMode) <span class="hljs-built_in">print</span>(<span class="hljs-string">"Unexpected error: <span class="hljs-subst">$e</span>"</span>);
      <span class="hljs-keyword">rethrow</span>;
    }
  }

  <span class="hljs-meta">@override</span>
  Future&lt;<span class="hljs-built_in">List</span>&lt;<span class="hljs-built_in">String</span>&gt;&gt; uploadMultipleDoc({<span class="hljs-keyword">required</span> <span class="hljs-built_in">List</span>&lt;UploadData&gt; files}) <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> Future.wait(
      files.map((file) =&gt; uploadDoc(file: file)),
    );
  }
}
</code></pre>
<p>This code defines a <strong>service class</strong> for uploading documents to Firebase Storage in a Flutter app. Let’s break it down step by step so you see exactly what’s happening:</p>
<p><strong>1. Imports</strong></p>
<ol>
<li><p><code>firebase_storage</code>: provides Firebase Storage SDK for uploading and managing files.</p>
</li>
<li><p><code>flutter/foundation.dart</code>: gives access to constants like <code>kDebugMode</code> for debug logging.</p>
</li>
<li><p><code>injectable.dart</code>: enables dependency injection using the injectable + getIt package.</p>
</li>
<li><p><code>i_upload_service.dart</code>: defines the abstract contract/interface for the upload service.</p>
</li>
<li><p><code>custom_error.dart</code>: defines a custom error class to standardize error handling.</p>
</li>
</ol>
<p><strong>2. Dependency Injection Setup</strong></p>
<pre><code class="lang-dart"><span class="hljs-meta">@LazySingleton</span>(<span class="hljs-keyword">as</span>: IUploadService)
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UploadService</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">IUploadService</span> </span>{
</code></pre>
<ol>
<li><p><code>@LazySingleton(as: IUploadService)</code> registers <code>UploadService</code> as the implementation of <code>IUploadService</code>.</p>
</li>
<li><p>This means anywhere in the app where <code>IUploadService</code> is requested, getIt will provide an instance of <code>UploadService</code>.</p>
</li>
<li><p>It’s a singleton, so only one instance is created and reused across the app.</p>
</li>
</ol>
<p><strong>3. Constructor</strong></p>
<pre><code class="lang-dart"><span class="hljs-keyword">final</span> FirebaseStorage firebaseStorage;

UploadService({<span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.firebaseStorage});
</code></pre>
<ol>
<li><p>The class requires a <code>FirebaseStorage</code> instance, which will also be injected automatically.</p>
</li>
<li><p>This makes the service easier to test and replace.</p>
</li>
</ol>
<p><strong>4. Upload a Single File</strong></p>
<pre><code class="lang-dart"><span class="hljs-meta">@override</span>
Future&lt;<span class="hljs-built_in">String</span>&gt; uploadDoc({<span class="hljs-keyword">required</span> UploadData file}) <span class="hljs-keyword">async</span> {
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">var</span> storageRef = firebaseStorage.ref(<span class="hljs-string">'<span class="hljs-subst">${file.folderName}</span>/<span class="hljs-subst">${file.fileName}</span>'</span>);
    <span class="hljs-keyword">var</span> uploadTask = storageRef.putData(file.fileData);
    TaskSnapshot snapshot = <span class="hljs-keyword">await</span> uploadTask;
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> snapshot.ref.getDownloadURL();
  } <span class="hljs-keyword">on</span> FirebaseException <span class="hljs-keyword">catch</span> (e) {
    <span class="hljs-keyword">throw</span> CustomError(
      errorMsg: <span class="hljs-string">"Firebase upload failed: <span class="hljs-subst">${e.message}</span>"</span>,
      code: e.code,
      plugin: e.plugin,
    );
  } <span class="hljs-keyword">catch</span> (e) {
    <span class="hljs-keyword">if</span> (kDebugMode) <span class="hljs-built_in">print</span>(<span class="hljs-string">"Unexpected error: <span class="hljs-subst">$e</span>"</span>);
    <span class="hljs-keyword">rethrow</span>;
  }
}
</code></pre>
<p>What this code does:</p>
<ol>
<li><p>Creates a reference in Firebase Storage at the path <code>folderName/fileName</code></p>
</li>
<li><p>Uploads the raw file bytes (<code>file.fileData</code>) using <code>putData</code>.</p>
</li>
<li><p>Waits for the upload to complete and retrieves a <code>TaskSnapshot</code>.</p>
</li>
<li><p>From the snapshot, gets the download URL of the uploaded file and returns it.</p>
</li>
<li><p>If a <code>FirebaseException</code> occurs, it wraps the error inside a custom <code>CustomError</code>.</p>
</li>
<li><p>Any other unexpected error is logged (only in debug mode) and rethrown.</p>
</li>
</ol>
<p><strong>5. Upload Multiple Files</strong></p>
<pre><code class="lang-dart"><span class="hljs-meta">@override</span>
Future&lt;<span class="hljs-built_in">List</span>&lt;<span class="hljs-built_in">String</span>&gt;&gt; uploadMultipleDoc({<span class="hljs-keyword">required</span> <span class="hljs-built_in">List</span>&lt;UploadData&gt; files}) <span class="hljs-keyword">async</span> {
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> Future.wait(
    files.map((file) =&gt; uploadDoc(file: file)),
  );
}
</code></pre>
<p>What the code does:</p>
<ol>
<li><p>Accepts a list of <code>UploadData</code> objects.</p>
</li>
<li><p>For each file, it calls <code>uploadDoc</code> (the single upload function).</p>
</li>
<li><p><code>Future.wait</code> runs all uploads <strong>in parallel</strong>, waits for them to complete, and returns a list of download URLs.</p>
</li>
</ol>
<p>This class is a Firebase Storage upload service. It can upload single or multiple documents. It follows dependency injection principles for testability and scalability. It uses error handling with <code>CustomError</code> to provide cleaner error messages. Multiple uploads are executed in parallel for efficiency.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756776717488/16d18b9c-309c-43a3-b595-74dabe7d7b3c.png" alt="Upload Flow with Firebase Storage" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h2 id="heading-how-to-handle-errors">How to Handle Errors</h2>
<p>Instead of relying on raw <code>print</code> statements, it’s better to use a <strong>structured error class</strong>. A structured error class organizes all error information, like the message, code, and source, into a single object. This makes error handling consistent, reusable, and easy to manage. You can inspect, log, or display errors programmatically, which is much more maintainable than scattered prints.</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:equatable/equatable.dart'</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CustomError</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Equatable</span> </span>{
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> errorMsg;
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> code;
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> plugin;

  <span class="hljs-keyword">const</span> CustomError({
    <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.errorMsg,
    <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.code,
    <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.plugin,
  });

  <span class="hljs-meta">@override</span>
  <span class="hljs-built_in">List</span>&lt;<span class="hljs-built_in">Object?</span>&gt; <span class="hljs-keyword">get</span> props =&gt; [errorMsg, code, plugin];

  <span class="hljs-meta">@override</span>
  <span class="hljs-built_in">String</span> toString() {
    <span class="hljs-keyword">return</span> <span class="hljs-string">'CustomError(errorMsg: <span class="hljs-subst">$errorMsg</span>, code: <span class="hljs-subst">$code</span>, plugin: <span class="hljs-subst">$plugin</span>)'</span>;
  }
}
</code></pre>
<p>Why you should use this approach:</p>
<ul>
<li><p>Ensures consistency across the project.</p>
</li>
<li><p>Makes errors reusable anywhere in the app.</p>
</li>
<li><p>Allows programmatic handling (for example, act differently based on the error code).</p>
</li>
<li><p>Provides clear debugging information through <code>toString()</code>.</p>
</li>
<li><p>Scales well as your app grows.</p>
</li>
</ul>
<h2 id="heading-dependency-injection-with-injectable">Dependency Injection with injectable</h2>
<p>In a typical app, you might manually create service instances like <code>UploadService</code> or <code>FirebaseStorage</code> wherever you need them. But as your app grows, manually creating and passing dependencies becomes messy, error-prone, and hard to test.</p>
<p>This is where <strong>Dependency Injection</strong> (DI) comes in. DI allows you to declare dependencies once, and let a framework handle creating and providing them wherever they’re needed. The <code>injectable</code> package in Flutter works with <code>getIt</code> to automate this process.</p>
<p>Instead of creating <code>UploadService</code> manually, you configure it with injectable so that your app automatically gets the correct instance when needed, following the singleton or lazy-loading patterns.</p>
<h3 id="heading-step-1-annotate-your-service">Step 1: Annotate your service</h3>
<pre><code class="lang-dart"><span class="hljs-meta">@LazySingleton</span>(<span class="hljs-keyword">as</span>: IUploadService)
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UploadService</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">IUploadService</span> </span>{
  <span class="hljs-comment">// your upload logic here</span>
}
</code></pre>
<p><code>@LazySingleton(as: IUploadService)</code> tells injectable:</p>
<ul>
<li><p><strong>Lazy</strong>: Only create the instance when it’s first used.</p>
</li>
<li><p><strong>Singleton</strong>: Reuse the same instance throughout the app.</p>
</li>
<li><p><strong>as: IUploadService</strong>: Expose the service via its interface, making testing and swapping implementations easier.</p>
</li>
</ul>
<h3 id="heading-step-2-run-the-generator">Step 2: Run the generator</h3>
<pre><code class="lang-dart">flutter pub run build_runner build
</code></pre>
<p>This command generates code that wiring all your injectable dependencies together, so you don’t have to manually instantiate them.</p>
<h3 id="heading-step-3-create-an-injectable-module">Step 3: Create an injectable module</h3>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:firebase_storage/firebase_storage.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:injectable/injectable.dart'</span>;

<span class="hljs-meta">@module</span>
<span class="hljs-keyword">abstract</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">InjectableModule</span> </span>{
  <span class="hljs-meta">@lazySingleton</span>
  FirebaseStorage <span class="hljs-keyword">get</span> firebaseStorage =&gt; FirebaseStorage.instance;
}
</code></pre>
<p>This code is setting up dependency injection for <code>FirebaseStorage</code> using the <code>injectable</code> package. Let me break it down:</p>
<ol>
<li><p><code>@module</code>: The <code>@module</code> annotation tells <code>injectable</code> that this class will act as a provider of external dependencies (things you don’t create manually but get from libraries, SDKs, or APIs).</p>
<p> In this case, <code>FirebaseStorage</code> is coming from the Firebase SDK, so you don’t construct it yourself. You just get an instance from the SDK.</p>
</li>
<li><p><code>abstract class InjectableModule</code>: This is a special module class that contains dependency definitions. Since it’s abstract, it won’t be instantiated directly. Instead, <code>injectable</code> generates code to handle the injection.</p>
</li>
<li><p><code>@lazySingleton</code>: This annotation tells <code>injectable</code> that the dependency should be created <strong>only once</strong> and reused throughout the app (singleton pattern).</p>
<ul>
<li><p><strong>Lazy</strong> means it won’t be created until it’s actually needed.</p>
</li>
<li><p><strong>Singleton</strong> means the same instance will be reused everywhere after the first creation.</p>
</li>
</ul>
</li>
<li><p><code>FirebaseStorage get firebaseStorage =&gt; FirebaseStorage.instance;</code>: This line defines what dependency to provide. Here it’s saying:</p>
<ul>
<li><p>Whenever something in the app needs a <code>FirebaseStorage</code> instance, inject <code>FirebaseStorage.instance</code>.</p>
</li>
<li><p>This way, you don’t manually create or pass around <code>FirebaseStorage</code> yourself – <code>injectable</code> plus <code>getIt</code> handle that automatically.</p>
</li>
</ul>
</li>
</ol>
<p>In practice, this ensures that everywhere in your app where you need <code>FirebaseStorage</code>, you can simply inject it via constructor injection (for example, in your <code>UploadService</code>) without manually instantiating it.</p>
<h3 id="heading-step-4-resolve-the-service-anywhere">Step 4: Resolve the service anywhere</h3>
<pre><code class="lang-dart"><span class="hljs-keyword">final</span> uploadService = getIt&lt;IUploadService&gt;();
</code></pre>
<h4 id="heading-why-we-do-this"><strong>Why we do this</strong></h4>
<p>By using injectable:</p>
<ol>
<li><p>You stop manually instantiating dependencies everywhere.</p>
</li>
<li><p>Your services are easier to test, because you can swap implementations via interfaces.</p>
</li>
<li><p>You ensure singleton patterns and lazy loading without extra boilerplate.</p>
</li>
<li><p>Your app becomes more maintainable, especially as it grows.</p>
</li>
</ol>
<p><strong>In practice:</strong><br>Anywhere in your app where <code>UploadService</code> needs <code>FirebaseStorage</code>, you just inject it via the constructor:</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UploadService</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">IUploadService</span> </span>{
  <span class="hljs-keyword">final</span> FirebaseStorage _firebaseStorage;

  UploadService(<span class="hljs-keyword">this</span>._firebaseStorage);

  <span class="hljs-comment">// Use _firebaseStorage here</span>
}
</code></pre>
<p>Injectable + getIt takes care of providing the correct <code>_firebaseStorage</code> instance automatically.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756776761337/7a32fc39-9147-4e78-ba82-150074cdb066.png" alt="Dependency Injection with getIt &amp; injectable" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h2 id="heading-how-to-use-the-upload-service">How to Use the Upload Service</h2>
<p>The <strong>Upload Service</strong> is a modular, reusable service in your app that handles uploading files to Firebase Storage. By using this service, you abstract away direct Firebase interactions, keep your code clean, and leverage dependency injection to access the service anywhere in your app.</p>
<p>The Upload Service provides several options:</p>
<ul>
<li><p><strong>Single file upload</strong> – Upload one file at a time and get its download URL.</p>
</li>
<li><p><strong>Multiple file upload</strong> – Upload a batch of files in one go and receive a list of download URLs.</p>
</li>
<li><p><strong>Error handling</strong> – Any issues during upload (like network errors or permission problems) are caught and can be handled gracefully.</p>
</li>
</ul>
<p>Below, we’ll go step by step through how to use these options in practice.</p>
<h3 id="heading-example-upload-a-single-file"><strong>Example: Upload a single file.</strong></h3>
<pre><code class="lang-dart">Future&lt;<span class="hljs-keyword">void</span>&gt; uploadFile(Uint8List fileData) <span class="hljs-keyword">async</span> {
  <span class="hljs-keyword">final</span> file = UploadData(
    fileData: fileData,
    fileName: <span class="hljs-string">'example.txt'</span>,
    folderName: <span class="hljs-string">'documents'</span>,
  );

  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">final</span> uploadService = getIt&lt;IUploadService&gt;();
    <span class="hljs-keyword">final</span> downloadUrl = <span class="hljs-keyword">await</span> uploadService.uploadDoc(file: file);
    <span class="hljs-built_in">print</span>(<span class="hljs-string">'Uploaded successfully: <span class="hljs-subst">$downloadUrl</span>'</span>);
  } <span class="hljs-keyword">catch</span> (e) {
    <span class="hljs-built_in">print</span>(<span class="hljs-string">'Upload failed: <span class="hljs-subst">$e</span>'</span>);
  }
}
</code></pre>
<p>This function <code>uploadFile</code> is a wrapper that prepares a file for upload and delegates the actual uploading to your <code>UploadService</code> via dependency injection. Let me break it down step by step:</p>
<pre><code class="lang-dart">Future&lt;<span class="hljs-keyword">void</span>&gt; uploadFile(Uint8List fileData) <span class="hljs-keyword">async</span> {
  <span class="hljs-keyword">final</span> file = UploadData(
    fileData: fileData,
    fileName: <span class="hljs-string">'example.txt'</span>,
    folderName: <span class="hljs-string">'documents'</span>,
  );
</code></pre>
<ol>
<li><p>First, it takes a file as raw bytes (<code>Uint8List fileData</code>).</p>
</li>
<li><p>Then it wraps this data in an <code>UploadData</code> object, giving it a <code>fileName</code> (<code>example.txt</code>) and a <code>folderName</code> (<code>documents</code>). This essentially creates metadata about the file, so your upload service knows what to call it and where to store it in Firebase Storage.</p>
</li>
</ol>
<pre><code class="lang-dart">  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">final</span> uploadService = getIt&lt;IUploadService&gt;();
    <span class="hljs-keyword">final</span> downloadUrl = <span class="hljs-keyword">await</span> uploadService.uploadDoc(file: file);
    <span class="hljs-built_in">print</span>(<span class="hljs-string">'Uploaded successfully: <span class="hljs-subst">$downloadUrl</span>'</span>);
  } <span class="hljs-keyword">catch</span> (e) {
    <span class="hljs-built_in">print</span>(<span class="hljs-string">'Upload failed: <span class="hljs-subst">$e</span>'</span>);
  }
}
</code></pre>
<ol start="3">
<li><p>Next, it retrieves the <code>IUploadService</code> instance using <code>getIt</code> (your dependency injection container). Thanks to the binding you defined earlier (<code>UploadService</code> registered as <code>IUploadService</code>), <code>getIt</code> knows to give you the correct implementation.</p>
</li>
<li><p>It calls <code>uploadService.uploadDoc(file: file)</code> which triggers the actual upload to Firebase Storage. If successful, Firebase returns a download URL of the uploaded file.</p>
</li>
<li><p>The function then prints out:</p>
<ul>
<li><p><code>"Uploaded successfully: &lt;downloadUrl&gt;"</code> if the upload worked.</p>
</li>
<li><p><code>"Upload failed: &lt;error&gt;"</code> if an error occurred (for example, no internet or Firebase permission issues).</p>
</li>
</ul>
</li>
</ol>
<p>In simple terms:</p>
<ol>
<li><p><strong>Input</strong>: Raw file data (bytes).</p>
</li>
<li><p><strong>Process</strong>: Wraps it in an <code>UploadData</code> object → sends it to Firebase via <code>UploadService</code>.</p>
</li>
<li><p><strong>Output</strong>: Prints the public download URL if upload succeeds, or prints an error message if it fails.</p>
</li>
</ol>
<h3 id="heading-example-upload-multiple-files"><strong>Example: Upload multiple files.</strong></h3>
<pre><code class="lang-dart">Future&lt;<span class="hljs-keyword">void</span>&gt; uploadMultiple(<span class="hljs-built_in">List</span>&lt;Uint8List&gt; filesData) <span class="hljs-keyword">async</span> {
  <span class="hljs-keyword">final</span> uploadService = getIt&lt;IUploadService&gt;();

  <span class="hljs-keyword">final</span> files = filesData.map((data) =&gt; UploadData(
    fileData: data,
    fileName: <span class="hljs-string">'<span class="hljs-subst">${DateTime.now().millisecondsSinceEpoch}</span>.txt'</span>,
    folderName: <span class="hljs-string">'batch_docs'</span>,
  )).toList();

  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">final</span> urls = <span class="hljs-keyword">await</span> uploadService.uploadMultipleDoc(files: files);
    <span class="hljs-built_in">print</span>(<span class="hljs-string">'All files uploaded: <span class="hljs-subst">$urls</span>'</span>);
  } <span class="hljs-keyword">catch</span> (e) {
    <span class="hljs-built_in">print</span>(<span class="hljs-string">'Batch upload failed: <span class="hljs-subst">$e</span>'</span>);
  }
}
</code></pre>
<p>This function handles batch uploading of multiple files to Firebase Storage using the <code>IUploadService</code>. Let’s break it down step by step:</p>
<h4 id="heading-1-access-the-upload-service">1. Access the upload service</h4>
<pre><code class="lang-dart"><span class="hljs-keyword">final</span> uploadService = getIt&lt;IUploadService&gt;();
</code></pre>
<p>Here, <code>getIt</code> retrieves the registered <code>IUploadService</code> instance through dependency injection. This service abstracts all the logic of uploading files, so you don’t deal with Firebase APIs directly in this method.</p>
<h4 id="heading-2-prepare-the-list-of-files">2. Prepare the list of files</h4>
<pre><code class="lang-dart"><span class="hljs-keyword">final</span> files = filesData.map((data) =&gt; UploadData(
  fileData: data,
  fileName: <span class="hljs-string">'<span class="hljs-subst">${DateTime.now().millisecondsSinceEpoch}</span>.txt'</span>,
  folderName: <span class="hljs-string">'batch_docs'</span>,
)).toList();
</code></pre>
<p><code>filesData</code> is a list of raw file contents (<code>Uint8List</code>). For each file in the list, it creates an <code>UploadData</code> object.</p>
<p>The filename is generated dynamically using the current timestamp (<code>DateTime.now().millisecondsSinceEpoch</code>), ensuring each file has a unique name.</p>
<p>All files are placed in the <code>"batch_docs"</code> folder in Firebase Storage. This way, you have a structured list of files ready for uploading.</p>
<h4 id="heading-3-upload-multiple-file-mechanism">3. Upload Multiple File Mechanism</h4>
<pre><code class="lang-dart"><span class="hljs-keyword">final</span> urls = <span class="hljs-keyword">await</span> uploadService.uploadMultipleDoc(files: files);
</code></pre>
<p>The <code>uploadService</code> is asked to upload all files in one go using its <code>uploadMultipleDoc</code> method. It uploads each file to Firebase Storage. Once done, it returns a list of download URLs, one for each uploaded file.</p>
<h4 id="heading-4-handle-success-or-failure">4. Handle success or failure</h4>
<pre><code class="lang-dart"><span class="hljs-built_in">print</span>(<span class="hljs-string">'All files uploaded: <span class="hljs-subst">$urls</span>'</span>);
</code></pre>
<p>On success, it prints out the URLs of all uploaded files (so you can later use them, for example, to display or share the documents).</p>
<pre><code class="lang-dart"><span class="hljs-built_in">print</span>(<span class="hljs-string">'Batch upload failed: <span class="hljs-subst">$e</span>'</span>);
</code></pre>
<p>If something goes wrong, it catches the exception and logs the error message.</p>
<p>In short, this function takes multiple raw files, wraps them into <code>UploadData</code> objects, uploads them all to Firebase Storage using the service layer, and prints the resulting download URLs.</p>
<h2 id="heading-best-practices">Best Practices</h2>
<ol>
<li><p>Validate file size before uploading to avoid oversized files.</p>
</li>
<li><p>Restrict file types (for example, only <code>image/*</code>) to improve security.</p>
</li>
<li><p>Store metadata (like user ID, timestamp) in Firestore along with the download URL.</p>
</li>
<li><p>Use unique paths (<code>uploads/userId/filename</code>) to prevent collisions.</p>
</li>
</ol>
<h2 id="heading-conclusion">Conclusion</h2>
<p>You now have a reusable and modular upload service for Flutter Web that supports single and multiple file uploads, handles errors in a structured way, and uses Dependency Injection for clean architecture.</p>
<p>This foundation makes it easy to extend the service further, for example by adding file deletion, upload progress tracking, or authenticated uploads.</p>
<h3 id="heading-references">References</h3>
<ol>
<li><p><a target="_blank" href="https://firebase.google.com/docs/storage/flutter/start">Firebase Storage for Flutter</a></p>
</li>
<li><p><a target="_blank" href="https://pub.dev/documentation/firebase_storage/latest/firebase_storage/firebase_storage-library.html">Firebase Storage API Reference</a></p>
</li>
<li><p><a target="_blank" href="https://pub.dev/packages/injectable">injectable Package</a></p>
</li>
<li><p><a target="_blank" href="https://pub.dev/packages/get_it">get_it Package</a></p>
</li>
</ol>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Set Up Firebase Crashlytics in a Flutter App (iOS and Android) ]]>
                </title>
                <description>
                    <![CDATA[ When you’re building mobile applications, one of the biggest challenges you might face is ensuring stability in real-world usage. No matter how much testing you do, unexpected crashes are bound to occur. This is where Firebase Crashlytics becomes an ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-set-up-firebase-crashlytics-in-a-flutter-app-ios-and-android/</link>
                <guid isPermaLink="false">68a6656e9437c0afc5323dc1</guid>
                
                    <category>
                        <![CDATA[ firebase-crashlytics ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Flutter ]]>
                    </category>
                
                    <category>
                        <![CDATA[ flutter-aware ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Firebase ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Atuoha Anthony ]]>
                </dc:creator>
                <pubDate>Thu, 21 Aug 2025 00:16:46 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1755735394033/aa70989a-2653-4f36-a8bf-fbf953d09512.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>When you’re building mobile applications, one of the biggest challenges you might face is ensuring stability in real-world usage. No matter how much testing you do, unexpected crashes are bound to occur.</p>
<p>This is where <strong>Firebase Crashlytics</strong> becomes an essential tool. Crashlytics is a lightweight, real-time crash reporter that helps you understand why your app is crashing and how widespread the problem is among users. With this knowledge, you can fix bugs faster and improve your app’s reliability.</p>
<p>In this article, we’ll walk through setting up Firebase Crashlytics in a Flutter app for both iOS and Android platforms. Along the way, you’ll learn not only how to integrate Crashlytics, but also the reasoning behind each step, so you fully understand how it works.</p>
<h3 id="heading-table-of-contents">Table of Contents:</h3>
<ol>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-set-up-the-flutter-projhttpsconsolefirebasegooglecomect">Set Up the Flutter Project</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-connect-flutter-to-firebase">Connect Flutter to Firebase</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-add-the-required-dependencies">Add the Required Dependencies</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-initialize-crashlytics-in-maindart">Initialize Crashlytics in main.dart</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-build-a-simple-test-screen">Build a Simple Test Screen</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-run-and-test-the-app">Run and Test the App</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-understanding-the-crashlytics-dashboard">Understanding the Crashlytics Dashboard</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-advanced-firebase-crashlytics-in-flutter-going-beyond-the-basics">Advanced Firebase Crashlytics in Flutter: Going Beyond the Basics</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-how-to-log-non-fatal-errors">How to Log Non-Fatal Errors</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-add-custom-keys-for-context">How to Add Custom Keys for Context</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-log-custom-events-and-breadcrumbs">How to Log Custom Events and Breadcrumbs</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-associate-crashes-with-users">How to Associate Crashes with Users</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-ensure-proper-symbolication">How to Ensure Proper Symbolication</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-controll-data-collection">How to Controll Data Collection</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-best-practices-for-production">Best Practices for Production</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-references">References</a></p>
</li>
</ol>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before jumping into the setup, make sure you have the following requirements ready. These prerequisites ensure your environment is properly configured and you won’t get stuck midway through the integration.</p>
<p>First, you need a working <strong>Flutter installation</strong> on your system. Flutter must be correctly installed and configured so you can run apps on both iOS and Android. If you haven’t set this up yet, follow the official <a target="_blank" href="https://docs.flutter.dev/get-started/install">Flutter installation</a> guide to prepare your development environment.</p>
<p>Next, you need a <strong>Firebase account</strong>. Firebase provides a web-based console where you’ll create a project that links to your Flutter app. You can sign up for free at the <a target="_blank" href="https://console.firebase.google.com/">Firebase Console.</a></p>
<p>For a smoother integration process, I also highly recommend installing the <a target="_blank" href="https://firebase.google.com/docs/flutter/setup?platform=ios"><strong>Firebase CL</strong></a><a target="_blank" href="https://docs.flutter.dev/get-started/install"><strong>I</strong></a>. The CLI enables the <code>flutterfire configure</code> command, which automatically links your <a target="_blank" href="https://docs.flutter.dev/get-started/install">Flutter</a> project to Firebase and generates a <code>firebase_options.dart</code> file with all your platform-specific configurations. This step is optional, but it saves you time compared to manually adding configuration files. You can install the CLI by following <a target="_blank" href="https://firebase.google.com/docs/cli">Firebase CLI setup instructions.</a></p>
<p>Finally, ensure you have either an <strong>iOS simulator</strong> (via Xcode on macOS) or an <strong>Android emulator</strong> (via Android Studio or the command line) to test the integration. Crashlytics will only log crashes once the app has run on a real or simulated device.</p>
<p>With these prerequisites in place, you’re ready to move on to the actual integration steps.</p>
<h2 id="heading-set-up-the-flutter-projhttpsconsolefirebasegooglecomect">Set Up the Flutter Pro<a target="_blank" href="https://console.firebase.google.com/">j</a>ect</h2>
<p>The journey begins by creating a Flutter project. If you don’t already have one, run the following command from your terminal:</p>
<pre><code class="lang-bash">flutter create my_crashlytics_app
<span class="hljs-built_in">cd</span> my_crashlytics_app
</code></pre>
<p>This generates the boilerplate structure for your Flutter app, giving us a foundation where we can add Firebase and Crashlytics.</p>
<h2 id="heading-connect-flutter-to-firebase">Connect Flutter to Firebase</h2>
<p>Before Crashlytics can work, your app must be connected to a Firebase project. Head over to the Firebase Console and create a new project. Think of the Firebase project as the “backend container” that manages all services, including analytics, authentication, and crash reporting.</p>
<p>Once the project is created, you need to register your Flutter apps with Firebase. Flutter supports both iOS and Android, so you’ll add both platforms.</p>
<p>On the iOS side, Firebase will guide you through adding an iOS app, downloading the <code>GoogleService-Info.plist</code> configuration file, and placing it inside the <code>ios/Runner</code> directory of your Flutter project. On Android, you’ll do something similar by downloading the <code>google-services.json</code> file and adding it to the <code>android/app</code> directory.</p>
<p>If you prefer a more streamlined approach, the Firebase CLI provides a <code>flutterfire configure</code> command. Running this will allow you to select your Firebase project and automatically generate a <code>firebase_options.dart</code> file for your Flutter app. This file centralizes your Firebase configuration and reduces manual setup.</p>
<h2 id="heading-add-the-required-dependencies">Add the Required Dependencies</h2>
<p>With Firebase linked, the next step is to bring in the necessary packages that enable Crashlytics. Flutter integrates with Firebase through plugins, which are small libraries that bridge Flutter and native SDKs. Open your <code>pubspec.yaml</code> file and add the following:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">dependencies:</span>
  <span class="hljs-attr">firebase_core:</span> <span class="hljs-string">^4.0.0</span>
  <span class="hljs-attr">firebase_crashlytics:</span> <span class="hljs-string">^5.0.0</span>
</code></pre>
<p>The <code>firebase_core</code> package initializes communication with Firebase, while <code>firebase_crashlytics</code> is the library that captures and reports crashes. Run <code>flutter pub get</code> to download and install these dependencies.</p>
<h2 id="heading-initialize-crashlytics-in-maindart">Initialize Crashlytics in <code>main.dart</code></h2>
<p>Now that the dependencies are installed, we need to initialize Firebase when the app starts and configure Crashlytics to capture both synchronous and asynchronous errors. Replace the contents of your <code>lib/main.dart</code> file with the following code:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'dart:ui'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:firebase_core/firebase_core.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:firebase_crashlytics/firebase_crashlytics.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'firebase_options.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'presentation/home_screen.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;

<span class="hljs-keyword">void</span> main() <span class="hljs-keyword">async</span> {
  WidgetsFlutterBinding.ensureInitialized();
  <span class="hljs-keyword">await</span> Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );

  <span class="hljs-comment">// Capture Flutter framework errors</span>
  FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterFatalError;

  <span class="hljs-comment">// Capture uncaught asynchronous errors</span>
  PlatformDispatcher.instance.onError = (error, stack) {
    FirebaseCrashlytics.instance.recordError(error, stack, fatal: <span class="hljs-keyword">true</span>);
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>;
  };

  runApp(<span class="hljs-keyword">const</span> MyApp());
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyApp</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-keyword">const</span> MyApp({<span class="hljs-keyword">super</span>.key});

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> MaterialApp(
      title: <span class="hljs-string">'Firebase Crashlytics Demo'</span>,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: <span class="hljs-keyword">true</span>,
      ),
      home: <span class="hljs-keyword">const</span> HomeScreen(),
    );
  }
}
</code></pre>
<p>Let’s pause to unpack this. The <code>FlutterError.onError</code> line ensures that any error that occurs inside Flutter’s widget tree is reported as a <strong>fatal crash</strong>. The <code>PlatformDispatcher.instance.onError</code> captures errors outside of the widget tree, such as asynchronous exceptions, and reports them to Crashlytics as well. Together, these configurations ensure that virtually all unexpected issues are sent to Firebase.</p>
<h2 id="heading-build-a-simple-test-screen">Build a Simple Test Screen</h2>
<p>To verify that Crashlytics works, let’s create a test screen where we can deliberately throw errors. Create a new folder called <code>presentation</code> in your <code>lib</code> directory, then inside it, create a file named <code>home_screen.dart</code> with the following content:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HomeScreen</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatefulWidget</span> </span>{
  <span class="hljs-keyword">const</span> HomeScreen({<span class="hljs-keyword">super</span>.key});

  <span class="hljs-meta">@override</span>
  State&lt;HomeScreen&gt; createState() =&gt; _HomeScreenState();
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">_HomeScreenState</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">State</span>&lt;<span class="hljs-title">HomeScreen</span>&gt; </span>{
  <span class="hljs-built_in">int</span> _counter = <span class="hljs-number">0</span>;

  <span class="hljs-keyword">void</span> _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: <span class="hljs-keyword">const</span> Text(<span class="hljs-string">'Firebase Crashlytics App'</span>),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: &lt;Widget&gt;[
            <span class="hljs-keyword">const</span> Text(<span class="hljs-string">'You have pushed the button this many times:'</span>),
            Text(
              <span class="hljs-string">'<span class="hljs-subst">$_counter</span>'</span>,
              style: Theme.of(context).textTheme.headlineMedium,
            ),
            <span class="hljs-keyword">const</span> SizedBox(height: <span class="hljs-number">15</span>),
            ElevatedButton(
              onPressed: () =&gt; <span class="hljs-keyword">throw</span> Exception(<span class="hljs-string">'Test Exception'</span>),
              child: <span class="hljs-keyword">const</span> Text(<span class="hljs-string">'Throw Exception'</span>),
            ),
            <span class="hljs-keyword">const</span> SizedBox(height: <span class="hljs-number">10</span>),
            ElevatedButton(
              onPressed: () {
                <span class="hljs-keyword">throw</span> <span class="hljs-keyword">const</span> FormatException(<span class="hljs-string">'Custom format error occurred'</span>);
              },
              child: <span class="hljs-keyword">const</span> Text(<span class="hljs-string">'Throw Exception with Feedback'</span>),
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: <span class="hljs-string">'Increment'</span>,
        child: <span class="hljs-keyword">const</span> Icon(Icons.add),
      ),
    );
  }
}
</code></pre>
<p>This screen provides two buttons: one that throws a general exception and another that throws a format exception. When clicked, these crashes are reported to Crashlytics. This makes it easy to test whether your setup is working correctly.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701668096906/3ccf29af-d94e-479a-aebd-664c390e4ba3.png" alt="General and format exception buttons" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h2 id="heading-run-and-test-the-app">Run and Test the App</h2>
<p>At this stage, run the app on an iOS simulator or Android emulator. Interact with the screen and press the buttons that throw exceptions. Even though the app will crash or display an error, Crashlytics will silently log the details and send them to Firebase once the app restarts and regains network connectivity.</p>
<p>Crashes usually take a couple of minutes to appear in the Firebase Console. Navigate to your project in the console, then go to <strong>Release &amp; Monitor &gt; Crashlytics</strong>. There, you will see a dashboard listing all recorded crashes, complete with stack traces, device information, and frequency of occurrence. The screenshots below showcase what you’ll be able to see on Crashlytics.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701668003651/e43c9e64-7a64-4d7b-a94f-2f908f2c76b9.png" alt="Crashlytics Dashboard" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701668011519/354c38af-f263-4de1-9896-f6e2dc16167c.png" alt="Checking Logs" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701668015513/3448cdaa-22eb-4cdb-8fad-07694ac6a0c9.png" alt="Viewing Data" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701668020390/877dee08-c182-4ec8-beca-c3c1dc27692a.png" alt="Detailed log of error" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701668024117/a2335fcc-4e56-438c-9d88-e37d98ca051c.png" alt="Stack trace of error" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h2 id="heading-understanding-the-crashlytics-dashboard">Understanding the Crashlytics Dashboard</h2>
<p>The Crashlytics dashboard is more than just a list of crashes. It groups issues together so you can see how many users are affected by a specific bug. It highlights trends such as whether a particular crash is new, increasing, or decreasing. It also integrates with alerts, allowing you to get notified when a severe issue affects a significant portion of your users.</p>
<p>This means you don’t just learn that your app crashed, you also get actionable insights to prioritize which bugs need immediate attention.</p>
<h2 id="heading-advanced-firebase-crashlytics-in-flutter-going-beyond-the-basics">Advanced Firebase Crashlytics in Flutter: Going Beyond the Basics</h2>
<p>Once Crashlytics is successfully integrated into your Flutter app, the next step is to take full advantage of its advanced features. While catching crashes is useful, real-world debugging often requires context, deeper insights, and error handling strategies that go beyond simply knowing an app has failed. Let’s explore these advanced concepts.</p>
<h3 id="heading-how-to-log-non-fatal-errors">How to Log Non-Fatal Errors</h3>
<p>Not every problem in an application leads to a crash. Sometimes you’ll encounter recoverable errors, such as a failed API call, a parsing issue, or a user action that leads to unexpected behavior. These issues don’t crash your app but still affect user experience. Crashlytics allows you to record them as <strong>non-fatal errors</strong>.</p>
<p>In Flutter, you can use:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">try</span> {
  <span class="hljs-comment">// Some code that might fail</span>
  <span class="hljs-keyword">final</span> result = <span class="hljs-built_in">int</span>.parse(<span class="hljs-string">"invalid_number"</span>);
} <span class="hljs-keyword">catch</span> (e, stack) {
  FirebaseCrashlytics.instance.recordError(
    e,
    stack,
    fatal: <span class="hljs-keyword">false</span>,
    reason: <span class="hljs-string">'Number parsing failed in profile setup'</span>,
  );
}
</code></pre>
<p>Here, the <code>fatal: false</code> flag ensures the error is logged without being treated as a full app crash. The optional <code>reason</code> parameter provides extra human-readable context in the Crashlytics dashboard. This feature is invaluable for tracking silent failures that degrade performance but don’t necessarily kill your app.</p>
<h3 id="heading-how-to-add-custom-keys-for-context">How to Add Custom Keys for Context</h3>
<p>One of the challenges with debugging crashes in production is reproducing the problem. A stack trace alone often doesn’t tell you enough about the user’s journey. Custom keys allow you to attach extra metadata to Crashlytics reports, such as the user’s app state, preferences, or which feature they were using when the crash occurred.</p>
<p>For example:</p>
<pre><code class="lang-dart">FirebaseCrashlytics.instance.setCustomKey(<span class="hljs-string">'screen'</span>, <span class="hljs-string">'CheckoutScreen'</span>);
FirebaseCrashlytics.instance.setCustomKey(<span class="hljs-string">'cart_items'</span>, <span class="hljs-number">3</span>);
FirebaseCrashlytics.instance.setCustomKey(<span class="hljs-string">'payment_method'</span>, <span class="hljs-string">'Card'</span>);
</code></pre>
<p>With these keys set, any crash or non-fatal error that occurs while the user is on the checkout screen will carry this context. When you open the report in the Firebase Console, you’ll immediately see these values, which makes debugging significantly easier.</p>
<h3 id="heading-how-to-log-custom-events-and-breadcrumbs">How to Log Custom Events and Breadcrumbs</h3>
<p>In addition to custom keys, Crashlytics allows you to log custom messages that act as <strong>breadcrumbs</strong>. These are small logs that tell you what the app was doing leading up to a crash.</p>
<pre><code class="lang-dart">FirebaseCrashlytics.instance.log(<span class="hljs-string">'User tapped "Place Order" button'</span>);
FirebaseCrashlytics.instance.log(<span class="hljs-string">'API request started: /checkout'</span>);
FirebaseCrashlytics.instance.log(<span class="hljs-string">'Payment process initialized'</span>);
</code></pre>
<p>If a crash happens afterward, you’ll have a trail of events that explain the sequence leading up to the failure. This is often the missing piece in diagnosing complex crashes.</p>
<h3 id="heading-how-to-associate-crashes-with-users">How to Associate Crashes with Users</h3>
<p>Crashlytics supports <strong>user identifiers</strong>, allowing you to link crashes back to specific users. While you should avoid storing sensitive data, you can safely attach unique identifiers such as user IDs, emails, or usernames.</p>
<pre><code class="lang-dart">FirebaseCrashlytics.instance.setUserIdentifier(<span class="hljs-string">'user_12345'</span>);
</code></pre>
<p>With this, you can investigate whether specific users or groups of users are disproportionately affected by a bug. This also helps customer support teams quickly link bug reports from users to real data in Crashlytics.</p>
<h3 id="heading-how-to-ensure-proper-symbolication">How to Ensure Proper Symbolication</h3>
<p>When you run your app in debug mode, stack traces are human-readable. But in release builds, especially on iOS and Android, stack traces can be obfuscated or stripped of symbols. Symbolication is the process of mapping these stripped traces back to meaningful method and class names.</p>
<p><strong>On iOS</strong>, you’ll need to upload dSYM files (debug symbol files) to Firebase. These files are generated when you build your iOS app for release. You can automate the upload by adding a Run Script in Xcode under your project’s build settings:</p>
<pre><code class="lang-bash"><span class="hljs-string">"<span class="hljs-variable">${PODS_ROOT}</span>/FirebaseCrashlytics/upload-symbols"</span> \
-gsp <span class="hljs-string">"<span class="hljs-variable">${PROJECT_DIR}</span>/Runner/GoogleService-Info.plist"</span> \
-p ios <span class="hljs-string">"<span class="hljs-variable">${DWARF_DSYM_FOLDER_PATH}</span>/<span class="hljs-variable">${DWARF_DSYM_FILE_NAME}</span>"</span>
</code></pre>
<p>This ensures that whenever you build a release, symbol files are automatically uploaded to Firebase.</p>
<p><strong>On Android</strong>, if you’re using ProGuard or R8 for code shrinking and obfuscation, you’ll need to upload mapping files. In your <code>app/build.gradle</code>, enable the Crashlytics Gradle plugin:</p>
<pre><code class="lang-bash">apply plugin: <span class="hljs-string">'com.google.firebase.crashlytics'</span>
</code></pre>
<p>This plugin takes care of uploading the mapping files automatically when you build a release.</p>
<p>Without symbolication, your crash reports will contain unreadable stack traces, making debugging almost impossible. Ensuring proper symbol upload is critical for production-level monitoring.</p>
<h3 id="heading-how-to-controll-data-collection">How to Controll Data Collection</h3>
<p>In some cases, such as adhering to GDPR or other data privacy laws, you may want to control when Crashlytics starts collecting data. Flutter gives you a way to enable or disable collection dynamically:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">await</span> FirebaseCrashlytics.instance.setCrashlyticsCollectionEnabled(<span class="hljs-keyword">false</span>);
</code></pre>
<p>You can turn this on once the user has given consent. This flexibility is especially useful in regions with strict user privacy requirements.</p>
<h2 id="heading-best-practices-for-production">Best Practices for Production</h2>
<ol>
<li><p><strong>Test before release</strong>: Always trigger crashes in debug mode and confirm they appear in the Crashlytics dashboard before deploying your app.</p>
</li>
<li><p><strong>Use non-fatal logs liberally</strong>: Many silent issues can be caught this way before they escalate into widespread crashes.</p>
</li>
<li><p><strong>Automate symbol uploads</strong>: Make sure your CI/CD or build pipeline uploads dSYM (iOS) and mapping files (Android) consistently.</p>
</li>
<li><p><strong>Add context with custom keys and logs</strong>: The more context you attach, the faster you can reproduce and fix bugs.</p>
</li>
<li><p><strong>Respect privacy</strong>: Never log personally identifiable or sensitive information.</p>
</li>
</ol>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Integrating Firebase Crashlytics into a Flutter app is a straightforward process, but its impact is massive. By providing real-time crash reporting and detailed analytics, Crashlytics helps you maintain stability, build user trust, and ultimately deliver a better app experience. From setting up the Firebase project to capturing both synchronous and asynchronous errors, we’ve gone through everything you need to get started.</p>
<p>Crashlytics goes far beyond crash reporting. By leveraging features like non-fatal error logging, custom keys, breadcrumbs, and user identifiers, you can transform raw crash data into meaningful insights that directly improve your debugging process.</p>
<p>With proper symbolication in place, you’ll always have readable stack traces, making it much easier to fix issues in production. With this advanced setup, Crashlytics becomes not just a safety net, but a core part of your development workflow, helping you ship stable apps, respond quickly to issues, and build trust with your users.</p>
<p>The next step is to deploy your app to real devices and monitor crashes as they happen in the wild. Over time, Crashlytics will become one of your most valuable tools in maintaining app quality.</p>
<h3 id="heading-references"><strong>References</strong></h3>
<ul>
<li><p>Flutter Documentation – <a target="_blank" href="https://docs.flutter.dev/get-started/install">Install Flutter</a></p>
</li>
<li><p>Firebase Documentation – <a target="_blank" href="https://console.firebase.google.com/">Firebase Console</a></p>
</li>
<li><p>Firebase Documentation – <a target="_blank" href="https://docs.flutter.dev/get-started/install">Add Firebase to your F</a><a target="_blank" href="https://firebase.google.com/docs/flutter/setup">lutter App</a></p>
</li>
<li><p>Firebase Crashlytics for Flutter – <a target="_blank" href="https://pub.dev/packages/firebase_crashlytics">firebase_crashlytics Pa</a><a target="_blank" href="https://console.firebase.google.com/">c</a><a target="_blank" href="https://docs.flutter.dev/get-started/install">kage</a></p>
</li>
<li><p>Firebase Core for Flutter – <a target="_blank" href="https://firebase.google.com/docs/flutter/setup">firebase</a><a target="_blank" href="https://console.firebase.google.com/">_core Package</a></p>
</li>
<li><p>Firebase Documentation – <a target="_blank" href="https://firebase.google.com/docs/flutter/setup">Fireb</a><a target="_blank" href="https://pub.dev/packages/firebase_crashlytics">ase Crashlyti</a><a target="_blank" href="https://firebase.google.com/docs/flutter/setup">c</a><a target="_blank" href="https://console.firebase.google.com/">s Overview</a></p>
</li>
<li><p>Firebase Documentation – <a target="_blank" href="https://pub.dev/packages/firebase_crashlytics">Upload dS</a><a target="_blank" href="https://pub.dev/packages/firebase_core">YM</a> <a target="_blank" href="https://firebase.google.com/docs/crashlytics/get-deobfuscated-reports?platform=ios">Files (iO</a><a target="_blank" href="https://firebase.google.com/docs/flutter/setup">S)</a></p>
</li>
<li><p>Firebase Documentation – <a target="_blank" href="https://firebase.google.com/docs/flutter/setup">U</a><a target="_blank" href="https://pub.dev/packages/firebase_crashlytics">p</a><a target="_blank" href="https://firebase.google.com/docs/crashlytics">load ProGuard/R8 Mappi</a><a target="_blank" href="https://firebase.google.com/docs/crashlytics/get-deobfuscated-reports?platform=android">ng</a> <a target="_blank" href="https://pub.dev/packages/firebase_core">Fi</a><a target="_blank" href="https://console.firebase.google.com/">les (Android)</a></p>
</li>
<li><p>Firebase CLI – <a target="_blank" href="https://firebase.google.com/docs/flutter/setup">Install</a> <a target="_blank" href="https://firebase.google.com/docs/crashlytics/get-deobfuscated-reports?platform=android">and Co</a><a target="_blank" href="https://firebase.google.com/docs/cli">nfigure</a> <a target="_blank" href="https://firebase.google.com/docs/crashlytics">Fire</a><a target="_blank" href="https://console.firebase.google.com/">base CLI</a></p>
</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build Database Seed Scripts for Your Node Application ]]>
                </title>
                <description>
                    <![CDATA[ Database seed scripts are pre-written pieces of code that populate your database with initial data, serving as the foundation for a consistent development environment. These files contain structured data that follows real-world scenarios, letting you... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-database-seed-scripts-for-your-node-application/</link>
                <guid isPermaLink="false">68880d1bebbc17df34200263</guid>
                
                    <category>
                        <![CDATA[ Firebase ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Node.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Databases ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Tope Fasasi ]]>
                </dc:creator>
                <pubDate>Mon, 28 Jul 2025 23:51:55 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1753746696472/4a181d0a-5e11-4603-ac27-151d16a6bbf4.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p><a target="_blank" href="https://supabase.com/docs/guides/local-development/seeding-your-database">Database seed scripts</a> are pre-written pieces of code that populate your database with initial data, serving as the foundation for a consistent development environment. These files contain structured data that follows real-world scenarios, letting you work with meaningful information from the moment you set up your local environment.</p>
<p>Instead of manually creating test users, products, or other entities every time you reset your database, seed files automate this process, ensuring every team member works with identical data sets.</p>
<p>The benefits of using seed files go far beyond convenience. They provide consistent test data across different environments, dramatically faster development setup times, and truly reproducible environments that eliminate the "it works on my machine" problem. When your entire team can spin up identical databases with realistic data in seconds, everyone can develop significantly faster and debugging becomes more predictable.</p>
<p><a target="_blank" href="https://medium.com/firebase-developers/what-is-firebase-the-complete-story-abridged-bcc730c5f2c0">Firebase</a>, Google's backend-as-a-service (BaaS) platform, offers an excellent foundation for implementing seed files thanks to its flexible <a target="_blank" href="https://www.mongodb.com/resources/basics/databases/nosql-explained">NoSQL</a> structure and robust <a target="_blank" href="https://www.youtube.com/watch?v=lqZEXpQDuHU">Node.js SDK</a>. <a target="_blank" href="https://firebase.google.com/docs/firestore">Firestore's</a> document-based architecture naturally accommodates the varied data types and relationships commonly found in seed files. At the same time, Firebase's real-time capabilities make sure that your seeded data immediately reflects across all connected clients.</p>
<p>Seed files prove most valuable during initial project setup, feature development requiring specific data configurations, automated testing scenarios, and when onboarding new team members. They're particularly crucial when working with complex data relationships or when your application requires substantial amounts of interconnected data to function properly.</p>
<p>This article will guide you through creating comprehensive seed files for Firebase-powered Node.js applications, covering everything from basic setup to advanced techniques for managing complex data relationships and environment-specific configurations.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-set-up-firebase-for-your-nodejs-application">How to Set Up Firebase for Your Node.js Application</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-plan-your-seed-data-structure">How to Plan Your Seed Data Structure</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-create-basic-seed-files">How to Create Basic Seed Files</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-build-complex-data-relationships">How to Build Complex Data Relationships</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-manage-seed-scripts">How to Manage Seed Scripts</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-environment-specific-seeding">Environment-Specific Seeding</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-integrate-all-this-into-your-development-workflow">How to Integrate All This into Your Development Workflow</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-document-your-seeding-practices">How to Document Your Seeding Practices</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-create-a-seeding-guide">Create a Seeding Guide</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-environment-configuration-documentation">Environment Configuration Documentation</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-write-automated-tests-for-seed-data">How to Write Automated Tests for Seed Data</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-insteall-testing-dependencies">Install Testing Dependencies</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-test-seed-data-structure">Test Seed Data Structure</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-test-raw-seed-data-files">Test Raw Seed Data Files</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-add-test-scripts-to-packagejson">Add Test Scripts to package.json</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-create-jest-configuration">Create Jest Configuration</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ol>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before getting started, you’ll need <a target="_blank" href="https://nodejs.org/en/download">Node.js 24</a> or higher running on your system because the Admin SDK requires modern JavaScript features. You also need to have an active Firebase project with Firestore enabled, which you can create through the <a target="_blank" href="https://console.firebase.google.com/u/0/">Firebase Console</a>.</p>
<p>You should also know ES6+ JavaScript features in general and async/await syntax and destructuring in particular, as these will be helpful when going through the code examples.</p>
<p>A rudimentary understanding of NoSQL database theory, especially document-based storage and collections, will also help, as Firestore stresses being in opposition to <a target="_blank" href="https://cloud.google.com/learn/what-is-a-relational-database">traditional relational databases</a>.</p>
<p>Finally, a little knowledge of the Firebase security model and authentication system will go a long way in ensuring that you can safely implement seed files in different environments.</p>
<p>To create a Firebase project and enable the Firestore database, read this <a target="_blank" href="https://firebase.google.com/docs/firestore/quickstart">guide</a>.</p>
<h2 id="heading-how-to-set-up-firebase-for-your-nodejs-application">How to Set Up Firebase for Your Node.js Application</h2>
<p>You’ll start by installing the server-side SDK, which allows access to Firebase services without user authentication. This SDK fits well in a trusted server environment that needs complete admin privileges for a Firebase project:</p>
<pre><code class="lang-javascript">npm install firebase-admin dotenv
</code></pre>
<p>The installation also brings in <code>dotenv</code>, which lets you securely maintain environment variables, something very important when handling Firebase credentials in varying deployment environments.</p>
<p>Next, you’ll need to configure your Firebase project by navigating to the Firebase Console. There, you can first create a service account: Go to Project Settings &gt; Service Accounts, then generate a new private key. This JSON file holds the credentials that will allow your apps to communicate with Firebase services. Store it safely and never commit it to your source version control.</p>
<p>Now you’ll need to create a Firebase initialization module to hold the code connecting to your Firestore database.</p>
<p>For example:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// config/firebase.js</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-built_in">require</span>(<span class="hljs-string">'dotenv'</span>).config();

<span class="hljs-keyword">const</span> serviceAccount = {
  <span class="hljs-attr">type</span>: <span class="hljs-string">"service_account"</span>,
  <span class="hljs-attr">project_id</span>: process.env.FIREBASE_PROJECT_ID,
  <span class="hljs-attr">private_key_id</span>: process.env.FIREBASE_PRIVATE_KEY_ID,
  <span class="hljs-attr">private_key</span>: process.env.FIREBASE_PRIVATE_KEY.replace(<span class="hljs-regexp">/\\n/g</span>, <span class="hljs-string">'\n'</span>),
  <span class="hljs-attr">client_email</span>: process.env.FIREBASE_CLIENT_EMAIL,
  <span class="hljs-attr">client_id</span>: process.env.FIREBASE_CLIENT_ID,
  <span class="hljs-attr">auth_uri</span>: <span class="hljs-string">"https://accounts.google.com/o/oauth2/auth"</span>,
  <span class="hljs-attr">token_uri</span>: <span class="hljs-string">"https://oauth2.googleapis.com/token"</span>,
  <span class="hljs-attr">auth_provider_x509_cert_url</span>: <span class="hljs-string">"https://www.googleapis.com/oauth2/v1/certs"</span>
};

admin.initializeApp({
  <span class="hljs-attr">credential</span>: admin.credential.cert(serviceAccount),
  <span class="hljs-attr">databaseURL</span>: <span class="hljs-string">`https://<span class="hljs-subst">${process.env.FIREBASE_PROJECT_ID}</span>.firebaseio.com`</span>
});

<span class="hljs-keyword">const</span> db = admin.firestore();
<span class="hljs-built_in">module</span>.exports = { admin, db };
</code></pre>
<p>This configuration module uses <a target="_blank" href="https://en.wikipedia.org/wiki/Environment_variable">environment variables</a> to securely store sensitive Firebase credentials while providing a clean interface for database operations throughout your application. The service account credentials enable full read/write access to your Firestore database, which is necessary for seed operations.</p>
<h2 id="heading-how-to-plan-your-seed-data-structure">How to Plan Your Seed Data Structure</h2>
<p>Effective seed data requires careful planning to make sure that it accurately reflects your application's real-world usage patterns. Start by analyzing your application's core entities and their relationships, identifying which collections are fundamental to your app’s operation and which depend on others.</p>
<p>Consider a typical e-commerce application structure where users create orders containing products from various categories. Your seed data should establish these relationships logically, ensuring referential integrity across collections. Users should exist before orders, products should belong to valid categories, and orders should reference existing users and products.</p>
<p>Designing seed data is pivotal to supporting different development scenarios. Users should be created with various roles and permissions, products should be scattered across multiple categories and different price ranges, and orders should be put into varying states (like pending, completed, or cancelled). This diversity in your data allows you to test various code paths and edge cases without manually creating certain data combinations.</p>
<p>You’ll also need to determine suitable data volumes for each environment. For quicker testing in development environments, 10-50 records per collection should be sufficient. But for staging environments, you could simulate production load by having hundreds or thousands of records. Testing environments usually need bare minimum, tightly-controlled data that supports particular test scenarios.</p>
<p>You should arrange your seed data by environments and purposes, having separate data sets for unit testing, integration testing, and general development. This way, teams can test for different reasons against a dataset without interfering with one another.</p>
<h2 id="heading-how-to-create-basic-seed-files">How to Create Basic Seed Files</h2>
<p>You’ll want to provide the seed scripts with an organized file structure so everything stays organized as the application grows. Create a folder called <code>seeds</code> with subfolders for various collections and environments like this:</p>
<pre><code class="lang-plaintext">seeds/
├── data/
│   ├── users.js
│   ├── products.js
│   └── categories.js
├── scripts/
│   ├── seedUsers.js
│   ├── seedProducts.js
│   └── seedAll.js
└── index.js
</code></pre>
<p>Separating raw data and seeding logic makes it easier to change data without modifying insertion scripts. Begin with a simple user seed script that covers the basics.</p>
<p>For example:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// seeds/scripts/seedUsers.js</span>
<span class="hljs-keyword">const</span> { db } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'../../config/firebase'</span>);
<span class="hljs-keyword">const</span> users = <span class="hljs-built_in">require</span>(<span class="hljs-string">'../data/users'</span>);

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">seedUsers</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Starting user seeding...'</span>);

  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> batch = db.batch();
    <span class="hljs-keyword">const</span> usersCollection = db.collection(<span class="hljs-string">'users'</span>);

    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> userData <span class="hljs-keyword">of</span> users) {
      <span class="hljs-keyword">const</span> docRef = usersCollection.doc(); <span class="hljs-comment">// Auto-generated ID</span>
      batch.set(docRef, {
        ...userData,
        <span class="hljs-attr">createdAt</span>: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(),
        <span class="hljs-attr">updatedAt</span>: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>()
      });
    }

    <span class="hljs-keyword">await</span> batch.commit();
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Successfully seeded <span class="hljs-subst">${users.length}</span> users`</span>);
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Error seeding users:'</span>, error);
    <span class="hljs-keyword">throw</span> error;
  }
}

<span class="hljs-built_in">module</span>.exports = seedUsers;
</code></pre>
<p>The principal features of the script involve: batch operations for efficiency, automatic timestamp generation, error handling with meaningful logging, and auto-generated document IDs. Batch operations are essential for performance, as they minimize the number of network calls and provide atomicity.</p>
<p>Now, create the relevant data files that’ll hold the actual seed data, distinct from the seeding logic.</p>
<p>For example:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// seeds/data/users.js</span>
<span class="hljs-built_in">module</span>.exports = [
  {
    <span class="hljs-attr">email</span>: <span class="hljs-string">'admin@example.com'</span>,
    <span class="hljs-attr">firstName</span>: <span class="hljs-string">'Admin'</span>,
    <span class="hljs-attr">lastName</span>: <span class="hljs-string">'User'</span>,
    <span class="hljs-attr">role</span>: <span class="hljs-string">'admin'</span>,
    <span class="hljs-attr">isActive</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">preferences</span>: {
      <span class="hljs-attr">theme</span>: <span class="hljs-string">'dark'</span>,
      <span class="hljs-attr">notifications</span>: <span class="hljs-literal">true</span>
    }
  },
  {
    <span class="hljs-attr">email</span>: <span class="hljs-string">'user@example.com'</span>,
    <span class="hljs-attr">firstName</span>: <span class="hljs-string">'Regular'</span>,
    <span class="hljs-attr">lastName</span>: <span class="hljs-string">'User'</span>,
    <span class="hljs-attr">role</span>: <span class="hljs-string">'user'</span>,
    <span class="hljs-attr">isActive</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">preferences</span>: {
      <span class="hljs-attr">theme</span>: <span class="hljs-string">'light'</span>,
      <span class="hljs-attr">notifications</span>: <span class="hljs-literal">false</span>
    }
  }
];
</code></pre>
<p>This separation makes it straightforward to alter seed data without the need to modify seeding logic itself. It facilitates quick adjustments of data for different environments or testing scenarios.</p>
<h2 id="heading-how-to-build-complex-data-relationships">How to Build Complex Data Relationships</h2>
<p>With every application that grows in complexity, you’ll need to employ some potentially advanced techniques to handle things like relationship-building among collections and data consistency. You can ensure correct referencing during seeding of related collections by storing document IDs and using those IDs in dependent collections.</p>
<p>You can create a seed system that takes care of collection dependencies automatically like this:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// seeds/scripts/seedWithReferences.js</span>
<span class="hljs-keyword">const</span> { db } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'../../config/firebase'</span>);

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">seedWithReferences</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Starting advanced seeding with references...'</span>);

  <span class="hljs-comment">// First, seed categories and store their IDs</span>
  <span class="hljs-keyword">const</span> categoryIds = <span class="hljs-keyword">await</span> seedCategories();

  <span class="hljs-comment">// Then, seed products with category references</span>
  <span class="hljs-keyword">const</span> productIds = <span class="hljs-keyword">await</span> seedProducts(categoryIds);

  <span class="hljs-comment">// Finally, seed orders with product references</span>
  <span class="hljs-keyword">await</span> seedOrders(productIds);
}

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">seedCategories</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> categories = [
    { <span class="hljs-attr">name</span>: <span class="hljs-string">'Electronics'</span>, <span class="hljs-attr">description</span>: <span class="hljs-string">'Electronic devices and gadgets'</span> },
    { <span class="hljs-attr">name</span>: <span class="hljs-string">'Books'</span>, <span class="hljs-attr">description</span>: <span class="hljs-string">'Physical and digital books'</span> }
  ];

  <span class="hljs-keyword">const</span> categoryIds = [];
  <span class="hljs-keyword">const</span> batch = db.batch();

  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> category <span class="hljs-keyword">of</span> categories) {
    <span class="hljs-keyword">const</span> docRef = db.collection(<span class="hljs-string">'categories'</span>).doc();
    batch.set(docRef, {
      ...category,
      <span class="hljs-attr">createdAt</span>: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>()
    });
    categoryIds.push({ <span class="hljs-attr">id</span>: docRef.id, <span class="hljs-attr">name</span>: category.name });
  }

  <span class="hljs-keyword">await</span> batch.commit();
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Seeded <span class="hljs-subst">${categories.length}</span> categories`</span>);
  <span class="hljs-keyword">return</span> categoryIds;
}

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">seedProducts</span>(<span class="hljs-params">categoryIds</span>) </span>{
  <span class="hljs-keyword">const</span> products = [
    {
      <span class="hljs-attr">name</span>: <span class="hljs-string">'Smartphone'</span>,
      <span class="hljs-attr">price</span>: <span class="hljs-number">599.99</span>,
      <span class="hljs-attr">categoryName</span>: <span class="hljs-string">'Electronics'</span>,
      <span class="hljs-attr">stock</span>: <span class="hljs-number">100</span>
    },
    {
      <span class="hljs-attr">name</span>: <span class="hljs-string">'JavaScript Guide'</span>,
      <span class="hljs-attr">price</span>: <span class="hljs-number">29.99</span>,
      <span class="hljs-attr">categoryName</span>: <span class="hljs-string">'Books'</span>,
      <span class="hljs-attr">stock</span>: <span class="hljs-number">50</span>
    }
  ];

  <span class="hljs-keyword">const</span> productIds = [];
  <span class="hljs-keyword">const</span> batch = db.batch();

  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> product <span class="hljs-keyword">of</span> products) {
    <span class="hljs-keyword">const</span> category = categoryIds.find(<span class="hljs-function"><span class="hljs-params">cat</span> =&gt;</span> cat.name === product.categoryName);
    <span class="hljs-keyword">const</span> docRef = db.collection(<span class="hljs-string">'products'</span>).doc();

    batch.set(docRef, {
      <span class="hljs-attr">name</span>: product.name,
      <span class="hljs-attr">price</span>: product.price,
      <span class="hljs-attr">stock</span>: product.stock,
      <span class="hljs-attr">categoryId</span>: category.id,
      <span class="hljs-attr">categoryName</span>: category.name,
      <span class="hljs-attr">createdAt</span>: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>()
    });

    productIds.push({ <span class="hljs-attr">id</span>: docRef.id, <span class="hljs-attr">name</span>: product.name, <span class="hljs-attr">price</span>: product.price });
  }

  <span class="hljs-keyword">await</span> batch.commit();
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Seeded <span class="hljs-subst">${products.length}</span> products`</span>);
  <span class="hljs-keyword">return</span> productIds;
}
</code></pre>
<p>This guarantees that relationships between collections will be properly maintained while the actual seeding takes place, which prevents any orphaned records and maintains referential integrity. IDs are returned by the function and can be used by dependent collections to create an obvious dependency chain.</p>
<p>To create realistic fake data, you can use the <a target="_blank" href="https://vueschool.io/articles/vuejs-tutorials/effortless-data-generation-with-faker-js-a-developers-guide/">Faker.js</a> library to churn out huge volumes of different variations of realistic-looking data.</p>
<p>For example:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> { faker } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'@faker-js/faker'</span>);

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">generateFakeUsers</span>(<span class="hljs-params">count = <span class="hljs-number">100</span></span>) </span>{
  <span class="hljs-keyword">const</span> users = [];

  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; count; i++) {
    users.push({
      <span class="hljs-attr">email</span>: faker.internet.email(),
      <span class="hljs-attr">firstName</span>: faker.person.firstName(),
      <span class="hljs-attr">lastName</span>: faker.person.lastName(),
      <span class="hljs-attr">dateOfBirth</span>: faker.date.birthdate(),
      <span class="hljs-attr">address</span>: {
        <span class="hljs-attr">street</span>: faker.location.streetAddress(),
        <span class="hljs-attr">city</span>: faker.location.city(),
        <span class="hljs-attr">country</span>: faker.location.country(),
        <span class="hljs-attr">zipCode</span>: faker.location.zipCode()
      },
      <span class="hljs-attr">phone</span>: faker.phone.number(),
      <span class="hljs-attr">isActive</span>: faker.datatype.boolean(<span class="hljs-number">0.9</span>), <span class="hljs-comment">// 90% active users</span>
      <span class="hljs-attr">registrationDate</span>: faker.date.past()
    });
  }

  <span class="hljs-keyword">return</span> users;
}
</code></pre>
<p>Using this technique, you can quickly generate large volumes of realistically behaving test data, especially for performance testing and making sure your application handles all kinds of data scenarios well.</p>
<h2 id="heading-how-to-manage-seed-scripts">How to Manage Seed Scripts</h2>
<p>A good seed script management system should give you flexibility in executing and maintaining your scripts. Here, you will develop one main seeding script that will initiate the entire seed process.</p>
<p>You’ll want to avoid unconditional seeding so that the existing data is not unintentionally overwritten.</p>
<p>Here is an example of how to do that:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// seeds/index.js</span>
<span class="hljs-keyword">const</span> seedUsers = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./scripts/seedUsers'</span>);
<span class="hljs-keyword">const</span> seedCategories = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./scripts/seedCategories'</span>);
<span class="hljs-keyword">const</span> seedProducts = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./scripts/seedProducts'</span>);
<span class="hljs-keyword">const</span> { db } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'../config/firebase'</span>);

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">clearCollection</span>(<span class="hljs-params">collectionName</span>) </span>{
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Clearing <span class="hljs-subst">${collectionName}</span> collection...`</span>);
  <span class="hljs-keyword">const</span> snapshot = <span class="hljs-keyword">await</span> db.collection(collectionName).get();
  <span class="hljs-keyword">const</span> batch = db.batch();

  snapshot.docs.forEach(<span class="hljs-function"><span class="hljs-params">doc</span> =&gt;</span> {
    batch.delete(doc.ref);
  });

  <span class="hljs-keyword">if</span> (snapshot.docs.length &gt; <span class="hljs-number">0</span>) {
    <span class="hljs-keyword">await</span> batch.commit();
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Cleared <span class="hljs-subst">${snapshot.docs.length}</span> documents from <span class="hljs-subst">${collectionName}</span>`</span>);
  }
}

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">runSeeds</span>(<span class="hljs-params">options = {}</span>) </span>{
  <span class="hljs-keyword">const</span> { clear = <span class="hljs-literal">false</span>, collections = [<span class="hljs-string">'users'</span>, <span class="hljs-string">'categories'</span>, <span class="hljs-string">'products'</span>] } = options;

  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">if</span> (clear) {
      <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> collection <span class="hljs-keyword">of</span> collections.reverse()) {
        <span class="hljs-keyword">await</span> clearCollection(collection);
      }
    }

    <span class="hljs-comment">// Run seeds in dependency order</span>
    <span class="hljs-keyword">if</span> (collections.includes(<span class="hljs-string">'users'</span>)) <span class="hljs-keyword">await</span> seedUsers();
    <span class="hljs-keyword">if</span> (collections.includes(<span class="hljs-string">'categories'</span>)) <span class="hljs-keyword">await</span> seedCategories();
    <span class="hljs-keyword">if</span> (collections.includes(<span class="hljs-string">'products'</span>)) <span class="hljs-keyword">await</span> seedProducts();

    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'All seeding completed successfully!'</span>);
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Seeding failed:'</span>, error);
    process.exit(<span class="hljs-number">1</span>);
  }
}

<span class="hljs-comment">// Command line interface</span>
<span class="hljs-keyword">if</span> (<span class="hljs-built_in">require</span>.main === <span class="hljs-built_in">module</span>) {
  <span class="hljs-keyword">const</span> args = process.argv.slice(<span class="hljs-number">2</span>);
  <span class="hljs-keyword">const</span> clear = args.includes(<span class="hljs-string">'--clear'</span>);
  <span class="hljs-keyword">const</span> collections = args.includes(<span class="hljs-string">'--collections'</span>) 
    ? args[args.indexOf(<span class="hljs-string">'--collections'</span>) + <span class="hljs-number">1</span>].split(<span class="hljs-string">','</span>) 
    : <span class="hljs-literal">undefined</span>;

  runSeeds({ clear, collections });
}

<span class="hljs-built_in">module</span>.exports = { runSeeds, clearCollection };
</code></pre>
<p>This management system provides a clean interface for running seeds with several options, such as clearing data or seeding some particular collections. The <a target="_blank" href="https://aws.amazon.com/what-is/cli/">CLI</a> can be easily plugged into npm package.json scripts and CI/CD pipelines.</p>
<p>Make sure you perform conditional seeding to avoid overwriting existing data:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">conditionalSeed</span>(<span class="hljs-params">collectionName, seedFunction</span>) </span>{
  <span class="hljs-keyword">const</span> snapshot = <span class="hljs-keyword">await</span> db.collection(collectionName).limit(<span class="hljs-number">1</span>).get();

  <span class="hljs-keyword">if</span> (snapshot.empty) {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`<span class="hljs-subst">${collectionName}</span> collection is empty, proceeding with seeding...`</span>);
    <span class="hljs-keyword">await</span> seedFunction();
  } <span class="hljs-keyword">else</span> {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`<span class="hljs-subst">${collectionName}</span> collection already contains data, skipping...`</span>);
  }
}
</code></pre>
<p>Here, the collections are checked for existing data before seeding, which helps prevent accidental data loss. It’s safe to run seed scripts more than once.</p>
<h2 id="heading-environment-specific-seeding">Environment-Specific Seeding</h2>
<p>You can make your seed system environment-aware by structuring environment-specific data sets and configurations. Use environment variables to decide which dataset will be used:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// seeds/data/index.js</span>
<span class="hljs-keyword">const</span> development = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./development'</span>);
<span class="hljs-keyword">const</span> staging = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./staging'</span>);
<span class="hljs-keyword">const</span> test = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./test'</span>);

<span class="hljs-keyword">const</span> data = {
  development,
  staging,
  test
};

<span class="hljs-built_in">module</span>.exports = data[process.env.NODE_ENV || <span class="hljs-string">'development'</span>];
</code></pre>
<p>You’ll create separate data files for each environment, with proper volumes and characteristics. Development environments should have minimal data that is nice and easy to understand, while staging environments can afford larger datasets that resemble production conditions better.</p>
<p>You can prevent accidental seeding via safety measures in production environments like this:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">safeProductionSeed</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">if</span> (process.env.NODE_ENV === <span class="hljs-string">'production'</span>) {
    <span class="hljs-keyword">const</span> confirmation = process.env.CONFIRM_PRODUCTION_SEED;
    <span class="hljs-keyword">if</span> (confirmation !== <span class="hljs-string">'YES_I_AM_SURE'</span>) {
      <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Production seeding requires explicit confirmation'</span>);
      process.exit(<span class="hljs-number">1</span>);
    }
  }

  <span class="hljs-comment">// Proceed with seeding...</span>
}
</code></pre>
<p>The protection requires an explicit confirmation to seed production databases, preventing accidental loss or corruption of data.</p>
<h2 id="heading-how-to-integrate-all-this-into-your-development-workflow">How to Integrate All This into Your Development Workflow</h2>
<p>Your seed scripts should ideally be integrated into your development workflow by adding suitable npm scripts to the package.json:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"scripts"</span>: {
    <span class="hljs-attr">"seed"</span>: <span class="hljs-string">"node seeds/index.js"</span>,
    <span class="hljs-attr">"seed:clear"</span>: <span class="hljs-string">"node seeds/index.js --clear"</span>,
    <span class="hljs-attr">"seed:users"</span>: <span class="hljs-string">"node seeds/index.js --collections users"</span>,
    <span class="hljs-attr">"seed:dev"</span>: <span class="hljs-string">"NODE_ENV=development npm run seed"</span>,
    <span class="hljs-attr">"seed:test"</span>: <span class="hljs-string">"NODE_ENV=test npm run seed:clear"</span>,
    <span class="hljs-attr">"dev"</span>: <span class="hljs-string">"npm run seed:dev &amp;&amp; npm start"</span>,
    <span class="hljs-attr">"test"</span>: <span class="hljs-string">"npm run seed:test &amp;&amp; npm run test:unit"</span>
  }
}
</code></pre>
<p>The scripts provide an easy way of seeding data for various common task scenarios and for plugging into the Dev and Test workflows. The <code>dev</code> script automatically seeds the database before starting the development server, ensuring developers always work with fresh, consistent data.</p>
<h2 id="heading-how-to-document-your-seeding-practices">How to Document Your Seeding Practices</h2>
<p>Proper documentation will really help your team and make long-term maintenance of your seeding system easier. Without it, your team members may have to search for the commands to run or squander time trying to figure out what data exists for a certain environment. Even worse, they might make ill-advised changes to the seed files.</p>
<p>Good documentation should answer three questions: How do I use the seeding system? What data exists and why? How do I extend or safely modify the system? Creating extensive documentation that addresses these items is our goal.</p>
<h3 id="heading-create-a-seeding-guide">Create a Seeding Guide</h3>
<p>Let's begin by creating a documentation file for the seeding system. This file should be placed in the root directory of the project so it’s always easy for team members to find.</p>
<pre><code class="lang-markdown"><span class="hljs-section"># Database Seeding Guide</span>

<span class="hljs-section">## Seed Commands</span>
<span class="hljs-bullet">-</span> To seed the database with fresh data for development: <span class="hljs-code">`npm run seed`</span>
<span class="hljs-bullet">-</span> To clear all existing data and reseed completly: <span class="hljs-code">`npm run seed:clear`</span>
<span class="hljs-bullet">-</span> To seed only the users collection: <span class="hljs-code">`npm run seed:users`</span>
<span class="hljs-bullet">-</span> To seed at a development data volume: <span class="hljs-code">`npm run seed:dev`</span>
<span class="hljs-bullet">-</span> To seed at a production data volume: <span class="hljs-code">`npm run seed:staging`</span>

<span class="hljs-section">## Environment Data Sets</span>
<span class="hljs-bullet">-</span> <span class="hljs-strong">**Development**</span>: 10-50 records per collection for fast local testing quick iteration
<span class="hljs-bullet">-</span> <span class="hljs-strong">**Staging**</span>: 100-1000 records for production-like-load-testing and performance evaluation
<span class="hljs-bullet">-</span> <span class="hljs-strong">**Test**</span>: Stripped-down controlled data specially designed for automated testing scenarios

<span class="hljs-section">## Collection Dependencies</span>
Our seeding system respects data relationships by running in this specific order:
<span class="hljs-bullet">1.</span> Categories (no dependencies) - Product categories must first exist
<span class="hljs-bullet">2.</span> Users (no dependencies) - User accounts are independent
<span class="hljs-bullet">3.</span> Products (requires Categories) - Each product looks up a category
<span class="hljs-bullet">4.</span> Orders (requires Users and Products) - Orders look up users and products

<span class="hljs-section">## Safety-Features</span>
<span class="hljs-bullet">-</span> Check automatically if there already is data that is about to seed to prevent accidental overwrites
<span class="hljs-bullet">-</span> Production environment needs explicit confirmation with CONFIRM<span class="hljs-emphasis">_PRODUCTION_</span>SEED=YES<span class="hljs-emphasis">_I_</span>AM<span class="hljs-emphasis">_SURE
- All database operations use atomic batch writes to guarantee consistency
- Conditional seeding makes sure duplicate data is not created when running scripts multiple times

### Adding New Seed Data
1. Add your data under `/seeds/data/[collection].js`
2. If your new data has relationships, update the corresponding seeding script
3. Test thoroughly in your development environment
4. Run the automated tests to verify data integrity
5. Update this documentation accordingly if you add commands or data descriptions</span>
</code></pre>
<p>This documentation format provides immediate answers to common questions team members might have. The commands give a copy-pastable set of instructions, while the environment descriptions allow developers to know what to expect from each setting.</p>
<p>The dependencies section is vital because it prevents team members from unknowingly breaking associations by running seeds in an incorrect order. The safety features section makes sure people have confidence that the system won't accidentally delete important data.</p>
<h2 id="heading-environment-configuration-documentation">Environment Configuration Documentation</h2>
<p>An environment variable may be confusing and troublesome if it is not properly documented. So you should create a template detailing exactly what's needed and why each variable matters.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Firebase Service Account Configuration</span>
<span class="hljs-comment"># Get these values from Firebase Console &gt; Project Settings &gt; Service Accounts</span>
FIREBASE_PROJECT_ID=your-project-id
FIREBASE_PRIVATE_KEY=<span class="hljs-string">"-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n"</span>
FIREBASE_CLIENT_EMAIL=firebase-adminsdk-xxx@project.iam.gserviceaccount.com
FIREBASE_PRIVATE_KEY_ID=your-private-key-id
FIREBASE_CLIENT_ID=your-client-id

<span class="hljs-comment"># Application Environment</span>
<span class="hljs-comment"># Controls which data set is used (development/staging/test/production)</span>
NODE_ENV=development

<span class="hljs-comment"># Production Safety Flag - ONLY set this in production environments</span>
<span class="hljs-comment"># This prevents accidental seeding of production databases</span>
<span class="hljs-comment"># CONFIRM_PRODUCTION_SEED=YES_I_AM_SURE</span>

<span class="hljs-comment"># Optional: Customize data volumes per environment</span>
<span class="hljs-comment"># SEED_USER_COUNT=50</span>
<span class="hljs-comment"># SEED_PRODUCT_COUNT=200</span>
</code></pre>
<p>This is where <code>.env.example</code> comes into its own. It shows developers exactly what variables they need to set, gives context about where to find the values, and adds safety warnings about production usage. In the comments, it doesn't just disclose what the variable should do, but also tells why we need it and how to obtain the values.</p>
<h2 id="heading-how-to-write-automated-tests-for-seed-data">How to Write Automated Tests for Seed Data</h2>
<p>Testing your seed scripts might seem unnecessary, but it becomes critical as your application grows. Without tests, changes to your data structure might break the seeding system, relationships might not be maintained correctly, and your seed data might get outdated as the application evolves.</p>
<p><a target="_blank" href="https://www.geeksforgeeks.org/software-testing/automation-testing-software-testing/">Automated tests</a> on seed data test for three key things: making sure the raw data files have the proper information, the process for seeding records is actually working, and relationships between data are kept intact. Let's create a full-fledged testing suite to cover all these scenarios.</p>
<h3 id="heading-install-testing-dependencies">Install Testing Dependencies</h3>
<p>Before writing tests, you'll need <a target="_blank" href="https://www.npmjs.com/package/jest">Jest</a> as your testing framework. Jest supports async operations very well, which is necessary when writing tests against databases.</p>
<pre><code class="lang-bash">npm install --save-dev jest
</code></pre>
<p>Since it supports promises and async/await, Jest is well-suited to test Firebase operations. But you will need to configure it for your particular Firebase setup. You'll learn how to do that in the following sections.</p>
<h3 id="heading-test-seed-data-structure">Test Seed Data Structure</h3>
<p>The first kind of tests verify if your seed actually works and creates data with the right structure. These tests run the actual seed scripts and check the database to see if things were created as expected.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> { db } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'../config/firebase'</span>);
<span class="hljs-keyword">const</span> { runSeeds, clearCollection } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'../seeds/index'</span>);

describe(<span class="hljs-string">'Seed Data Tests'</span>, <span class="hljs-function">() =&gt;</span> {
  beforeAll(<span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-comment">// Ensure we're using test environment to avoid affecting other data</span>
    process.env.NODE_ENV = <span class="hljs-string">'test'</span>;
    <span class="hljs-comment">// Start with a clean slate by clearing and reseeding all data</span>
    <span class="hljs-keyword">await</span> runSeeds({ <span class="hljs-attr">clear</span>: <span class="hljs-literal">true</span> });
  });

  afterAll(<span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-comment">// Clean up test data to avoid cluttering the test database</span>
    <span class="hljs-keyword">await</span> clearCollection(<span class="hljs-string">'users'</span>);
    <span class="hljs-keyword">await</span> clearCollection(<span class="hljs-string">'categories'</span>); 
    <span class="hljs-keyword">await</span> clearCollection(<span class="hljs-string">'products'</span>);
  });

  test(<span class="hljs-string">'users collection has correct structure'</span>, <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">const</span> snapshot = <span class="hljs-keyword">await</span> db.collection(<span class="hljs-string">'users'</span>).limit(<span class="hljs-number">1</span>).get();
    expect(snapshot.empty).toBe(<span class="hljs-literal">false</span>);

    <span class="hljs-keyword">const</span> user = snapshot.docs[<span class="hljs-number">0</span>].data();
    expect(user).toHaveProperty(<span class="hljs-string">'email'</span>);
    expect(user).toHaveProperty(<span class="hljs-string">'firstName'</span>);
    expect(user).toHaveProperty(<span class="hljs-string">'lastName'</span>);
    expect(user).toHaveProperty(<span class="hljs-string">'role'</span>);
    expect(user).toHaveProperty(<span class="hljs-string">'createdAt'</span>);
    expect(user).toHaveProperty(<span class="hljs-string">'updatedAt'</span>);
  });

  test(<span class="hljs-string">'products maintain referential integrity with categories'</span>, <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">const</span> [productsSnapshot, categoriesSnapshot] = <span class="hljs-keyword">await</span> <span class="hljs-built_in">Promise</span>.all([
      db.collection(<span class="hljs-string">'products'</span>).get(),
      db.collection(<span class="hljs-string">'categories'</span>).get()
    ]);

    <span class="hljs-keyword">const</span> categoryIds = categoriesSnapshot.docs.map(<span class="hljs-function"><span class="hljs-params">doc</span> =&gt;</span> doc.id);

    productsSnapshot.docs.forEach(<span class="hljs-function"><span class="hljs-params">productDoc</span> =&gt;</span> {
      <span class="hljs-keyword">const</span> product = productDoc.data();
      expect(product).toHaveProperty(<span class="hljs-string">'categoryId'</span>);
      expect(categoryIds).toContain(product.categoryId);
    });
  });

  test(<span class="hljs-string">'seed scripts handle existing data correctly'</span>, <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-comment">// Get initial count after first seeding</span>
    <span class="hljs-keyword">const</span> initialSnapshot = <span class="hljs-keyword">await</span> db.collection(<span class="hljs-string">'users'</span>).get();
    <span class="hljs-keyword">const</span> initialCount = initialSnapshot.size;

    <span class="hljs-comment">// Run seeds again - should not create duplicates</span>
    <span class="hljs-keyword">await</span> runSeeds({ <span class="hljs-attr">collections</span>: [<span class="hljs-string">'users'</span>] });

    <span class="hljs-keyword">const</span> finalSnapshot = <span class="hljs-keyword">await</span> db.collection(<span class="hljs-string">'users'</span>).get();
    expect(finalSnapshot.size).toBe(initialCount);
  });
});
</code></pre>
<p>There are three basic things that these tests check for your seed system. The structure test makes sure seeded documents have all the necessary fields – if you add a required field to your application but fail to update the seed data, this test will alert you.</p>
<p>The referential integrity test is vital to enforce the intended relationships between the data. It makes sure that every product actually references a category existing in the database. If you don't have this test, you can accidentally create orphaned records that break the application.</p>
<p>The duplicate-handling test preserves the <a target="_blank" href="https://www.freecodecamp.org/news/idempotence-explained/">idempotency</a> of your seeding system-it can be executed multiple times without duplicate data being generated. This is important since developers often resettle their local databases in their development workflow.</p>
<h3 id="heading-test-raw-seed-data-files">Test Raw Seed Data Files</h3>
<p>Before putting your raw seed data into the database, it should be tested. Such checks let you catch problems with the data itself before they cause issues in your application.</p>
<p>These validation tests will resolve most data-quality concerns before they reach your database. That is, email verification ensures every user email is in correct format-otherwise the users would face authentication issues later. Role verification would also prevent misspellings of assignment names that could destroy your authorization system.</p>
<p>Category reference is very important for enforcing data-level relationships before seeding even starts. If someone adds a product referencing a non-existent category, this test will blow up immediately.</p>
<p>The duplicate email test addresses a common issue where a user can accidentally be assigned the same email address, which violates any unique constraints in your application.</p>
<h3 id="heading-add-test-scripts-to-packagejson">Add Test Scripts to package.json</h3>
<p>Adding npm scripts will make your tests easier to run. Testing then becomes a part of your regular development workflow.</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"scripts"</span>: {
    <span class="hljs-attr">"test:seeds"</span>: <span class="hljs-string">"jest tests/seedData.test.js"</span>,
    <span class="hljs-attr">"test:seed-validation"</span>: <span class="hljs-string">"jest tests/seedDataValidation.test.js"</span>,
    <span class="hljs-attr">"test:all-seeds"</span>: <span class="hljs-string">"npm run test:seed-validation &amp;&amp; npm run test:seeds"</span>,
    <span class="hljs-attr">"dev:safe"</span>: <span class="hljs-string">"npm run test:seed-validation &amp;&amp; npm run seed:dev &amp;&amp; npm start"</span>
  }
}
</code></pre>
<p>Here, <code>test:all-seeds</code> runs both sets of tests in the right order-from checking the raw data all the way to testing the seeding process. <code>dev:safe</code> is an example seed test integration into the developer flow – seed testing is assured before you run the development server.</p>
<h3 id="heading-create-jest-configuration">Create Jest Configuration</h3>
<p>Set up Jest to best accommodate Firebase operations, which tend to be longer than typical unit tests and creation of special timeouts.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// jest.config.js</span>
<span class="hljs-built_in">module</span>.exports = {
  <span class="hljs-attr">testEnvironment</span>: <span class="hljs-string">'node'</span>,
  <span class="hljs-attr">testTimeout</span>: <span class="hljs-number">30000</span>, <span class="hljs-comment">// Firebase operations can be slow, especially batch writes</span>
  <span class="hljs-attr">setupFilesAfterEnv</span>: [<span class="hljs-string">'&lt;rootDir&gt;/tests/setup.js'</span>],
  <span class="hljs-comment">// Only run test files, ignore seed data files</span>
  <span class="hljs-attr">testMatch</span>: [<span class="hljs-string">'**/tests/**/*.test.js'</span>]
};
</code></pre>
<pre><code class="lang-javascript"><span class="hljs-comment">// tests/setup.js</span>
<span class="hljs-comment">// Global test configuration that applies to all test files</span>

<span class="hljs-comment">// Ensure all tests run in test environment</span>
process.env.NODE_ENV = <span class="hljs-string">'test'</span>;

<span class="hljs-comment">// Increase timeout for Firebase operations</span>
jest.setTimeout(<span class="hljs-number">30000</span>);

<span class="hljs-comment">// Optional: Add global test utilities</span>
<span class="hljs-built_in">global</span>.testDb = <span class="hljs-built_in">require</span>(<span class="hljs-string">'../config/firebase'</span>).db;
</code></pre>
<p>This configuration entails setting a longer timeout for the tests since Firebase operations, especially batch writes, can take a number of seconds. Also, this setup file makes sure all the tests run in the test environment so that you don’t mistakenly alter any development data.</p>
<p>JestConfig also states that files with a name ending in <code>.test.js</code> are the only ones to be considered tests, preventing Jest from considering your seed data files as tests.</p>
<p>A complete test and documentation will transform your seeding system from a mere utility to a solid, maintainable component of your development infrastructure. Documentation serves to empower the team to use this system with confidence, while tests identify issues before they trickle into development or production environments.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Seed files are a crucial element in a modern application's building blocks that ensure a uniform, reproducible development environment for samples. When you implement advanced seeding processes using a combination of Firebase and Node.js, you get a potent system that acts as a development accelerator, fostering testing reliability and consistency among your team members.</p>
<p>The methods discussed in this article, from basic file management up to intricate relationship handling and environment-specific configurations, provide you with the framework necessary to implement seed files effectively in your Firebase Node.js setups. As your application grows, these patterns will grow with you, supporting anything from just a simple development environment to some really complex multi-environment deployments.</p>
<p>You can explore the <a target="_blank" href="https://firebase.google.com/docs/data-connect/data-seeding-bulk-operations">official seed docs</a> to see more advanced seeding patterns and examples. You can also reach out to <a target="_blank" href="topefasasi99@gmail.com">me</a> for any questions or collaboration.</p>
<p>I hope you found this guide helpful! 🙂</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Integrate Firebase into Your Flutter Applications: A Handbook for Developers ]]>
                </title>
                <description>
                    <![CDATA[ In the world of software development, speed, scalability, and user experience are paramount. Flutter, with its expressive UI toolkit and native compilation, offers an unparalleled frontend experience, while Firebase, Google's robust Backend-as-a-Serv... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-integrate-firebase-into-your-flutter-applications-a-handbook-for-developers/</link>
                <guid isPermaLink="false">688271806aa78fcca439436b</guid>
                
                    <category>
                        <![CDATA[ Flutter ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Firebase ]]>
                    </category>
                
                    <category>
                        <![CDATA[ flutter-aware ]]>
                    </category>
                
                    <category>
                        <![CDATA[ handbook ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Atuoha Anthony ]]>
                </dc:creator>
                <pubDate>Thu, 24 Jul 2025 17:46:40 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1753379188809/d37055e6-34cc-4b14-a70f-a412ffe69714.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In the world of software development, speed, scalability, and user experience are paramount. Flutter, with its expressive UI toolkit and native compilation, offers an unparalleled frontend experience, while Firebase, Google's robust Backend-as-a-Service (BaaS), provides the essential backend infrastructure.</p>
<p>The synergy between Flutter and Firebase allows developers to build feature-rich, high-performance applications with remarkable efficiency.</p>
<p>This article will give you a deep dive into integrating and leveraging a wide array of Firebase services within your Flutter applications. We'll explore the FlutterFire ecosystem, I’ll explain essential code snippets, and clarify how the Firebase Console serves as your primary management interface. You’ll also learn about the evolving concept of "Firebase Studio" as an advanced development environment.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>To gain the most from this deep dive into building cutting-edge Flutter applications with Firebase, there are some tools and key concepts you should know. This article assumes you have:</p>
<h3 id="heading-1-fundamental-programming-knowledge">1. Fundamental Programming Knowledge</h3>
<ul>
<li><p><strong>Basic Programming Concepts:</strong> Familiarity with concepts such as variables, data types, control flow (loops, conditionals), functions, and object-oriented programming (OOP) principles (classes, objects, inheritance, encapsulation).</p>
</li>
<li><p><strong>Dart Programming Language:</strong> As Flutter's primary language, a working knowledge of Dart syntax, asynchronous programming (Futures, async/await), and collection types (Lists, Maps) is essential. While this article will focus on application building, a prior grasp of Dart will significantly accelerate your learning.</p>
</li>
</ul>
<h3 id="heading-2-flutter-development-environment">2. Flutter Development Environment</h3>
<ul>
<li><p><strong>Flutter SDK Installed:</strong> You should have the Flutter SDK correctly installed and configured on your operating system (Windows, macOS, or Linux). This includes:</p>
<ul>
<li><p>The Flutter command-line tool (<code>flutter</code>).</p>
</li>
<li><p>Necessary supporting libraries and platform-specific SDKs (for example, Android SDK, Xcode for iOS development on macOS).</p>
</li>
<li><p>You can verify your setup by running <code>flutter doctor</code> in your terminal and resolving any reported issues.</p>
</li>
</ul>
</li>
<li><p><strong>Integrated Development Environment (IDE):</strong></p>
<ul>
<li><p><strong>VS Code (with Flutter and Dart extensions):</strong> Highly recommended for its lightweight nature and powerful extensions for Flutter development.</p>
</li>
<li><p><strong>Android Studio (with Flutter and Dart plugins):</strong> A robust option, especially if you're heavily involved in native Android development.</p>
</li>
</ul>
</li>
<li><p><strong>Device or Emulator Setup:</strong></p>
<ul>
<li><p><strong>Android Emulator:</strong> Configured and running via Android Studio.</p>
</li>
<li><p><strong>iOS Simulator (macOS only):</strong> Configured and running via Xcode.</p>
</li>
<li><p><strong>Physical Device:</strong> An Android or iOS device with USB debugging enabled for real-world testing.</p>
</li>
</ul>
</li>
<li><p><strong>Basic Flutter Application Development:</strong> You should be able to:</p>
<ul>
<li><p>Create a new Flutter project (<code>flutter create</code>).</p>
</li>
<li><p>Understand the basic widget tree structure (StatelessWidget, StatefulWidget).</p>
</li>
<li><p>Run a simple "Hello World" Flutter application on an emulator or physical device.</p>
</li>
</ul>
</li>
</ul>
<h3 id="heading-3-firebase-account-and-console-familiarity">3. Firebase Account and Console Familiarity</h3>
<ul>
<li><p><strong>Google Account:</strong> A valid Google account is required to access and use Firebase.</p>
</li>
<li><p><strong>Firebase Project:</strong> You should have a basic understanding of what a Firebase project is and how to create one via the <a target="_blank" href="https://console.firebase.google.com/">Firebase Console</a>.</p>
</li>
<li><p><strong>Familiarity with Firebase Console:</strong> Basic navigation and understanding of the different services available in the Firebase Console (for example, Authentication, Firestore, Storage).</p>
</li>
<li><p><strong>Firebase CLI (Command Line Interface):</strong> While not strictly required for every step, installing and being able to log in to the Firebase CLI (<code>firebase login</code>) is highly recommended for tasks like deploying Cloud Functions or interacting with your Firebase project from the terminal.</p>
</li>
<li><p><strong>FlutterFire CLI:</strong> For streamlined Firebase integration in Flutter, installing the FlutterFire CLI (<code>dart pub global activate flutterfire_cli</code>) is essential for commands like <code>flutterfire configure</code>.</p>
</li>
</ul>
<h3 id="heading-4-optional-but-recommended">4. Optional but Recommended</h3>
<ul>
<li><p><strong>Version Control (Git):</strong> Familiarity with Git for managing your project's codebase.</p>
</li>
<li><p><strong>Command Line Basics:</strong> Comfort with navigating directories and executing commands in your terminal.</p>
</li>
<li><p><strong>Cloud Concepts:</strong> A general understanding of cloud services, databases (NoSQL vs. SQL), and serverless computing will be beneficial but not strictly necessary to follow the core concepts.</p>
</li>
<li><p><strong>Firebase Studio:</strong> While you can follow this article without it, exploring Firebase Studio (formerly Project IDX) can significantly enhance your development experience by providing an AI-assisted, cloud-based IDE with deep Firebase integration.</p>
</li>
</ul>
<h2 id="heading-table-of-contents"><strong>Table of Contents:</strong></h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-1-the-foundation-setting-up-your-firebase-project-and-flutterfire">1. The Foundation: Setting Up Your Firebase Project and FlutterFire</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-2-deep-dive-into-core-firebase-services-with-flutter">2. Deep Dive into Core Firebase Services with Flutter</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-3-other-valuable-firebase-services-for-flutter">3. Other Valuable Firebase Services for Flutter</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-4-firebase-local-emulators-developing-offline-and-faster">4. Firebase Local Emulators: Developing Offline and Faster</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-5-continuous-integration-and-deployment-cicd-with-firebase-and-flutter">5. Continuous Integration and Deployment (CI/CD) with Firebase &amp; Flutter</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-references">References</a></p>
</li>
</ul>
<h2 id="heading-1-the-foundation-setting-up-your-firebase-project-and-flutterfire">1. The Foundation: Setting Up Your Firebase Project and FlutterFire</h2>
<p>Before diving into specific services, we need to establish the connection between your Flutter project and Firebase.</p>
<h3 id="heading-creating-a-firebase-project">Creating a Firebase Project:</h3>
<p>Your Firebase journey begins in the Firebase <a target="_blank" href="https://firebase.google.com/docs/studio">C</a>onsole (console.firebase.google.com). This web-based interface is where you'll create, configure, and monitor all your Firebase projects and services.</p>
<ol>
<li><p><strong>Navigate to the console:</strong> Open your web browser and go to <code>console.firebase.google.com</code>.</p>
</li>
<li><p><strong>Sign in:</strong> Use your Google account credentials.</p>
</li>
<li><p><strong>Create a new project:</strong> Click "Add project" or "Create a project."</p>
</li>
<li><p><strong>Add your project details:</strong></p>
<ul>
<li><p><strong>Project name:</strong> Choose a descriptive name (for example, "MyFlutterAwesomeApp").</p>
</li>
<li><p><strong>Project ID:</strong> Firebase automatically generates a unique ID. This ID is critical as it identifies your project across all Firebase and Google Cloud services. You can customize it if desired, ensuring it's globally unique.</p>
</li>
<li><p><strong>Google Analytics:</strong> Strongly recommended. Google Analytics provides vital insights into user behavior, app usage, and performance metrics, which are invaluable for optimizing your Flutter app.</p>
</li>
</ul>
</li>
<li><p><strong>Finalize Creation:</strong> Click "Create project." Firebase will provision the necessary cloud resources.</p>
</li>
</ol>
<h3 id="heading-integrating-flutterfire">Integrating FlutterFire:</h3>
<p>FlutterFire is the official suite of Firebase plugins for Flutter. The <code>flutterfire_cli</code> simplifies the setup process.</p>
<h4 id="heading-step-1-install-firebase-cli">Step 1: Install Firebase CLI</h4>
<p>If you haven't already, install the Firebase Command Line Interface globally via npm:</p>
<pre><code class="lang-bash">npm install -g firebase-tools
</code></pre>
<p>This tool allows you to interact with Firebase from your terminal, including project initialization and deployment.</p>
<h4 id="heading-step-2-log-in-to-firebase-cli">Step 2: Log In to Firebase CLI</h4>
<p>Authenticate your CLI with your Google account:</p>
<pre><code class="lang-bash">firebase login
</code></pre>
<h4 id="heading-step-3-install-flutterfire-cli">Step 3: Install FlutterFire CLI</h4>
<p>Activate the FlutterFire CLI globally using Dart's package manager:</p>
<pre><code class="lang-bash">dart pub global activate flutterfire_cli
</code></pre>
<p>This tool automates the platform-specific configuration for your Flutter app.</p>
<h4 id="heading-step-4-createnavigate-to-your-flutter-project">Step 4: Create/Navigate to your Flutter Project</h4>
<pre><code class="lang-bash">flutter create my_deep_dive_app
<span class="hljs-built_in">cd</span> my_deep_dive_app
</code></pre>
<h4 id="heading-step-5-configure-firebase-for-flutter">Step 5: Configure Firebase for Flutter</h4>
<p>Run the <code>flutterfire configure</code> command from your Flutter project's root directory.</p>
<pre><code class="lang-bash">flutterfire configure
</code></pre>
<p>Here’s what’s going on:</p>
<ul>
<li><p>This command is the magic wand. It interacts with your Firebase project, registers your Flutter app's Android, iOS, and Web platforms (you'll select which ones to enable), and automatically generates the <code>lib/firebase_options.dart</code> file.</p>
</li>
<li><p><code>firebase_options.dart</code> contains the platform-specific Firebase configuration details (API keys, project IDs, and so on) that your Flutter app needs to connect to Firebase. This eliminates manual configuration for each platform.</p>
</li>
</ul>
<h4 id="heading-step-6-add-the-firebasecore-dependency">Step 6: Add the <code>firebase_core</code> Dependency</h4>
<p>Open your <code>pubspec.yaml</code> file (located at the root of your Flutter project) and add <code>firebase_core</code> to your <code>dependencies</code>. This plugin is the foundational layer for all other Firebase services.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">dependencies:</span>
  <span class="hljs-attr">flutter:</span>
    <span class="hljs-attr">sdk:</span> <span class="hljs-string">flutter</span>
  <span class="hljs-attr">firebase_core:</span> <span class="hljs-string">^2.x.x</span> <span class="hljs-comment"># Use the latest stable version from pub.dev</span>
</code></pre>
<p>Run <code>flutter pub get</code> in your terminal to fetch the new dependency.</p>
<h4 id="heading-step-7-initialize-firebase-in-maindart">Step 7: Initialize Firebase in <code>main.dart</code></h4>
<p>Before your Flutter application runs, you must initialize Firebase. You typically do this in the <code>main</code> function.</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:firebase_core/firebase_core.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'firebase_options.dart'</span>; <span class="hljs-comment">// This file is generated by `flutterfire configure`</span>

Future&lt;<span class="hljs-keyword">void</span>&gt; main() <span class="hljs-keyword">async</span> {
  <span class="hljs-comment">// Ensures that Flutter's widget binding is initialized before Firebase is initialized.</span>
  <span class="hljs-comment">// This is crucial for asynchronous operations like Firebase.initializeApp().</span>
  WidgetsFlutterBinding.ensureInitialized();

  <span class="hljs-comment">// Initializes Firebase for the current platform.</span>
  <span class="hljs-comment">// DefaultFirebaseOptions.currentPlatform uses the configuration from firebase_options.dart</span>
  <span class="hljs-comment">// based on whether the app is running on Android, iOS, or Web.</span>
  <span class="hljs-keyword">await</span> Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );

  runApp(<span class="hljs-keyword">const</span> MyApp());
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyApp</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-keyword">const</span> MyApp({<span class="hljs-keyword">super</span>.key});

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> MaterialApp(
      title: <span class="hljs-string">'Flutter Firebase Deep Dive'</span>,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: <span class="hljs-keyword">const</span> AuthWrapper(), <span class="hljs-comment">// We'll use this for authentication flow</span>
    );
  }
}

<span class="hljs-comment">// Placeholder for AuthWrapper which will manage authentication state</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AuthWrapper</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-keyword">const</span> AuthWrapper({<span class="hljs-keyword">super</span>.key});

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> Scaffold(
      appBar: AppBar(title: <span class="hljs-keyword">const</span> Text(<span class="hljs-string">'Firebase Deep Dive'</span>)),
      body: <span class="hljs-keyword">const</span> Center(
        child: Text(<span class="hljs-string">'Firebase Initialized. Ready for action!'</span>),
      ),
    );
  }
}
</code></pre>
<p>Here’s what’s going on:</p>
<ul>
<li><p><code>WidgetsFlutterBinding.ensureInitialized()</code>: This line is vital. It makes sure that the Flutter engine is fully initialized before attempting to perform any asynchronous operations, such as calling <code>Firebase.initializeApp()</code>.</p>
</li>
<li><p><code>await Firebase.initializeApp(...)</code>: This is the core Firebase initialization. It sets up the connection to your Firebase project.</p>
</li>
<li><p><code>DefaultFirebaseOptions.currentPlatform</code>: This static property from the generated <code>firebase_options.dart</code> file automatically selects the correct Firebase configuration for the platform your Flutter app is currently running on (iOS, Android, or Web).</p>
</li>
</ul>
<h2 id="heading-2-deep-dive-into-core-firebase-services-with-flutter">2. Deep Dive into Core Firebase Services with Flutter</h2>
<p>Now, let's explore the individual Firebase services and how to interact with them in Flutter. For each service, you'll typically add a new FlutterFire plugin to your <code>pubspec.yaml</code> and then enable the service in the Firebase Console.</p>
<h3 id="heading-firebase-authentication-identity-management-made-easy">Firebase Authentication: Identity Management Made Easy</h3>
<p>Firebase Authentication simplifies user authentication, offering various methods without requiring you to manage backend servers. Let’s walk through the setup now.</p>
<h4 id="heading-step-1-add-dependency">Step 1: Add Dependency</h4>
<pre><code class="lang-yaml"><span class="hljs-attr">dependencies:</span>
  <span class="hljs-comment"># ...</span>
  <span class="hljs-attr">firebase_auth:</span> <span class="hljs-string">^latest_version</span> <span class="hljs-comment"># Check pub.dev for the latest</span>
</code></pre>
<p>Run <code>flutter pub get</code>.</p>
<h4 id="heading-step-2-enable-providers-firebase-console">Step 2: Enable Providers (Firebase Console)</h4>
<p>Go to "Authentication" -&gt; "Sign-in method." Enable desired providers like "Email/Password," "Google," "Facebook," and so on. Follow the on-screen instructions for each (for example, providing API keys for social providers).</p>
<p>Here’s the code. It’s a lot, so I’ve added comments and explained key points after:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:firebase_auth/firebase_auth.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>; <span class="hljs-comment">// For UI elements like SnackBar</span>

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AuthService</span> </span>{
  <span class="hljs-keyword">final</span> FirebaseAuth _auth = FirebaseAuth.instance;

  <span class="hljs-comment">// Stream to listen to authentication state changes (User logged in/out)</span>
  Stream&lt;User?&gt; <span class="hljs-keyword">get</span> user {
    <span class="hljs-keyword">return</span> _auth.authStateChanges();
  }

  <span class="hljs-comment">// Register with Email and Password</span>
  Future&lt;User?&gt; registerWithEmailAndPassword(<span class="hljs-built_in">String</span> email, <span class="hljs-built_in">String</span> password) <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">try</span> {
      <span class="hljs-comment">// Creates a new user account with the provided email and password.</span>
      <span class="hljs-comment">// On success, returns a UserCredential object containing the newly created user.</span>
      UserCredential result = <span class="hljs-keyword">await</span> _auth.createUserWithEmailAndPassword(
        email: email,
        password: password,
      );
      <span class="hljs-keyword">return</span> result.user; <span class="hljs-comment">// Return the User object</span>
    } <span class="hljs-keyword">on</span> FirebaseAuthException <span class="hljs-keyword">catch</span> (e) {
      <span class="hljs-comment">// Catch specific Firebase authentication exceptions for better error handling</span>
      <span class="hljs-built_in">print</span>(<span class="hljs-string">"FirebaseAuthException during registration: <span class="hljs-subst">${e.code}</span> - <span class="hljs-subst">${e.message}</span>"</span>);
      <span class="hljs-comment">// You can display a user-friendly message based on e.code</span>
      <span class="hljs-keyword">if</span> (e.code == <span class="hljs-string">'weak-password'</span>) {
        <span class="hljs-comment">// Handle weak password</span>
      } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (e.code == <span class="hljs-string">'email-already-in-use'</span>) {
        <span class="hljs-comment">// Handle email already registered</span>
      }
      <span class="hljs-keyword">return</span> <span class="hljs-keyword">null</span>;
    } <span class="hljs-keyword">catch</span> (e) {
      <span class="hljs-comment">// Catch any other general exceptions</span>
      <span class="hljs-built_in">print</span>(<span class="hljs-string">"General error during registration: <span class="hljs-subst">$e</span>"</span>);
      <span class="hljs-keyword">return</span> <span class="hljs-keyword">null</span>;
    }
  }

  <span class="hljs-comment">// Sign in with Email and Password</span>
  Future&lt;User?&gt; signInWithEmailAndPassword(<span class="hljs-built_in">String</span> email, <span class="hljs-built_in">String</span> password) <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">try</span> {
      <span class="hljs-comment">// Signs in an existing user with email and password.</span>
      UserCredential result = <span class="hljs-keyword">await</span> _auth.signInWithEmailAndPassword(
        email: email,
        password: password,
      );
      <span class="hljs-keyword">return</span> result.user;
    } <span class="hljs-keyword">on</span> FirebaseAuthException <span class="hljs-keyword">catch</span> (e) {
      <span class="hljs-built_in">print</span>(<span class="hljs-string">"FirebaseAuthException during sign-in: <span class="hljs-subst">${e.code}</span> - <span class="hljs-subst">${e.message}</span>"</span>);
      <span class="hljs-keyword">if</span> (e.code == <span class="hljs-string">'user-not-found'</span>) {
        <span class="hljs-comment">// Handle no user found</span>
      } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (e.code == <span class="hljs-string">'wrong-password'</span>) {
        <span class="hljs-comment">// Handle incorrect password</span>
      }
      <span class="hljs-keyword">return</span> <span class="hljs-keyword">null</span>;
    } <span class="hljs-keyword">catch</span> (e) {
      <span class="hljs-built_in">print</span>(<span class="hljs-string">"General error during sign-in: <span class="hljs-subst">$e</span>"</span>);
      <span class="hljs-keyword">return</span> <span class="hljs-keyword">null</span>;
    }
  }

  <span class="hljs-comment">// Sign out</span>
  Future&lt;<span class="hljs-keyword">void</span>&gt; signOut() <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">try</span> {
      <span class="hljs-comment">// Signs out the currently authenticated user.</span>
      <span class="hljs-keyword">await</span> _auth.signOut();
      <span class="hljs-built_in">print</span>(<span class="hljs-string">"User signed out successfully."</span>);
    } <span class="hljs-keyword">catch</span> (e) {
      <span class="hljs-built_in">print</span>(<span class="hljs-string">"Error signing out: <span class="hljs-subst">$e</span>"</span>);
    }
  }

  <span class="hljs-comment">// Example: Google Sign-In (requires additional setup for iOS/Android/Web)</span>
  <span class="hljs-comment">// This is a simplified example, a full implementation involves more steps</span>
  <span class="hljs-comment">// with google_sign_in package.</span>
  Future&lt;User?&gt; signInWithGoogle() <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">try</span> {
      <span class="hljs-comment">// Trigger the Google Sign-In flow</span>
      <span class="hljs-comment">// final GoogleSignInAccount? googleUser = await GoogleSignIn().signIn();</span>
      <span class="hljs-comment">// final GoogleSignInAuthentication googleAuth = await googleUser!.authentication;</span>

      <span class="hljs-comment">// Create a new credential</span>
      <span class="hljs-comment">// final OAuthCredential credential = GoogleAuthProvider.credential(</span>
      <span class="hljs-comment">//   accessToken: googleAuth.accessToken,</span>
      <span class="hljs-comment">//   idToken: googleAuth.idToken,</span>
      <span class="hljs-comment">// );</span>

      <span class="hljs-comment">// Sign in to Firebase with the credential</span>
      <span class="hljs-comment">// UserCredential result = await _auth.signInWithCredential(credential);</span>
      <span class="hljs-comment">// return result.user;</span>
      <span class="hljs-built_in">print</span>(<span class="hljs-string">"Google Sign-In not fully implemented in this snippet, requires google_sign_in package."</span>);
      <span class="hljs-keyword">return</span> <span class="hljs-keyword">null</span>;
    } <span class="hljs-keyword">catch</span> (e) {
      <span class="hljs-built_in">print</span>(<span class="hljs-string">"Error with Google Sign-In: <span class="hljs-subst">$e</span>"</span>);
      <span class="hljs-keyword">return</span> <span class="hljs-keyword">null</span>;
    }
  }
}

<span class="hljs-comment">// Example of how AuthWrapper might look to manage navigation based on auth state</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AuthWrapper</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-keyword">const</span> AuthWrapper({<span class="hljs-keyword">super</span>.key});

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-comment">// Access the user stream from AuthService</span>
    <span class="hljs-keyword">return</span> StreamBuilder&lt;User?&gt;(
      stream: AuthService().user, <span class="hljs-comment">// Listen to authentication state changes</span>
      builder: (context, snapshot) {
        <span class="hljs-keyword">if</span> (snapshot.connectionState == ConnectionState.waiting) {
          <span class="hljs-comment">// While waiting for the authentication state to be determined, show a loading spinner</span>
          <span class="hljs-keyword">return</span> <span class="hljs-keyword">const</span> Scaffold(body: Center(child: CircularProgressIndicator()));
        } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (snapshot.hasData) {
          <span class="hljs-comment">// If there is user data, the user is signed in</span>
          <span class="hljs-keyword">return</span> <span class="hljs-keyword">const</span> HomeScreen(); <span class="hljs-comment">// Navigate to your main app screen</span>
        } <span class="hljs-keyword">else</span> {
          <span class="hljs-comment">// If there is no user data, the user is signed out</span>
          <span class="hljs-keyword">return</span> <span class="hljs-keyword">const</span> SignInScreen(); <span class="hljs-comment">// Navigate to your sign-in screen</span>
        }
      },
    );
  }
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SignInScreen</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-keyword">const</span> SignInScreen({<span class="hljs-keyword">super</span>.key});

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">final</span> AuthService _auth = AuthService();
    <span class="hljs-keyword">final</span> TextEditingController _emailController = TextEditingController();
    <span class="hljs-keyword">final</span> TextEditingController _passwordController = TextEditingController();

    <span class="hljs-keyword">return</span> Scaffold(
      appBar: AppBar(title: <span class="hljs-keyword">const</span> Text(<span class="hljs-string">'Sign In'</span>)),
      body: Padding(
        padding: <span class="hljs-keyword">const</span> EdgeInsets.all(<span class="hljs-number">16.0</span>),
        child: Column(
          children: [
            TextField(controller: _emailController, decoration: <span class="hljs-keyword">const</span> InputDecoration(labelText: <span class="hljs-string">'Email'</span>)),
            TextField(controller: _passwordController, decoration: <span class="hljs-keyword">const</span> InputDecoration(labelText: <span class="hljs-string">'Password'</span>), obscureText: <span class="hljs-keyword">true</span>),
            ElevatedButton(
              onPressed: () <span class="hljs-keyword">async</span> {
                User? user = <span class="hljs-keyword">await</span> _auth.signInWithEmailAndPassword(_emailController.text, _passwordController.text);
                <span class="hljs-keyword">if</span> (user != <span class="hljs-keyword">null</span>) {
                  ScaffoldMessenger.of(context).showSnackBar(<span class="hljs-keyword">const</span> SnackBar(content: Text(<span class="hljs-string">'Signed In Successfully!'</span>)));
                } <span class="hljs-keyword">else</span> {
                  ScaffoldMessenger.of(context).showSnackBar(<span class="hljs-keyword">const</span> SnackBar(content: Text(<span class="hljs-string">'Sign In Failed.'</span>)));
                }
              },
              child: <span class="hljs-keyword">const</span> Text(<span class="hljs-string">'Sign In'</span>),
            ),
            ElevatedButton(
              onPressed: () <span class="hljs-keyword">async</span> {
                User? user = <span class="hljs-keyword">await</span> _auth.registerWithEmailAndPassword(_emailController.text, _passwordController.text);
                <span class="hljs-keyword">if</span> (user != <span class="hljs-keyword">null</span>) {
                  ScaffoldMessenger.of(context).showSnackBar(<span class="hljs-keyword">const</span> SnackBar(content: Text(<span class="hljs-string">'Registered Successfully!'</span>)));
                } <span class="hljs-keyword">else</span> {
                  ScaffoldMessenger.of(context).showSnackBar(<span class="hljs-keyword">const</span> SnackBar(content: Text(<span class="hljs-string">'Registration Failed.'</span>)));
                }
              },
              child: <span class="hljs-keyword">const</span> Text(<span class="hljs-string">'Register'</span>),
            ),
            <span class="hljs-comment">// Add Google Sign-In button here (requires google_sign_in package)</span>
            <span class="hljs-comment">// ElevatedButton(</span>
            <span class="hljs-comment">//   onPressed: () async {</span>
            <span class="hljs-comment">//     User? user = await _auth.signInWithGoogle();</span>
            <span class="hljs-comment">//     if (user != null) {</span>
            <span class="hljs-comment">//       ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Signed In with Google!')));</span>
            <span class="hljs-comment">//     } else {</span>
            <span class="hljs-comment">//       ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Google Sign In Failed.')));</span>
            <span class="hljs-comment">//     }</span>
            <span class="hljs-comment">//   },</span>
            <span class="hljs-comment">//   child: const Text('Sign In with Google'),</span>
            <span class="hljs-comment">// ),</span>
          ],
        ),
      ),
    );
  }
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HomeScreen</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-keyword">const</span> HomeScreen({<span class="hljs-keyword">super</span>.key});

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">final</span> AuthService _auth = AuthService();
    <span class="hljs-keyword">final</span> User? currentUser = FirebaseAuth.instance.currentUser; <span class="hljs-comment">// Get current user</span>

    <span class="hljs-keyword">return</span> Scaffold(
      appBar: AppBar(
        title: <span class="hljs-keyword">const</span> Text(<span class="hljs-string">'Home Screen'</span>),
        actions: [
          IconButton(
            icon: <span class="hljs-keyword">const</span> Icon(Icons.logout),
            onPressed: () <span class="hljs-keyword">async</span> {
              <span class="hljs-keyword">await</span> _auth.signOut();
            },
          ),
        ],
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(<span class="hljs-string">'Welcome, <span class="hljs-subst">${currentUser?.email ?? <span class="hljs-string">'Guest'</span>}</span>!'</span>),
            <span class="hljs-keyword">const</span> SizedBox(height: <span class="hljs-number">20</span>),
            ElevatedButton(
              onPressed: () {
                <span class="hljs-comment">// Navigate to other parts of your app</span>
                <span class="hljs-built_in">print</span>(<span class="hljs-string">'Navigate to profile or settings'</span>);
              },
              child: <span class="hljs-keyword">const</span> Text(<span class="hljs-string">'Go to Profile'</span>),
            ),
          ],
        ),
      ),
    );
  }
}
</code></pre>
<p>Key concepts in this auth code:</p>
<ul>
<li><p><code>FirebaseAuth.instance</code>: The singleton instance of the Firebase Authentication service.</p>
</li>
<li><p><code>_auth.authStateChanges()</code>: A Dart <code>Stream</code> that emits a <code>User</code> object whenever the user's sign-in state changes (for example, login, logout, registration). This is powerful for building reactive UIs that respond to authentication state.</p>
</li>
<li><p><code>createUserWithEmailAndPassword()</code>: Registers a new user. If successful, <code>result.user</code> contains the new <code>User</code> object.</p>
</li>
<li><p><code>signInWithEmailAndPassword()</code>: Authenticates an existing user.</p>
</li>
<li><p><code>signOut()</code>: Logs out the current user.</p>
</li>
<li><p><code>FirebaseAuthException</code>: Specific exceptions provided by Firebase for authentication errors (for example, <code>weak-password</code>, <code>email-already-in-use</code>, <code>user-not-found</code>). Catching these allows you to provide precise feedback to the user.</p>
</li>
<li><p><code>User</code> object: Represents the currently logged-in user, providing access to properties like <code>uid</code> (unique user ID), <code>email</code>, <code>displayName</code>, and so on. The <code>uid</code> is especially important for associating user data in Firestore or Realtime Database.</p>
</li>
</ul>
<h3 id="heading-cloud-firestore-real-time-nosql-database">Cloud Firestore: Real-time NoSQL Database</h3>
<p>Cloud Firestore is a flexible, scalable NoSQL document database for mobile, web, and server development. It offers real-time data synchronization and powerful querying capabilities. Here are the setup steps:</p>
<h4 id="heading-step-1-add-dependency-1">Step 1: Add Dependency</h4>
<pre><code class="lang-yaml"><span class="hljs-attr">dependencies:</span>
  <span class="hljs-comment"># ...</span>
  <span class="hljs-attr">cloud_firestore:</span> <span class="hljs-string">^latest_version</span> <span class="hljs-comment"># Check pub.dev for the latest</span>
</code></pre>
<p>Run <code>flutter pub get</code>.</p>
<h4 id="heading-step-2-enable-firestore-firebase-console">Step 2: Enable Firestore (Firebase Console)</h4>
<p>Go to "Firestore Database" and click "Create database." Choose a security rules mode (start in test mode for development, but <em>always</em> define stricter rules for production) and a location.</p>
<p>Here’s the code:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:cloud_firestore/cloud_firestore.dart'</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">FirestoreService</span> </span>{
  <span class="hljs-comment">// Get the singleton instance of Cloud Firestore</span>
  <span class="hljs-keyword">final</span> FirebaseFirestore _firestore = FirebaseFirestore.instance;

  <span class="hljs-comment">// Add a new user document</span>
  Future&lt;<span class="hljs-keyword">void</span>&gt; addUser(<span class="hljs-built_in">String</span> uid, <span class="hljs-built_in">String</span> email, <span class="hljs-built_in">String</span> username) <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">try</span> {
      <span class="hljs-comment">// Access the 'users' collection and create/set a document with the user's UID as its ID.</span>
      <span class="hljs-comment">// `set()` will create the document if it doesn't exist or overwrite it if it does.</span>
      <span class="hljs-keyword">await</span> _firestore.collection(<span class="hljs-string">'users'</span>).doc(uid).<span class="hljs-keyword">set</span>({
        <span class="hljs-string">'email'</span>: email,
        <span class="hljs-string">'username'</span>: username,
        <span class="hljs-string">'createdAt'</span>: FieldValue.serverTimestamp(), <span class="hljs-comment">// Automatically get server timestamp</span>
        <span class="hljs-string">'lastActive'</span>: FieldValue.serverTimestamp(),
      });
      <span class="hljs-built_in">print</span>(<span class="hljs-string">'User <span class="hljs-subst">$username</span> added/updated in Firestore!'</span>);
    } <span class="hljs-keyword">catch</span> (e) {
      <span class="hljs-built_in">print</span>(<span class="hljs-string">'Error adding user to Firestore: <span class="hljs-subst">$e</span>'</span>);
    }
  }

  <span class="hljs-comment">// Get a single user document by UID</span>
  Future&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;?&gt; getUserData(<span class="hljs-built_in">String</span> uid) <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">try</span> {
      <span class="hljs-comment">// Get a specific document from the 'users' collection.</span>
      DocumentSnapshot doc = <span class="hljs-keyword">await</span> _firestore.collection(<span class="hljs-string">'users'</span>).doc(uid).<span class="hljs-keyword">get</span>();
      <span class="hljs-keyword">if</span> (doc.exists) {
        <span class="hljs-comment">// If the document exists, return its data.</span>
        <span class="hljs-keyword">return</span> doc.data() <span class="hljs-keyword">as</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;?;
      } <span class="hljs-keyword">else</span> {
        <span class="hljs-built_in">print</span>(<span class="hljs-string">'User document not found for UID: <span class="hljs-subst">$uid</span>'</span>);
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">null</span>;
      }
    } <span class="hljs-keyword">catch</span> (e) {
      <span class="hljs-built_in">print</span>(<span class="hljs-string">'Error getting user data: <span class="hljs-subst">$e</span>'</span>);
      <span class="hljs-keyword">return</span> <span class="hljs-keyword">null</span>;
    }
  }

  <span class="hljs-comment">// Stream of user data (real-time updates for a single document)</span>
  Stream&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;?&gt; getUserStream(<span class="hljs-built_in">String</span> uid) {
    <span class="hljs-comment">// Listen for real-time changes to a specific user document.</span>
    <span class="hljs-keyword">return</span> _firestore.collection(<span class="hljs-string">'users'</span>).doc(uid).snapshots().map((snapshot) {
      <span class="hljs-comment">// Map the snapshot to a Map&lt;String, dynamic&gt; or null if the document doesn't exist.</span>
      <span class="hljs-keyword">return</span> snapshot.data();
    });
  }

  <span class="hljs-comment">// Add a new message to a chat collection</span>
  Future&lt;<span class="hljs-keyword">void</span>&gt; addMessage(<span class="hljs-built_in">String</span> chatRoomId, <span class="hljs-built_in">String</span> senderUid, <span class="hljs-built_in">String</span> messageText) <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">try</span> {
      <span class="hljs-comment">// Access a specific chat room's messages sub-collection.</span>
      <span class="hljs-comment">// `add()` generates a new unique ID for the document.</span>
      <span class="hljs-keyword">await</span> _firestore.collection(<span class="hljs-string">'chat_rooms'</span>).doc(chatRoomId).collection(<span class="hljs-string">'messages'</span>).add({
        <span class="hljs-string">'senderId'</span>: senderUid,
        <span class="hljs-string">'message'</span>: messageText,
        <span class="hljs-string">'timestamp'</span>: FieldValue.serverTimestamp(),
      });
      <span class="hljs-built_in">print</span>(<span class="hljs-string">'Message sent in chat room <span class="hljs-subst">$chatRoomId</span>'</span>);
    } <span class="hljs-keyword">catch</span> (e) {
      <span class="hljs-built_in">print</span>(<span class="hljs-string">'Error sending message: <span class="hljs-subst">$e</span>'</span>);
    }
  }

  <span class="hljs-comment">// Stream of messages for a specific chat room (real-time updates for a collection)</span>
  Stream&lt;<span class="hljs-built_in">List</span>&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt;&gt; getMessagesStream(<span class="hljs-built_in">String</span> chatRoomId) {
    <span class="hljs-comment">// Listen to all documents in the messages sub-collection, ordered by timestamp.</span>
    <span class="hljs-keyword">return</span> _firestore
        .collection(<span class="hljs-string">'chat_rooms'</span>)
        .doc(chatRoomId)
        .collection(<span class="hljs-string">'messages'</span>)
        .orderBy(<span class="hljs-string">'timestamp'</span>, descending: <span class="hljs-keyword">true</span>) <span class="hljs-comment">// Order messages for display</span>
        .snapshots() <span class="hljs-comment">// Get real-time snapshots</span>
        .map((snapshot) {
          <span class="hljs-comment">// Map each document snapshot to its data, converting to a list of maps.</span>
          <span class="hljs-keyword">return</span> snapshot.docs.map((doc) =&gt; doc.data()).toList();
        });
  }

  <span class="hljs-comment">// Update a field in a document</span>
  Future&lt;<span class="hljs-keyword">void</span>&gt; updateUsername(<span class="hljs-built_in">String</span> uid, <span class="hljs-built_in">String</span> newUsername) <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">try</span> {
      <span class="hljs-comment">// Updates specific fields in a document without overwriting the entire document.</span>
      <span class="hljs-keyword">await</span> _firestore.collection(<span class="hljs-string">'users'</span>).doc(uid).update({<span class="hljs-string">'username'</span>: newUsername});
      <span class="hljs-built_in">print</span>(<span class="hljs-string">'Username for <span class="hljs-subst">$uid</span> updated to <span class="hljs-subst">$newUsername</span>!'</span>);
    } <span class="hljs-keyword">catch</span> (e) {
      <span class="hljs-built_in">print</span>(<span class="hljs-string">'Error updating username: <span class="hljs-subst">$e</span>'</span>);
    }
  }

  <span class="hljs-comment">// Delete a document</span>
  Future&lt;<span class="hljs-keyword">void</span>&gt; deleteUserDocument(<span class="hljs-built_in">String</span> uid) <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">try</span> {
      <span class="hljs-comment">// Deletes a specific document.</span>
      <span class="hljs-keyword">await</span> _firestore.collection(<span class="hljs-string">'users'</span>).doc(uid).delete();
      <span class="hljs-built_in">print</span>(<span class="hljs-string">'User document for <span class="hljs-subst">$uid</span> deleted!'</span>);
    } <span class="hljs-keyword">catch</span> (e) {
      <span class="hljs-built_in">print</span>(<span class="hljs-string">'Error deleting user document: <span class="hljs-subst">$e</span>'</span>);
    }
  }
}
</code></pre>
<p>Key concepts in Firestore code:</p>
<ul>
<li><p><code>FirebaseFirestore.instance</code>: The singleton instance for interacting with Firestore.</p>
</li>
<li><p><code>collection('collection_name')</code>: Refers to a top-level collection.</p>
</li>
<li><p><code>doc('document_id')</code>: Refers to a specific document within a collection. If the ID is known (for example, user UID), you can use <code>doc()</code>.</p>
</li>
<li><p><code>add(data)</code>: Adds a new document to a collection with an automatically generated unique ID.</p>
</li>
<li><p><code>set(data)</code>: Creates a document with a specified ID. If a document with that ID already exists, it completely overwrites it. Use <code>SetOptions(merge: true)</code> to merge data instead of overwriting.</p>
</li>
<li><p><code>update(data)</code>: Updates specific fields within an existing document. It will fail if the document does not exist.</p>
</li>
<li><p><code>delete()</code>: Deletes a document.</p>
</li>
<li><p><code>get()</code>: Fetches a single document or a query result once.</p>
</li>
<li><p><code>snapshots()</code>: Returns a <code>Stream</code> that emits <code>QuerySnapshot</code> or <code>DocumentSnapshot</code> objects whenever the data changes. This is the core of real-time functionality.</p>
</li>
<li><p><code>orderBy()</code>, <code>where()</code>, <code>limit()</code>: Powerful methods for querying and filtering data.</p>
</li>
<li><p><code>FieldValue.serverTimestamp()</code>: A special value that, when set, is automatically replaced by the server's timestamp when the document is written. Useful for <code>createdAt</code> or <code>lastModified</code> fields.</p>
</li>
</ul>
<h3 id="heading-cloud-storage-scalable-file-storage">Cloud Storage: Scalable File Storage</h3>
<p>Firebase Cloud Storage allows you to store and retrieve user-generated content, such as images, videos, and other files. It's backed by Google Cloud Storage, offering high availability and scalability.</p>
<h4 id="heading-step-1-add-dependency-2">Step 1: Add Dependency</h4>
<pre><code class="lang-yaml"><span class="hljs-attr">dependencies:</span>
  <span class="hljs-comment"># ...</span>
  <span class="hljs-attr">firebase_storage:</span> <span class="hljs-string">^latest_version</span> <span class="hljs-comment"># Check pub.dev for the latest</span>
  <span class="hljs-attr">image_picker:</span> <span class="hljs-string">^latest_version</span> <span class="hljs-comment"># (Optional) For picking images from device</span>
  <span class="hljs-attr">file_picker:</span> <span class="hljs-string">^latest_version</span> <span class="hljs-comment"># (Optional) For picking any file type</span>
</code></pre>
<p>Run <code>flutter pub get</code>.</p>
<h4 id="heading-step-2-enable-storage-firebase-console">Step 2: Enable Storage (Firebase Console)</h4>
<p>Go to "Storage" in your Firebase project and click "Get started." Configure security rules (e.g., allow read/write for authenticated users) before proceeding.</p>
<p>Here’s the code:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'dart:io'</span>; <span class="hljs-comment">// Required for File class</span>
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:firebase_storage/firebase_storage.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:image_picker/image_picker.dart'</span>; <span class="hljs-comment">// From pub.dev for picking images</span>

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">StorageService</span> </span>{
  <span class="hljs-keyword">final</span> FirebaseStorage _storage = FirebaseStorage.instance;

  <span class="hljs-comment">// Upload a file (e.g., an image) to Firebase Storage</span>
  Future&lt;<span class="hljs-built_in">String?</span>&gt; uploadImage(File imageFile, <span class="hljs-built_in">String</span> folderPath) <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">try</span> {
      <span class="hljs-comment">// Create a unique file name using timestamp to avoid collisions</span>
      <span class="hljs-built_in">String</span> fileName = <span class="hljs-built_in">DateTime</span>.now().millisecondsSinceEpoch.toString();
      <span class="hljs-comment">// Create a reference to the storage path</span>
      Reference storageRef = _storage.ref().child(<span class="hljs-string">'<span class="hljs-subst">$folderPath</span>/<span class="hljs-subst">$fileName</span>.jpg'</span>);

      <span class="hljs-comment">// Upload the file</span>
      UploadTask uploadTask = storageRef.putFile(imageFile);

      <span class="hljs-comment">// Wait for the upload to complete and get the snapshot</span>
      TaskSnapshot snapshot = <span class="hljs-keyword">await</span> uploadTask;

      <span class="hljs-comment">// Get the download URL of the uploaded file</span>
      <span class="hljs-built_in">String</span> downloadUrl = <span class="hljs-keyword">await</span> snapshot.ref.getDownloadURL();
      <span class="hljs-built_in">print</span>(<span class="hljs-string">'Image uploaded! URL: <span class="hljs-subst">$downloadUrl</span>'</span>);
      <span class="hljs-keyword">return</span> downloadUrl; <span class="hljs-comment">// Return the public URL to store in Firestore, etc.</span>
    } <span class="hljs-keyword">on</span> FirebaseException <span class="hljs-keyword">catch</span> (e) {
      <span class="hljs-built_in">print</span>(<span class="hljs-string">'Firebase Storage Error: <span class="hljs-subst">${e.code}</span> - <span class="hljs-subst">${e.message}</span>'</span>);
      <span class="hljs-keyword">return</span> <span class="hljs-keyword">null</span>;
    } <span class="hljs-keyword">catch</span> (e) {
      <span class="hljs-built_in">print</span>(<span class="hljs-string">'General Storage Error: <span class="hljs-subst">$e</span>'</span>);
      <span class="hljs-keyword">return</span> <span class="hljs-keyword">null</span>;
    }
  }

  <span class="hljs-comment">// Example: Picking an image from the gallery and uploading</span>
  Future&lt;<span class="hljs-built_in">String?</span>&gt; pickAndUploadImage(<span class="hljs-built_in">String</span> folderPath) <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">final</span> ImagePicker picker = ImagePicker();
    <span class="hljs-keyword">final</span> XFile? pickedFile = <span class="hljs-keyword">await</span> picker.pickImage(source: ImageSource.gallery);

    <span class="hljs-keyword">if</span> (pickedFile != <span class="hljs-keyword">null</span>) {
      File file = File(pickedFile.path);
      <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> uploadImage(file, folderPath);
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-built_in">print</span>(<span class="hljs-string">'No image selected.'</span>);
      <span class="hljs-keyword">return</span> <span class="hljs-keyword">null</span>;
    }
  }

  <span class="hljs-comment">// Download a file from Firebase Storage</span>
  Future&lt;<span class="hljs-keyword">void</span>&gt; downloadFile(<span class="hljs-built_in">String</span> storagePath, <span class="hljs-built_in">String</span> localPath) <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">try</span> {
      Reference ref = _storage.ref().child(storagePath);
      <span class="hljs-comment">// Create a local file to save the downloaded content</span>
      File downloadToFile = File(localPath);
      <span class="hljs-keyword">await</span> ref.writeToFile(downloadToFile); <span class="hljs-comment">// Write the downloaded bytes to the local file</span>
      <span class="hljs-built_in">print</span>(<span class="hljs-string">'File downloaded to <span class="hljs-subst">$localPath</span>'</span>);
    } <span class="hljs-keyword">on</span> FirebaseException <span class="hljs-keyword">catch</span> (e) {
      <span class="hljs-built_in">print</span>(<span class="hljs-string">'Error downloading file: <span class="hljs-subst">${e.code}</span> - <span class="hljs-subst">${e.message}</span>'</span>);
    } <span class="hljs-keyword">catch</span> (e) {
      <span class="hljs-built_in">print</span>(<span class="hljs-string">'General download error: <span class="hljs-subst">$e</span>'</span>);
    }
  }

  <span class="hljs-comment">// Delete a file from Firebase Storage</span>
  Future&lt;<span class="hljs-keyword">void</span>&gt; deleteFile(<span class="hljs-built_in">String</span> storagePath) <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">try</span> {
      Reference ref = _storage.ref().child(storagePath);
      <span class="hljs-keyword">await</span> ref.delete(); <span class="hljs-comment">// Delete the file from Storage</span>
      <span class="hljs-built_in">print</span>(<span class="hljs-string">'File deleted from Storage: <span class="hljs-subst">$storagePath</span>'</span>);
    } <span class="hljs-keyword">on</span> FirebaseException <span class="hljs-keyword">catch</span> (e) {
      <span class="hljs-built_in">print</span>(<span class="hljs-string">'Error deleting file: <span class="hljs-subst">${e.code}</span> - <span class="hljs-subst">${e.message}</span>'</span>);
    } <span class="hljs-keyword">catch</span> (e) {
      <span class="hljs-built_in">print</span>(<span class="hljs-string">'General delete error: <span class="hljs-subst">$e</span>'</span>);
    }
  }
}
</code></pre>
<p>Key concepts in storage code:</p>
<ul>
<li><p><code>FirebaseStorage.instance</code>: The singleton instance for interacting with Storage.</p>
</li>
<li><p><code>_storage.ref()</code>: Gets a root reference to your Storage bucket.</p>
</li>
<li><p><code>child('path/to/file.jpg')</code>: Creates a reference to a specific file or path within your Storage bucket.</p>
</li>
<li><p><code>putFile(file)</code>: Uploads a <code>File</code> object. Other methods like <code>putString</code> (for base64 or raw strings) and <code>putData</code> (for <code>Uint8List</code>) are also available.</p>
</li>
<li><p><code>UploadTask</code>: Represents an ongoing upload operation. You can listen to its progress or await its completion.</p>
</li>
<li><p><code>TaskSnapshot</code>: Contains information about the completed upload, including <code>ref</code> (reference to the uploaded file) and <code>bytesTransferred</code>.</p>
</li>
<li><p><code>getDownloadURL()</code>: Once uploaded, this method provides a public URL to access the file. You'd typically store this URL in your Firestore database.</p>
</li>
<li><p><code>writeToFile()</code>: Downloads a file and saves it to a specified local path.</p>
</li>
<li><p><code>delete()</code>: Deletes a file at the specified reference.</p>
</li>
</ul>
<h3 id="heading-cloud-functions-serverless-backend-logic">Cloud Functions: Serverless Backend Logic</h3>
<p>Cloud Functions allow you to run backend code in response to events triggered by Firebase products (like Firestore writes, Authentication events, Storage uploads) or HTTPS requests. This is "serverless," meaning Google manages the server infrastructure.</p>
<h4 id="heading-step-1-add-dependency-3">Step 1: Add Dependency</h4>
<pre><code class="lang-yaml"><span class="hljs-attr">dependencies:</span>
  <span class="hljs-comment"># ...</span>
  <span class="hljs-attr">cloud_functions:</span> <span class="hljs-string">^latest_version</span> <span class="hljs-comment"># Check pub.dev for the latest</span>
</code></pre>
<p>Run <code>flutter pub get</code>.</p>
<h4 id="heading-step-2-initialize-functions-firebase-cli">Step 2: Initialize Functions (Firebase CLI)</h4>
<p>In your Flutter project's root, if you haven't already, run:</p>
<pre><code class="lang-bash">firebase init <span class="hljs-built_in">functions</span>
</code></pre>
<ul>
<li><p>Select your Firebase project.</p>
</li>
<li><p>Choose a language (JavaScript or TypeScript). JavaScript is simpler for quick examples.</p>
</li>
<li><p>This creates a <code>functions</code> directory in your project root.</p>
</li>
</ul>
<h4 id="heading-step-3-enable-cloud-functions-api-google-cloud-console">Step 3: Enable Cloud Functions API (Google Cloud Console)</h4>
<p>Ensure the Cloud Functions API is enabled for your project. (Usually enabled by default with Firebase setup).</p>
<p><strong>Here’s the code (Node.js for Function, Dart for Calling):</strong></p>
<p><code>functions/index.js</code> (Your Cloud Function Code):</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Import Firebase Admin SDK to interact with other Firebase services</span>
<span class="hljs-keyword">const</span> functions = <span class="hljs-built_in">require</span>(<span class="hljs-string">'firebase-functions'</span>);
<span class="hljs-keyword">const</span> admin = <span class="hljs-built_in">require</span>(<span class="hljs-string">'firebase-admin'</span>);
admin.initializeApp(); <span class="hljs-comment">// Initializes the Admin SDK</span>

<span class="hljs-comment">// 1. HTTP Callable Function: Called directly from your Flutter app via HTTPS</span>
<span class="hljs-built_in">exports</span>.addMessage = functions.https.onCall(<span class="hljs-keyword">async</span> (data, context) =&gt; {
  <span class="hljs-comment">// context.auth contains authentication info if the user is logged in</span>
  <span class="hljs-keyword">if</span> (!context.auth) {
    <span class="hljs-comment">// Throw an error if the function is called by an unauthenticated user</span>
    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> functions.https.HttpsError(
      <span class="hljs-string">'unauthenticated'</span>,
      <span class="hljs-string">'The function must be called while authenticated.'</span>
    );
  }

  <span class="hljs-comment">// Get data passed from the Flutter app</span>
  <span class="hljs-keyword">const</span> text = data.text;
  <span class="hljs-keyword">const</span> uid = context.auth.uid; <span class="hljs-comment">// The authenticated user's ID</span>

  <span class="hljs-comment">// Validate input</span>
  <span class="hljs-keyword">if</span> (!text || <span class="hljs-keyword">typeof</span> text !== <span class="hljs-string">'string'</span> || text.length === <span class="hljs-number">0</span>) {
    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> functions.https.HttpsError(
      <span class="hljs-string">'invalid-argument'</span>,
      <span class="hljs-string">'The function must be called with one argument "text" containing the message text.'</span>
    );
  }

  <span class="hljs-comment">// Write to Firestore</span>
  <span class="hljs-keyword">await</span> admin.firestore().collection(<span class="hljs-string">'messages'</span>).add({
    <span class="hljs-attr">text</span>: text,
    <span class="hljs-attr">senderId</span>: uid,
    <span class="hljs-attr">timestamp</span>: admin.firestore.FieldValue.serverTimestamp(),
  });

  <span class="hljs-comment">// Return a success message to the client</span>
  <span class="hljs-keyword">return</span> { <span class="hljs-attr">status</span>: <span class="hljs-string">'success'</span>, <span class="hljs-attr">message</span>: <span class="hljs-string">'Message added successfully!'</span> };
});

<span class="hljs-comment">// 2. Firestore Triggered Function: Runs in response to a Firestore document creation</span>
<span class="hljs-built_in">exports</span>.onNewUserCreated = functions.firestore
  .document(<span class="hljs-string">'users/{userId}'</span>) <span class="hljs-comment">// Listens for any new document in the 'users' collection</span>
  .onCreate(<span class="hljs-keyword">async</span> (snap, context) =&gt; {
    <span class="hljs-comment">// snap.data() contains the data of the newly created document</span>
    <span class="hljs-keyword">const</span> newUser = snap.data();
    <span class="hljs-keyword">const</span> userId = context.params.userId; <span class="hljs-comment">// Get the ID of the new document (user ID)</span>

    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`New user created: <span class="hljs-subst">${newUser.email}</span> with ID: <span class="hljs-subst">${userId}</span>`</span>);

    <span class="hljs-comment">// Example: Send a welcome email (requires a third-party email service integration)</span>
    <span class="hljs-comment">// Or update another part of the database</span>
    <span class="hljs-keyword">await</span> admin.firestore().collection(<span class="hljs-string">'notifications'</span>).add({
      <span class="hljs-attr">userId</span>: userId,
      <span class="hljs-attr">message</span>: <span class="hljs-string">`Welcome, <span class="hljs-subst">${newUser.username}</span>! Thanks for joining.`</span>,
      <span class="hljs-attr">read</span>: <span class="hljs-literal">false</span>,
      <span class="hljs-attr">timestamp</span>: admin.firestore.FieldValue.serverTimestamp(),
    });

    <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>; <span class="hljs-comment">// Always return null or a Promise in background functions</span>
  });
</code></pre>
<h4 id="heading-deploy-cloud-functions">Deploy Cloud Functions:</h4>
<p>Navigate to your <code>functions</code> directory in the terminal and run:</p>
<pre><code class="lang-bash">firebase deploy --only <span class="hljs-built_in">functions</span>
</code></pre>
<p><code>main.dart</code> / Flutter Code for Calling Functions:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:cloud_functions/cloud_functions.dart'</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CloudFunctionsService</span> </span>{
  <span class="hljs-keyword">final</span> FirebaseFunctions _functions = FirebaseFunctions.instance;

  <span class="hljs-comment">// Call the 'addMessage' HTTPS Callable Function</span>
  Future&lt;<span class="hljs-built_in">String?</span>&gt; callAddMessageFunction(<span class="hljs-built_in">String</span> messageText) <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">try</span> {
      <span class="hljs-comment">// Get a reference to the callable function</span>
      HttpsCallable callable = _functions.httpsCallable(<span class="hljs-string">'addMessage'</span>);

      <span class="hljs-comment">// Call the function with parameters. The data argument is what becomes 'data' in the function.</span>
      <span class="hljs-keyword">final</span> HttpsCallableResult result = <span class="hljs-keyword">await</span> callable.call(&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;{
        <span class="hljs-string">'text'</span>: messageText,
      });

      <span class="hljs-comment">// Access the data returned by the function</span>
      <span class="hljs-built_in">print</span>(<span class="hljs-string">'Cloud Function Result: <span class="hljs-subst">${result.data}</span>'</span>);
      <span class="hljs-keyword">return</span> result.data[<span class="hljs-string">'message'</span>] <span class="hljs-keyword">as</span> <span class="hljs-built_in">String?</span>;
    } <span class="hljs-keyword">on</span> FirebaseFunctionsException <span class="hljs-keyword">catch</span> (e) {
      <span class="hljs-comment">// Handle errors specifically from Cloud Functions</span>
      <span class="hljs-built_in">print</span>(<span class="hljs-string">'Cloud Function Error: <span class="hljs-subst">${e.code}</span> - <span class="hljs-subst">${e.message}</span>'</span>);
      <span class="hljs-keyword">if</span> (e.details != <span class="hljs-keyword">null</span>) {
        <span class="hljs-built_in">print</span>(<span class="hljs-string">'Error details: <span class="hljs-subst">${e.details}</span>'</span>);
      }
      <span class="hljs-keyword">return</span> <span class="hljs-keyword">null</span>;
    } <span class="hljs-keyword">catch</span> (e) {
      <span class="hljs-built_in">print</span>(<span class="hljs-string">'General error calling function: <span class="hljs-subst">$e</span>'</span>);
      <span class="hljs-keyword">return</span> <span class="hljs-keyword">null</span>;
    }
  }
}
</code></pre>
<p><strong>Key concepts in the cloud functions code:</strong></p>
<ul>
<li><p><strong>Node.js Environment:</strong> Cloud Functions are typically written in Node.js (or Python, Go, Java, and so on). The Firebase Admin SDK is crucial here for interacting with other Firebase services from the backend.</p>
</li>
<li><p><code>functions.https.onCall()</code>: Defines an HTTPS Callable Function. These are the most common way for your Flutter app to directly invoke backend logic. Firebase automatically handles authentication and CORS.</p>
</li>
<li><p><code>data</code>: The payload sent from the Flutter app.</p>
</li>
<li><p><code>context.auth</code>: Contains authentication details of the user who invoked the function (if authenticated).</p>
</li>
<li><p><code>functions.firestore.document().onCreate()</code>: Defines a function triggered by a Firestore event. Other triggers include <code>onUpdate</code>, <code>onDelete</code>, <code>onWrite</code> for Firestore/Realtime Database, and <code>onFinalize</code>, <code>onDelete</code> for Cloud Storage.</p>
</li>
<li><p><code>snap</code>: For database triggers, this is a <code>DocumentSnapshot</code> of the data that triggered the event.</p>
</li>
<li><p><code>context.params</code>: For path-based triggers (like <code>users/{userId}</code>), this contains the wildcard values (for example, <code>context.params.userId</code>).</p>
</li>
<li><p><strong>Flutter</strong> <code>cloud_functions</code>:</p>
<ul>
<li><p><code>FirebaseFunctions.instance</code>: The singleton instance.</p>
</li>
<li><p><code>httpsCallable('functionName')</code>: Gets a reference to your callable function.</p>
</li>
<li><p><a target="_blank" href="http://callable.call"><code>callable.call</code></a><code>(data)</code>: Invokes the function with the provided data (a <code>Map&lt;String, dynamic&gt;</code>).</p>
</li>
<li><p><code>FirebaseFunctionsException</code>: Catches specific errors thrown by Cloud Functions.</p>
</li>
</ul>
</li>
</ul>
<h3 id="heading-firebase-hosting-fast-amp-secure-web-hosting">Firebase Hosting: Fast &amp; Secure Web Hosting</h3>
<p>Firebase Hosting provides fast, secure, and reliable hosting for your Flutter web applications, static content, and single-page applications (SPAs). It includes a global CDN, SSL certificates, and custom domain support.</p>
<h4 id="heading-step-1-add-flutter-web-support">Step 1: Add Flutter Web Support</h4>
<p>If your project doesn't already, add web support:</p>
<pre><code class="lang-bash">flutter create . --platforms web
</code></pre>
<h4 id="heading-step-2-build-flutter-web-app">Step 2: Build Flutter Web App</h4>
<pre><code class="lang-bash">flutter build web --release
</code></pre>
<p>This command compiles your Flutter app into optimized HTML, CSS, JavaScript, and assets, placing them in the <code>build/web</code> directory.</p>
<h4 id="heading-step-3-initialize-firebase-hosting-firebase-cli">Step 3: Initialize Firebase Hosting (Firebase CLI)</h4>
<p>From your Flutter project's root:</p>
<pre><code class="lang-bash">firebase init hosting
</code></pre>
<ul>
<li><p>Select your Firebase project.</p>
</li>
<li><p><strong>Public directory:</strong> Crucially, set this to <code>build/web</code> (this is where Flutter puts its web output).</p>
</li>
<li><p><strong>Configure as a single-page app (rewrite all URLs to /index.html)?</strong> For most Flutter web apps, say <code>Yes</code>. This ensures all routes are handled by your Flutter app.</p>
</li>
<li><p><strong>Set up automatic builds and deploys with GitHub?</strong> Optional, but highly recommended for CI/CD.</p>
</li>
</ul>
<h4 id="heading-deployment">Deployment:</h4>
<pre><code class="lang-bash"><span class="hljs-comment"># From your Flutter project root</span>
flutter build web --release <span class="hljs-comment"># Rebuild your web app if you made changes</span>
firebase deploy --only hosting <span class="hljs-comment"># Deploy only the hosting portion</span>
</code></pre>
<p>Here’s what’s going on:</p>
<ul>
<li><p><code>flutter build web --release</code>: Creates an optimized, minified version of your Flutter web app suitable for production deployment. The <code>--release</code> flag is important for performance.</p>
</li>
<li><p><code>firebase deploy --only hosting</code>: Deploys the contents of your configured public directory (<code>build/web</code>) to Firebase Hosting. After deployment, Firebase will provide you with a public URL (for example, <a target="_blank" href="http://your-project-id.web.app"><code>your-project-id.web.app</code></a> or <a target="_blank" href="http://your-project-id.firebaseapp.com"><code>your-project-id.firebaseapp.com</code></a>).</p>
</li>
</ul>
<p><strong>Firebase Console:</strong> Go to "Hosting" to view your deployed sites, deployment history, connected domains, and configure custom redirects or rewrites.</p>
<h3 id="heading-firebase-remote-config-dynamic-app-behavior">Firebase Remote Config: Dynamic App Behavior</h3>
<p>Firebase Remote Config is a cloud service that lets you change the behavior and appearance of your app without requiring users to download an app update. You define parameters in the Firebase Console, set their default in-app values, and then update those values remotely.</p>
<h4 id="heading-step-1-add-dependency-4">Step 1: Add Dependency</h4>
<pre><code class="lang-yaml"><span class="hljs-attr">dependencies:</span>
  <span class="hljs-comment"># ...</span>
  <span class="hljs-attr">firebase_remote_config:</span> <span class="hljs-string">^latest_version</span> <span class="hljs-comment"># Check pub.dev for the latest</span>
</code></pre>
<p>Run <code>flutter pub get</code>.</p>
<h4 id="heading-step-2-enable-remote-config-firebase-console">Step 2: Enable Remote Config (Firebase Console)</h4>
<p>Go to "Remote Config." Click "Create your first parameter."</p>
<ul>
<li><p>Define a <strong>Parameter Key</strong> (for example, <code>welcome_message</code>, <code>show_new_feature</code>).</p>
</li>
<li><p>Provide a <strong>Default value</strong> (this is what your app will use if no remote value is fetched).</p>
</li>
<li><p>Add <strong>Conditional values</strong> (optional): You can set different values for specific user segments (for example, users in a particular country, app version, or Google Analytics audience).</p>
</li>
<li><p><strong>Publish Changes:</strong> After defining parameters, hit "Publish changes" to make them live.</p>
</li>
</ul>
<p>Here’s the code:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:firebase_remote_config/firebase_remote_config.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RemoteConfigService</span> </span>{
  <span class="hljs-keyword">final</span> FirebaseRemoteConfig _remoteConfig = FirebaseRemoteConfig.instance;

  Future&lt;<span class="hljs-keyword">void</span>&gt; initializeRemoteConfig() <span class="hljs-keyword">async</span> {
    <span class="hljs-comment">// Set default values for parameters.</span>
    <span class="hljs-comment">// These values are used if no remote value is fetched or if fetch fails.</span>
    <span class="hljs-keyword">await</span> _remoteConfig.setDefaults(<span class="hljs-keyword">const</span> {
      <span class="hljs-string">'welcome_message'</span>: <span class="hljs-string">'Welcome to our awesome app!'</span>,
      <span class="hljs-string">'show_promo_banner'</span>: <span class="hljs-keyword">false</span>,
      <span class="hljs-string">'promo_text_color'</span>: <span class="hljs-string">'#FFFFFF'</span>, <span class="hljs-comment">// White</span>
    });

    <span class="hljs-comment">// Configure fetch settings (e.g., minimum fetch interval)</span>
    <span class="hljs-comment">// In production, set a higher minimumFetchInterval (e.g., 1 hour).</span>
    <span class="hljs-comment">// During development, you can set it to zero for rapid testing.</span>
    <span class="hljs-keyword">await</span> _remoteConfig.setConfigSettings(RemoteConfigSettings(
      fetchTimeout: <span class="hljs-keyword">const</span> <span class="hljs-built_in">Duration</span>(minutes: <span class="hljs-number">1</span>), <span class="hljs-comment">// Max duration to wait for fetch</span>
      minimumFetchInterval: <span class="hljs-built_in">Duration</span>.zero, <span class="hljs-comment">// How often to fetch (set to 0 for dev)</span>
    ));

    <span class="hljs-comment">// Fetch and activate the latest values from Firebase</span>
    <span class="hljs-keyword">await</span> _remoteConfig.fetchAndActivate();

    <span class="hljs-comment">// Listen for real-time updates (optional, for instant changes without re-fetching)</span>
    <span class="hljs-comment">// This is useful for rapidly deploying changes to users who are actively using the app.</span>
    _remoteConfig.onConfigUpdated.listen((event) <span class="hljs-keyword">async</span> {
      <span class="hljs-built_in">print</span>(<span class="hljs-string">'Remote Config updated: <span class="hljs-subst">${event.updatedKeys}</span>'</span>);
      <span class="hljs-keyword">await</span> _remoteConfig.activate(); <span class="hljs-comment">// Activate the new config</span>
      <span class="hljs-built_in">print</span>(<span class="hljs-string">'New config activated!'</span>);
      <span class="hljs-comment">// You might want to rebuild your UI or notify listeners here</span>
    });

    <span class="hljs-built_in">print</span>(<span class="hljs-string">'Remote Config initialized and fetched!'</span>);
  }

  <span class="hljs-comment">// Get a string parameter</span>
  <span class="hljs-built_in">String</span> getWelcomeMessage() {
    <span class="hljs-keyword">return</span> _remoteConfig.getString(<span class="hljs-string">'welcome_message'</span>);
  }

  <span class="hljs-comment">// Get a boolean parameter</span>
  <span class="hljs-built_in">bool</span> showPromoBanner() {
    <span class="hljs-keyword">return</span> _remoteConfig.getBool(<span class="hljs-string">'show_promo_banner'</span>);
  }

  <span class="hljs-comment">// Get a color parameter (example: convert hex string to Color object)</span>
  Color getPromoTextColor() {
    <span class="hljs-built_in">String</span> hexColor = _remoteConfig.getString(<span class="hljs-string">'promo_text_color'</span>);
    <span class="hljs-comment">// Remove # if present, then parse hex to int</span>
    hexColor = hexColor.toUpperCase().replaceAll(<span class="hljs-string">"#"</span>, <span class="hljs-string">""</span>);
    <span class="hljs-keyword">if</span> (hexColor.length == <span class="hljs-number">6</span>) {
      hexColor = <span class="hljs-string">"FF<span class="hljs-subst">$hexColor</span>"</span>; <span class="hljs-comment">// Add alpha if not present</span>
    }
    <span class="hljs-keyword">return</span> Color(<span class="hljs-built_in">int</span>.parse(hexColor, radix: <span class="hljs-number">16</span>));
  }
}

<span class="hljs-comment">// Example usage in a Flutter Widget</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyConfiguredScreen</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatefulWidget</span> </span>{
  <span class="hljs-keyword">const</span> MyConfiguredScreen({<span class="hljs-keyword">super</span>.key});

  <span class="hljs-meta">@override</span>
  State&lt;MyConfiguredScreen&gt; createState() =&gt; _MyConfiguredScreenState();
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">_MyConfiguredScreenState</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">State</span>&lt;<span class="hljs-title">MyConfiguredScreen</span>&gt; </span>{
  <span class="hljs-keyword">final</span> RemoteConfigService _remoteConfigService = RemoteConfigService();
  <span class="hljs-built_in">String</span> _welcomeMessage = <span class="hljs-string">"Loading..."</span>;
  <span class="hljs-built_in">bool</span> _showBanner = <span class="hljs-keyword">false</span>;
  Color _bannerColor = Colors.white;

  <span class="hljs-meta">@override</span>
  <span class="hljs-keyword">void</span> initState() {
    <span class="hljs-keyword">super</span>.initState();
    _loadConfig();
  }

  Future&lt;<span class="hljs-keyword">void</span>&gt; _loadConfig() <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">await</span> _remoteConfigService.initializeRemoteConfig();
    setState(() {
      _welcomeMessage = _remoteConfigService.getWelcomeMessage();
      _showBanner = _remoteConfigService.showPromoBanner();
      _bannerColor = _remoteConfigService.getPromoTextColor();
    });
  }

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> Scaffold(
      appBar: AppBar(title: <span class="hljs-keyword">const</span> Text(<span class="hljs-string">'Remote Config Example'</span>)),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              _welcomeMessage,
              style: <span class="hljs-keyword">const</span> TextStyle(fontSize: <span class="hljs-number">24</span>, fontWeight: FontWeight.bold),
            ),
            <span class="hljs-keyword">if</span> (_showBanner)
              Padding(
                padding: <span class="hljs-keyword">const</span> EdgeInsets.all(<span class="hljs-number">16.0</span>),
                child: Container(
                  padding: <span class="hljs-keyword">const</span> EdgeInsets.all(<span class="hljs-number">12.0</span>),
                  color: _bannerColor,
                  child: Text(
                    <span class="hljs-string">'Special Promotion!'</span>,
                    style: TextStyle(color: _bannerColor.computeLuminance() &gt; <span class="hljs-number">0.5</span> ? Colors.black : Colors.white),
                  ),
                ),
              ),
            ElevatedButton(
              onPressed: _loadConfig, <span class="hljs-comment">// Allow refreshing config manually</span>
              child: <span class="hljs-keyword">const</span> Text(<span class="hljs-string">'Refresh Config'</span>),
            ),
          ],
        ),
      ),
    );
  }
}
</code></pre>
<p>Key concepts in remote config code:</p>
<ul>
<li><p><code>FirebaseRemoteConfig.instance</code>: The singleton instance for Remote Config.</p>
</li>
<li><p><code>setDefaults()</code>: Crucial for setting in-app default values. These are used immediately on app startup and serve as a fallback if no remote values can be fetched (for example, offline).</p>
</li>
<li><p><code>setConfigSettings()</code>: Configures how often the app attempts to fetch new configurations (<code>minimumFetchInterval</code>) and the <code>fetchTimeout</code>. During development, <a target="_blank" href="http://Duration.zero"><code>Duration.zero</code></a> for <code>minimumFetchInterval</code> is useful for quick testing.</p>
</li>
<li><p><code>fetchAndActivate()</code>: Fetches the latest configuration values from Firebase and then activates them, making them available to your app. This is an atomic operation.</p>
</li>
<li><p><code>onConfigUpdated.listen()</code>: A stream that emits an event whenever the Remote Config values are updated and published in the Firebase Console. This allows for real-time, dynamic updates in your running app without requiring a manual re-fetch.</p>
</li>
<li><p><code>getString()</code>, <code>getBool()</code>, <code>getInt()</code>, <code>getDouble()</code>: Methods to retrieve the parameter values by their keys. The types must match what you configured in the Console.</p>
</li>
</ul>
<h3 id="heading-firebase-cloud-messaging-fcm-push-notifications">Firebase Cloud Messaging (FCM): Push Notifications</h3>
<p>Firebase Cloud Messaging (FCM) is a cross-platform messaging solution that lets you reliably send messages at no cost. You can send notification messages (displayed to the user) or data messages (handled by your app's code).</p>
<h4 id="heading-step-1-add-dependency-5">Step 1: Add Dependency</h4>
<pre><code class="lang-yaml"><span class="hljs-attr">dependencies:</span>
  <span class="hljs-comment"># ...</span>
  <span class="hljs-attr">firebase_messaging:</span> <span class="hljs-string">^latest_version</span> <span class="hljs-comment"># Check pub.dev for the latest</span>
  <span class="hljs-attr">flutter_local_notifications:</span> <span class="hljs-string">^latest_version</span> <span class="hljs-comment"># For showing foreground notifications</span>
</code></pre>
<p>Run <code>flutter pub get</code>.</p>
<h4 id="heading-step-2-platform-specific-setup">Step 2: Platform-Specific Setup</h4>
<p><strong>For Android:</strong> Ensure your <code>android/app/build.gradle</code> has <code>apply plugin: '</code><a target="_blank" href="http://com.google.gms.google"><code>com.google.gms.google</code></a><code>-services'</code> and <code>implementation platform('</code><a target="_blank" href="http://com.google"><code>com.google</code></a><code>.firebase:firebase-bom:...')</code>. No further major steps usually.</p>
<p><strong>For iOS:</strong></p>
<ul>
<li><p>Enable Push Notifications capability in Xcode (Project Target &gt; Signing &amp; Capabilities).</p>
</li>
<li><p>Enable Background Modes &gt; Remote notifications.</p>
</li>
<li><p>Ensure your <code>GoogleService-Info.plist</code> is correctly placed.</p>
</li>
<li><p>Use CocoaPods to update: <code>cd ios &amp;&amp; pod install</code>.</p>
</li>
</ul>
<p><strong>For Web:</strong> Create a <code>firebase-messaging-sw.js</code> file in your <code>web</code> directory and register it as a service worker in <code>web/index.html</code>. This file handles background messages for web.</p>
<ul>
<li><p><code>web/firebase-messaging-sw.js</code>:</p>
<pre><code class="lang-javascript">  importScripts(<span class="hljs-string">'https://www.gstatic.com/firebasejs/9.22.0/firebase-app-compat.js'</span>);
  importScripts(<span class="hljs-string">'https://www.gstatic.com/firebasejs/9.22.0/firebase-messaging-compat.js'</span>);

  firebase.initializeApp({ <span class="hljs-comment">/* your web firebaseConfig object here */</span> });
  <span class="hljs-keyword">const</span> messaging = firebase.messaging();

  messaging.onBackgroundMessage(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">payload</span>) </span>{
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'[firebase-messaging-sw.js] Received background message '</span>, payload);
    <span class="hljs-comment">// Customize notification here</span>
    <span class="hljs-keyword">const</span> notificationTitle = payload.notification.title;
    <span class="hljs-keyword">const</span> notificationOptions = {
      <span class="hljs-attr">body</span>: payload.notification.body,
      <span class="hljs-attr">icon</span>: <span class="hljs-string">'/icons/Icon-192.png'</span> <span class="hljs-comment">// Ensure this path is correct</span>
    };
    <span class="hljs-keyword">return</span> self.registration.showNotification(notificationTitle, notificationOptions);
  });
</code></pre>
</li>
<li><p><code>web/index.html</code> (inside <code>&lt;body&gt;</code> tag, before <code>main.dart.js</code>):</p>
<pre><code class="lang-xml">  <span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
    <span class="hljs-keyword">if</span> (<span class="hljs-string">'serviceWorker'</span> <span class="hljs-keyword">in</span> navigator) {
      <span class="hljs-built_in">window</span>.addEventListener(<span class="hljs-string">'load'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
        navigator.serviceWorker.register(<span class="hljs-string">'/firebase-messaging-sw.js'</span>);
      });
    }
  </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre>
</li>
</ul>
<p><strong>For Firebase Console:</strong> No explicit "enable" step – FCM is enabled by default.</p>
<p>Here’s the code:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:firebase_messaging/firebase_messaging.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter_local_notifications/flutter_local_notifications.dart'</span>; <span class="hljs-comment">// For local notifications</span>
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;

<span class="hljs-comment">// Top-level function for handling background messages (must be outside any class)</span>
<span class="hljs-meta">@pragma</span>(<span class="hljs-string">'vm:entry-point'</span>)
Future&lt;<span class="hljs-keyword">void</span>&gt; _firebaseMessagingBackgroundHandler(RemoteMessage message) <span class="hljs-keyword">async</span> {
  <span class="hljs-comment">// If you're going to use other Firebase services in the background,</span>
  <span class="hljs-comment">// make sure to call `initializeApp` before using other Firebase services.</span>
  <span class="hljs-keyword">await</span> Firebase.initializeApp(); <span class="hljs-comment">// Ensure Firebase is initialized for background tasks</span>
  <span class="hljs-built_in">print</span>(<span class="hljs-string">"Handling a background message: <span class="hljs-subst">${message.messageId}</span>"</span>);

  <span class="hljs-comment">// You can show a local notification for background messages</span>
  <span class="hljs-comment">// Or perform other background tasks like updating Firestore</span>
  NotificationService().showNotification(message);
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">NotificationService</span> </span>{
  <span class="hljs-keyword">final</span> FirebaseMessaging _firebaseMessaging = FirebaseMessaging.instance;
  <span class="hljs-keyword">final</span> FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();

  <span class="hljs-keyword">static</span> <span class="hljs-built_in">bool</span> _isFlutterLocalNotificationsInitialized = <span class="hljs-keyword">false</span>;

  Future&lt;<span class="hljs-keyword">void</span>&gt; initialize() <span class="hljs-keyword">async</span> {
    <span class="hljs-comment">// Request permissions for iOS and Web (Android handles it automatically)</span>
    NotificationSettings settings = <span class="hljs-keyword">await</span> _firebaseMessaging.requestPermission(
      alert: <span class="hljs-keyword">true</span>,
      badge: <span class="hljs-keyword">true</span>,
      sound: <span class="hljs-keyword">true</span>,
      provisional: <span class="hljs-keyword">false</span>,
    );
    <span class="hljs-built_in">print</span>(<span class="hljs-string">'User granted permission: <span class="hljs-subst">${settings.authorizationStatus}</span>'</span>);

    <span class="hljs-comment">// Setup background message handler (for when the app is terminated or in background)</span>
    FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);

    <span class="hljs-comment">// Initialize flutter_local_notifications plugin for foreground notifications</span>
    <span class="hljs-keyword">if</span> (!_isFlutterLocalNotificationsInitialized) {
      <span class="hljs-keyword">const</span> AndroidInitializationSettings initializationSettingsAndroid =
          AndroidInitializationSettings(<span class="hljs-string">'@mipmap/ic_launcher'</span>); <span class="hljs-comment">// Your app icon</span>

      <span class="hljs-keyword">const</span> DarwinInitializationSettings initializationSettingsIOS =
          DarwinInitializationSettings(
        requestAlertPermission: <span class="hljs-keyword">false</span>,
        requestBadgePermission: <span class="hljs-keyword">false</span>,
        requestSoundPermission: <span class="hljs-keyword">false</span>,
      );

      <span class="hljs-keyword">const</span> InitializationSettings initializationSettings = InitializationSettings(
        android: initializationSettingsAndroid,
        iOS: initializationSettingsIOS,
      );

      <span class="hljs-keyword">await</span> _flutterLocalNotificationsPlugin.initialize(
        initializationSettings,
        onDidReceiveNotificationResponse: (NotificationResponse response) <span class="hljs-keyword">async</span> {
          <span class="hljs-comment">// Handle notification tap when app is in foreground/background/terminated</span>
          <span class="hljs-built_in">print</span>(<span class="hljs-string">'Notification tapped: <span class="hljs-subst">${response.payload}</span>'</span>);
          <span class="hljs-comment">// You can navigate based on the payload data</span>
        },
      );
      _isFlutterLocalNotificationsInitialized = <span class="hljs-keyword">true</span>;
    }

    <span class="hljs-comment">// Handle messages when the app is in the foreground</span>
    FirebaseMessaging.onMessage.listen((RemoteMessage message) {
      <span class="hljs-built_in">print</span>(<span class="hljs-string">'Got a message whilst in the foreground!'</span>);
      <span class="hljs-built_in">print</span>(<span class="hljs-string">'Message data: <span class="hljs-subst">${message.data}</span>'</span>);
      <span class="hljs-keyword">if</span> (message.notification != <span class="hljs-keyword">null</span>) {
        <span class="hljs-built_in">print</span>(<span class="hljs-string">'Message also contained a notification: <span class="hljs-subst">${message.notification!.title}</span> / <span class="hljs-subst">${message.notification!.body}</span>'</span>);
        <span class="hljs-comment">// Show local notification for foreground messages</span>
        showNotification(message);
      }
    });

    <span class="hljs-comment">// Handle messages when the app is opened from a terminated state</span>
    _firebaseMessaging.getInitialMessage().then((RemoteMessage? message) {
      <span class="hljs-keyword">if</span> (message != <span class="hljs-keyword">null</span>) {
        <span class="hljs-built_in">print</span>(<span class="hljs-string">'App opened from terminated state with message: <span class="hljs-subst">${message.data}</span>'</span>);
        <span class="hljs-comment">// Navigate or handle the message</span>
      }
    });

    <span class="hljs-comment">// Handle messages when the app is opened from a background state</span>
    FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
      <span class="hljs-built_in">print</span>(<span class="hljs-string">'App opened from background with message: <span class="hljs-subst">${message.data}</span>'</span>);
      <span class="hljs-comment">// Navigate or handle the message</span>
    });

    <span class="hljs-comment">// Get the FCM token for the device</span>
    <span class="hljs-built_in">String?</span> token = <span class="hljs-keyword">await</span> _firebaseMessaging.getToken();
    <span class="hljs-built_in">print</span>(<span class="hljs-string">'FCM Token: <span class="hljs-subst">$token</span>'</span>);

    <span class="hljs-comment">// Subscribe to a topic (optional, for sending messages to groups of users)</span>
    <span class="hljs-keyword">await</span> _firebaseMessaging.subscribeToTopic(<span class="hljs-string">'general_updates'</span>);
    <span class="hljs-built_in">print</span>(<span class="hljs-string">'Subscribed to topic: general_updates'</span>);
  }

  <span class="hljs-comment">// Helper to display a local notification</span>
  Future&lt;<span class="hljs-keyword">void</span>&gt; showNotification(RemoteMessage message) <span class="hljs-keyword">async</span> {
    RemoteNotification? notification = message.notification;
    AndroidNotification? android = message.notification?.android;

    <span class="hljs-keyword">if</span> (notification != <span class="hljs-keyword">null</span> &amp;&amp; android != <span class="hljs-keyword">null</span>) {
      _flutterLocalNotificationsPlugin.<span class="hljs-keyword">show</span>(
        notification.hashCode, <span class="hljs-comment">// Unique ID for the notification</span>
        notification.title,
        notification.body,
        NotificationDetails(
          android: AndroidNotificationDetails(
            <span class="hljs-string">'channel_id'</span>, <span class="hljs-comment">// Must match your Android Notification Channel ID</span>
            <span class="hljs-string">'channel_name'</span>,
            channelDescription: <span class="hljs-string">'Description for notifications'</span>,
            icon: android.smallIcon,
            <span class="hljs-comment">// other properties like sound, importance</span>
          ),
        ),
        payload: message.data.toString(), <span class="hljs-comment">// Pass data to be retrieved on tap</span>
      );
    }
  }

  <span class="hljs-comment">// You can also send test messages directly from the Firebase Console (Engage &gt; Cloud Messaging).</span>
}

<span class="hljs-comment">// Ensure you call NotificationService().initialize() in your main.dart after Firebase.initializeApp()</span>
<span class="hljs-comment">// Example:</span>
<span class="hljs-comment">/*
void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );
  await NotificationService().initialize(); // Initialize FCM
  runApp(const MyApp());
}
*/</span>
</code></pre>
<p><strong>Key concepts in FCM code:</strong></p>
<ul>
<li><p><code>FirebaseMessaging.instance</code>: The singleton instance for FCM.</p>
</li>
<li><p><code>requestPermission()</code>: Prompts the user for notification permissions (especially on iOS and Web).</p>
</li>
<li><p><code>_firebaseMessagingBackgroundHandler()</code>: A crucial top-level, static function that handles messages received when the app is in the background or terminated. It <em>must</em> be a top-level function.</p>
</li>
<li><p><code>FirebaseMessaging.onMessage.listen()</code>: Listens for incoming messages when the app is in the <em>foreground</em>. For these, you typically need <code>flutter_local_notifications</code> to display a notification, as the system won't display it automatically.</p>
</li>
<li><p><code>FirebaseMessaging.getInitialMessage()</code>: Checks if the app was launched by tapping on a notification while it was in a <em>terminated</em> state.</p>
</li>
<li><p><code>FirebaseMessaging.onMessageOpenedApp.listen()</code>: Listens for when a user taps on a notification to open the app from a <em>background</em> state.</p>
</li>
<li><p><code>getToken()</code>: Retrieves the unique FCM registration token for the device. This token is used to send targeted notifications to specific devices.</p>
</li>
<li><p><code>subscribeToTopic()</code>: Allows you to send messages to groups of users who have subscribed to a particular topic, instead of sending to individual tokens.</p>
</li>
<li><p><code>flutter_local_notifications</code>: A separate plugin necessary to show heads-up notifications when your app is in the foreground, or to customize background/terminated notifications.</p>
</li>
</ul>
<h3 id="heading-firebase-crashlytics-crash-reporting">Firebase Crashlytics: Crash Reporting</h3>
<p>Firebase Crashlytics helps you track, prioritize, and fix stability issues that impact your app's quality. It provides real-time crash reports and comprehensive data for debugging.</p>
<h4 id="heading-step-1-add-dependency-6">Step 1: Add Dependency</h4>
<pre><code class="lang-yaml"><span class="hljs-attr">dependencies:</span>
  <span class="hljs-comment"># ...</span>
  <span class="hljs-attr">firebase_crashlytics:</span> <span class="hljs-string">^latest_version</span> <span class="hljs-comment"># Check pub.dev for the latest</span>
</code></pre>
<p>Run <code>flutter pub get</code>.</p>
<h4 id="heading-step-2-platform-specific-setup-1">Step 2: Platform-Specific Setup</h4>
<p><strong>For Android:</strong> Add the Crashlytics Gradle plugin in your <code>android/build.gradle</code> and apply it in <code>android/app/build.gradle</code>. (Refer to FlutterFire docs for specific versions.)</p>
<p><strong>iOS:</strong> No additional steps beyond <code>GoogleService-Info.plist</code> and <code>pod install</code> are usually required.</p>
<h4 id="heading-step-3-enable-crashlytics-firebase-console">Step 3: Enable Crashlytics (Firebase Console)</h4>
<p>Go to "Crashlytics" and click "Enable Crashlytics."</p>
<p><strong>Here’s the code:</strong></p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:firebase_crashlytics/firebase_crashlytics.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/foundation.dart'</span>; <span class="hljs-comment">// For kDebugMode</span>
<span class="hljs-keyword">import</span> <span class="hljs-string">'dart:async'</span>; <span class="hljs-comment">// For runZonedGuarded</span>

<span class="hljs-keyword">void</span> main() {
  <span class="hljs-comment">// Catch any errors that occur in the Flutter framework and send them to Crashlytics.</span>
  <span class="hljs-comment">// This should be done as early as possible in your app's lifecycle.</span>
  FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterError;

  <span class="hljs-comment">// Use runZonedGuarded to catch all errors that are not handled by the Flutter framework</span>
  <span class="hljs-comment">// (e.g., errors in asynchronous operations or isolates).</span>
  runZonedGuarded&lt;Future&lt;<span class="hljs-keyword">void</span>&gt;&gt;(() <span class="hljs-keyword">async</span> {
    WidgetsFlutterBinding.ensureInitialized();
    <span class="hljs-keyword">await</span> Firebase.initializeApp();

    <span class="hljs-comment">// Disable Crashlytics in debug mode for development (optional, but good practice)</span>
    <span class="hljs-comment">// You can temporarily set to true to test crash reporting</span>
    <span class="hljs-keyword">if</span> (kDebugMode) {
      <span class="hljs-keyword">await</span> FirebaseCrashlytics.instance.setCrashlyticsCollectionEnabled(<span class="hljs-keyword">false</span>);
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-keyword">await</span> FirebaseCrashlytics.instance.setCrashlyticsCollectionEnabled(<span class="hljs-keyword">true</span>);
    }

    runApp(<span class="hljs-keyword">const</span> MyApp());
  }, (error, stack) {
    <span class="hljs-comment">// Catch errors from outside the Flutter framework (e.g., async errors)</span>
    FirebaseCrashlytics.instance.recordError(error, stack, fatal: <span class="hljs-keyword">true</span>); <span class="hljs-comment">// Mark as fatal</span>
  });
}

<span class="hljs-comment">// Example usage within your app to force a crash or log a non-fatal error</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CrashTestScreen</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-keyword">const</span> CrashTestScreen({<span class="hljs-keyword">super</span>.key});

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> Scaffold(
      appBar: AppBar(title: <span class="hljs-keyword">const</span> Text(<span class="hljs-string">'Crashlytics Test'</span>)),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ElevatedButton(
              onPressed: () {
                <span class="hljs-comment">// Force a crash (for testing Crashlytics integration)</span>
                FirebaseCrashlytics.instance.crash();
              },
              child: <span class="hljs-keyword">const</span> Text(<span class="hljs-string">'Force Crash!'</span>),
            ),
            ElevatedButton(
              onPressed: () {
                <span class="hljs-keyword">try</span> {
                  <span class="hljs-comment">// Simulate an error that doesn't crash the app</span>
                  <span class="hljs-keyword">throw</span> Exception(<span class="hljs-string">'This is a non-fatal error caught manually.'</span>);
                } <span class="hljs-keyword">catch</span> (e, s) {
                  <span class="hljs-comment">// Record a non-fatal error with stack trace</span>
                  FirebaseCrashlytics.instance.recordError(e, s, reason: <span class="hljs-string">'manual non-fatal error'</span>);
                  ScaffoldMessenger.of(context).showSnackBar(
                    <span class="hljs-keyword">const</span> SnackBar(content: Text(<span class="hljs-string">'Non-fatal error logged! Check Crashlytics.'</span>)),
                  );
                }
              },
              child: <span class="hljs-keyword">const</span> Text(<span class="hljs-string">'Log Non-Fatal Error'</span>),
            ),
            ElevatedButton(
              onPressed: () {
                <span class="hljs-comment">// Add custom key-value pairs to crash reports for more context</span>
                FirebaseCrashlytics.instance.setCustomKey(<span class="hljs-string">'user_id'</span>, <span class="hljs-string">'test_user_123'</span>);
                FirebaseCrashlytics.instance.setCustomKey(<span class="hljs-string">'app_flow'</span>, <span class="hljs-string">'checkout_process'</span>);
                FirebaseCrashlytics.instance.log(<span class="hljs-string">'User entered payment details.'</span>); <span class="hljs-comment">// Add a log message</span>
                ScaffoldMessenger.of(context).showSnackBar(
                  <span class="hljs-keyword">const</span> SnackBar(content: Text(<span class="hljs-string">'Custom data and log added.'</span>)),
                );
              },
              child: <span class="hljs-keyword">const</span> Text(<span class="hljs-string">'Add Custom Data'</span>),
            ),
          ],
        ),
      ),
    );
  }
}
</code></pre>
<p><strong>Key concepts in Crashlytics code:</strong></p>
<ul>
<li><p><code>FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterError;</code>: This line, placed in <code>main()</code>, automatically catches all errors thrown by the Flutter framework (for example, UI rendering errors) and sends them to Crashlytics.</p>
</li>
<li><p><code>runZonedGuarded()</code>: A powerful Dart feature. It creates an error zone that catches <em>all</em> asynchronous errors (for example, errors in <code>Future</code> callbacks, <code>Stream</code> listeners) that are not explicitly handled by <code>try-catch</code> blocks. This ensures comprehensive crash reporting.</p>
</li>
<li><p><code>FirebaseCrashlytics.instance.recordError(error, stack, {fatal: true});</code>: Manually logs an error to Crashlytics. <code>fatal: true</code> indicates a crash that terminated the app.</p>
</li>
<li><p><code>setCrashlyticsCollectionEnabled(bool enabled)</code>: Allows you to control whether Crashlytics collects data. It's often disabled in <code>kDebugMode</code> to avoid cluttering your console with development errors.</p>
</li>
<li><p><code>setCustomKey(key, value)</code>: Attaches custom key-value pairs to a crash report, providing more context (for example, user ID, current screen, specific app state).</p>
</li>
<li><p><code>log(message)</code>: Adds custom log messages to a crash report, helping you trace the user's actions leading up to a crash.</p>
</li>
<li><p><strong>Firebase Console (Crashlytics section):</strong> Provides a dashboard to view aggregated crash reports, stack traces, device info, custom keys, and logs. You can prioritize crashes, filter by version/OS, and track trends.</p>
</li>
</ul>
<h3 id="heading-firebase-performance-monitoring-app-performance-insights">Firebase Performance Monitoring: App Performance Insights</h3>
<p>Firebase Performance Monitoring helps you gain insights into your app's performance characteristics in real-world scenarios. It automatically collects data like app startup time, network request latency, and screen rendering times. You can also add custom traces to measure specific parts of your code.</p>
<h4 id="heading-step-1-add-dependency-7">Step 1: Add Dependency</h4>
<pre><code class="lang-yaml"><span class="hljs-attr">dependencies:</span>
  <span class="hljs-comment"># ...</span>
  <span class="hljs-attr">firebase_performance:</span> <span class="hljs-string">^latest_version</span> <span class="hljs-comment"># Check pub.dev for the latest</span>
</code></pre>
<p>Run <code>flutter pub get</code>.</p>
<h4 id="heading-step-2-platform-specific-setup-2">Step 2: Platform-Specific Setup</h4>
<p>Performance Monitoring usually requires minimal additional setup beyond adding the plugin, but check FlutterFire documentation for any specific Gradle/Podfile configurations.</p>
<h4 id="heading-step-3-enable-performance-monitoring-firebase-console">Step 3: Enable Performance Monitoring (Firebase Console)</h4>
<p>Go to "Performance" and click "Enable Performance Monitoring."</p>
<p>Here’s the code:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:firebase_performance/firebase_performance.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PerformanceMonitorService</span> </span>{
  <span class="hljs-keyword">final</span> FirebasePerformance _performance = FirebasePerformance.instance;

  <span class="hljs-comment">// Example: Custom Trace for a specific operation (e.g., fetching user profile)</span>
  Future&lt;<span class="hljs-keyword">void</span>&gt; measureUserProfileFetch() <span class="hljs-keyword">async</span> {
    <span class="hljs-comment">// Define a custom trace with a unique name</span>
    <span class="hljs-keyword">final</span> Trace profileTrace = _performance.newTrace(<span class="hljs-string">'fetch_user_profile_trace'</span>);

    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">await</span> profileTrace.start(); <span class="hljs-comment">// Start measuring</span>

      <span class="hljs-comment">// Simulate network request or database operation</span>
      <span class="hljs-built_in">print</span>(<span class="hljs-string">'Fetching user profile...'</span>);
      <span class="hljs-keyword">await</span> Future.delayed(<span class="hljs-keyword">const</span> <span class="hljs-built_in">Duration</span>(seconds: <span class="hljs-number">2</span>)); <span class="hljs-comment">// Simulate work</span>

      <span class="hljs-comment">// Add custom metrics (optional)</span>
      profileTrace.putMetric(<span class="hljs-string">'data_size_kb'</span>, <span class="hljs-number">150</span>);
      profileTrace.putAttribute(<span class="hljs-string">'source'</span>, <span class="hljs-string">'firestore'</span>);

      <span class="hljs-built_in">print</span>(<span class="hljs-string">'User profile fetched!'</span>);
    } <span class="hljs-keyword">catch</span> (e) {
      <span class="hljs-built_in">print</span>(<span class="hljs-string">'Error fetching profile: <span class="hljs-subst">$e</span>'</span>);
    } <span class="hljs-keyword">finally</span> {
      <span class="hljs-keyword">await</span> profileTrace.stop(); <span class="hljs-comment">// Stop measuring (always call stop in finally block)</span>
    }
  }

  <span class="hljs-comment">// Example: Monitoring an HTTP request (automatic for network requests but can be customized)</span>
  Future&lt;<span class="hljs-keyword">void</span>&gt; makeMonitoredHttpRequest() <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">final</span> HttpMetric httpMetric = _performance.newHttpMetric(<span class="hljs-string">'https://jsonplaceholder.typicode.com/posts/1'</span>, HttpMethod.Get);
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">await</span> httpMetric.start(); <span class="hljs-comment">// Start measuring HTTP request</span>

      <span class="hljs-comment">// Simulate an HTTP GET request</span>
      <span class="hljs-keyword">final</span> uri = <span class="hljs-built_in">Uri</span>.parse(<span class="hljs-string">'https://jsonplaceholder.typicode.com/posts/1'</span>);
      <span class="hljs-keyword">final</span> client = HttpClient();
      <span class="hljs-keyword">final</span> request = <span class="hljs-keyword">await</span> client.getUrl(uri);
      <span class="hljs-keyword">final</span> response = <span class="hljs-keyword">await</span> request.close();

      httpMetric.putAttribute(<span class="hljs-string">'status_code'</span>, response.statusCode.toString());
      httpMetric.putAttribute(<span class="hljs-string">'content_type'</span>, response.headers.contentType.toString());

      <span class="hljs-keyword">await</span> response.drain(); <span class="hljs-comment">// Consume the response body</span>
      httpMetric.responseContentType = response.headers.contentType?.value;
      httpMetric.responsePayloadSize = response.contentLength;
      httpMetric.httpResponseCode = response.statusCode;

      <span class="hljs-built_in">print</span>(<span class="hljs-string">'HTTP request completed with status: <span class="hljs-subst">${response.statusCode}</span>'</span>);
    } <span class="hljs-keyword">catch</span> (e) {
      <span class="hljs-built_in">print</span>(<span class="hljs-string">'HTTP request error: <span class="hljs-subst">$e</span>'</span>);
    } <span class="hljs-keyword">finally</span> {
      <span class="hljs-keyword">await</span> httpMetric.stop(); <span class="hljs-comment">// Stop measuring HTTP request</span>
    }
  }
}

<span class="hljs-comment">// Example usage in a Flutter Widget</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PerformanceScreen</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-keyword">const</span> PerformanceScreen({<span class="hljs-keyword">super</span>.key});

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">final</span> PerformanceMonitorService _perfService = PerformanceMonitorService();
    <span class="hljs-keyword">return</span> Scaffold(
      appBar: AppBar(title: <span class="hljs-keyword">const</span> Text(<span class="hljs-string">'Performance Monitoring'</span>)),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ElevatedButton(
              onPressed: () =&gt; _perfService.measureUserProfileFetch(),
              child: <span class="hljs-keyword">const</span> Text(<span class="hljs-string">'Measure User Profile Fetch'</span>),
            ),
            ElevatedButton(
              onPressed: () =&gt; _perfService.makeMonitoredHttpRequest(),
              child: <span class="hljs-keyword">const</span> Text(<span class="hljs-string">'Make Monitored HTTP Request'</span>),
            ),
          ],
        ),
      ),
    );
  }
}
</code></pre>
<p><strong>Key concepts in performance monitoring code:</strong></p>
<ul>
<li><p><code>FirebasePerformance.instance</code>: Singleton instance.</p>
</li>
<li><p><code>newTrace('trace_name')</code>: Creates a custom trace to measure the duration and optionally custom metrics of specific code blocks.</p>
<ul>
<li><p><code>trace.start()</code>: Begins the measurement.</p>
</li>
<li><p><code>trace.stop()</code>: Ends the measurement. Always ensure <code>stop()</code> is called, ideally in a <code>finally</code> block.</p>
</li>
<li><p><code>putMetric(name, value)</code>: Adds a custom metric (for example, number of items processed).</p>
</li>
<li><p><code>putAttribute(key, value)</code>: Adds custom attributes (for example, network type, user ID) for filtering in the Console.</p>
</li>
</ul>
</li>
<li><p><code>newHttpMetric(url, method)</code>: Automatically monitors network requests made by your app. Performance Monitoring usually detects common HTTP libraries automatically, but you can explicitly instrument with <code>HttpMetric</code> for fine-grained control or custom network stacks.</p>
<ul>
<li><p><code>httpMetric.start()</code>, <code>httpMetric.stop()</code>: Start and stop measurement.</p>
</li>
<li><p><code>httpMetric.responseCode</code>, <code>httpMetric.requestPayloadSize</code>, <code>httpMetric.responsePayloadSize</code>, <code>httpMetric.responseContentType</code>: Properties to set for detailed HTTP request metrics.</p>
</li>
</ul>
</li>
<li><p><strong>Firebase Console (Performance section):</strong> Provides dashboards for app startup time, network requests, and custom traces. You can filter data, identify bottlenecks, and monitor trends over time.</p>
</li>
</ul>
<h3 id="heading-firebase-ab-testing-experimentation-for-optimization">Firebase A/B Testing: Experimentation for Optimization</h3>
<p>Firebase A/B Testing helps you optimize your app experience by making it easy to run, analyze, and scale product and marketing experiments. It works seamlessly with Remote Config (for in-app feature variations) and Cloud Messaging (for testing different notification messages).</p>
<p>Let’s walk through the setup.</p>
<h4 id="heading-step-1-dependencies">Step 1: Dependencies</h4>
<p>A/B Testing relies on <strong>Firebase Remote Config</strong> and <strong>Google Analytics</strong>. So ensure <code>firebase_remote_config</code> and <code>firebase_analytics</code> are in your <code>pubspec.yaml</code>.</p>
<h4 id="heading-step-2-enable-ab-testing-firebase-console">Step 2: Enable A/B Testing (Firebase Console)</h4>
<p>Go to "A/B Testing" and click "Get started."</p>
<h4 id="heading-step-3-create-an-experiment-firebase-console">Step 3: Create an Experiment (Firebase Console)</h4>
<ul>
<li><p>Choose between a Remote Config experiment or a Notifications experiment.</p>
</li>
<li><p>Define <strong>Variants</strong>: Your control group (original behavior) and one or more test variants (for example, a different welcome message, a new button color).</p>
</li>
<li><p><strong>Targeting:</strong> Specify which users should be included in the experiment (for example, app version, audience from Analytics, specific user property).</p>
</li>
<li><p><strong>Goals:</strong> Define what success looks like (for example, a specific Analytics event like <code>purchase</code>, <code>session_start</code>, <code>first_open</code>).</p>
</li>
<li><p><strong>Distribution:</strong> Set the percentage of users to include in the experiment.</p>
</li>
<li><p><strong>Start Experiment:</strong> Publish the experiment. Firebase handles the user allocation and data collection.</p>
</li>
</ul>
<p>Let’s look at the code:</p>
<p>The Flutter code for A/B testing is primarily the <strong>Remote Config code</strong> you've already seen. The A/B Testing platform simply serves different Remote Config values to different user segments based on your experiment definitions.</p>
<pre><code class="lang-dart"><span class="hljs-comment">// The RemoteConfigService from earlier is sufficient.</span>
<span class="hljs-comment">// Your app will automatically receive the Remote Config values</span>
<span class="hljs-comment">// assigned by the A/B test.</span>

<span class="hljs-comment">// No additional A/B Testing specific Flutter code is typically needed beyond</span>
<span class="hljs-comment">// ensuring your app fetches and activates Remote Config values,</span>
<span class="hljs-comment">// and logs relevant Analytics events for your experiment goals.</span>

<span class="hljs-comment">// Ensure you log relevant Analytics events for your A/B test goals.</span>
<span class="hljs-comment">// Example:</span>
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:firebase_analytics/firebase_analytics.dart'</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AnalyticsService</span> </span>{
  <span class="hljs-keyword">final</span> FirebaseAnalytics _analytics = FirebaseAnalytics.instance;

  Future&lt;<span class="hljs-keyword">void</span>&gt; logPurchaseEvent({
    <span class="hljs-keyword">required</span> <span class="hljs-built_in">String</span> itemId,
    <span class="hljs-keyword">required</span> <span class="hljs-built_in">String</span> itemName,
    <span class="hljs-keyword">required</span> <span class="hljs-built_in">double</span> value,
  }) <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">await</span> _analytics.logPurchase(
      currency: <span class="hljs-string">'USD'</span>,
      value: value,
      items: [
        AnalyticsEventItem(itemId: itemId, itemName: itemName),
      ],
    );
    <span class="hljs-built_in">print</span>(<span class="hljs-string">'Purchase event logged for analytics: <span class="hljs-subst">$itemName</span>'</span>);
  }

  Future&lt;<span class="hljs-keyword">void</span>&gt; logCustomEvent(<span class="hljs-built_in">String</span> eventName, <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt; parameters) <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">await</span> _analytics.logEvent(name: eventName, parameters: parameters);
    <span class="hljs-built_in">print</span>(<span class="hljs-string">'Custom event logged: <span class="hljs-subst">$eventName</span> with params: <span class="hljs-subst">$parameters</span>'</span>);
  }
}
</code></pre>
<p>Key concepts in A/B testing code:</p>
<ul>
<li><p><strong>Variants:</strong> Different versions of your app's behavior or UI you want to test.</p>
</li>
<li><p><strong>Targeting Rules:</strong> Define which users participate in the experiment.</p>
</li>
<li><p><strong>Goals:</strong> Key metrics (usually Firebase Analytics events) that define success for your experiment. Firebase will analyze which variant best achieves these goals.</p>
</li>
<li><p><strong>Remote Config Integration:</strong> A/B Testing uses Remote Config to deliver different feature flags or values to different user groups. Your Flutter app simply fetches the Remote Config values, and the A/B Testing backend decides which variant's values to send.</p>
</li>
<li><p><strong>Analytics Integration:</strong> Crucial for measuring the impact of your variants on user behavior and achieving your experiment goals.</p>
</li>
</ul>
<h3 id="heading-firebase-app-distribution-beta-testing-workflow">Firebase App Distribution: Beta Testing Workflow</h3>
<p>Firebase App Distribution makes it easy to distribute pre-release versions of your app to trusted testers. It streamlines the beta testing workflow by managing tester groups, sending out invites, and collecting feedback.</p>
<h4 id="heading-step-1-add-firebaseappdistribution-to-pubspecyaml-optional-for-local-testingci-mostly-for-sdk">Step 1: Add <code>firebase_app_distribution</code> to <code>pubspec.yaml</code> (optional for local testing/CI, mostly for SDK):</h4>
<pre><code class="lang-yaml"><span class="hljs-attr">dependencies:</span>
  <span class="hljs-comment"># ...</span>
  <span class="hljs-attr">firebase_app_distribution:</span> <span class="hljs-string">^latest_version</span> <span class="hljs-comment"># Check pub.dev for the latest</span>
</code></pre>
<p>Run <code>flutter pub get</code>.</p>
<h4 id="heading-step-2-enable-app-distribution-firebase-console">Step 2: Enable App Distribution (Firebase Console)</h4>
<p>Go to "App Distribution" and click "Get started."</p>
<h4 id="heading-step-3-tester-management-firebase-console">Step 3: Tester Management (Firebase Console)</h4>
<p>Add testers by email, create groups, and invite them.</p>
<h4 id="heading-step-4-integration-for-builddistribution-primarily-clicicd">Step 4: Integration for Build/Distribution (Primarily CLI/CI/CD)</h4>
<ul>
<li><p><strong>For Android:</strong> Build your APK/AAB (<code>flutter build apk --release</code> or <code>flutter build appbundle --release</code>).</p>
</li>
<li><p><strong>For iOS:</strong> Build your IPA (requires Xcode and Apple Developer Program).</p>
</li>
</ul>
<p>Distribution (Using Firebase CLI):</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Android Example:</span>
<span class="hljs-comment"># 1. Build your release APK/AAB</span>
flutter build apk --release <span class="hljs-comment"># or flutter build appbundle --release</span>

<span class="hljs-comment"># 2. Distribute using Firebase CLI</span>
firebase appdistribution:distribute build/app/outputs/flutter-apk/app-release.apk \
  --app &lt;YOUR_ANDROID_APP_ID_FROM_FIREBASE_CONSOLE&gt; \
  --groups <span class="hljs-string">"testers"</span> \
  --release-notes <span class="hljs-string">"New features: login, chat, profile update."</span>
</code></pre>
<pre><code class="lang-bash"><span class="hljs-comment"># iOS Example:</span>
<span class="hljs-comment"># 1. Build your release IPA (usually via Xcode or a CI/CD pipeline)</span>
<span class="hljs-comment">#    (e.g., flutter build ipa --release - for native builds, complex)</span>

<span class="hljs-comment"># 2. Distribute using Firebase CLI</span>
<span class="hljs-comment">#    Ensure your IPA path is correct and your app is signed for distribution</span>
firebase appdistribution:distribute /path/to/your/app.ipa \
  --app &lt;YOUR_IOS_APP_ID_FROM_FIREBASE_CONSOLE&gt; \
  --groups <span class="hljs-string">"ios-testers"</span> \
  --release-notes <span class="hljs-string">"iOS specific fixes and improvements."</span>
</code></pre>
<p>Here’s what’s going on in this code:</p>
<ul>
<li><p><code>firebase appdistribution:distribute</code>: The core command for uploading your app builds.</p>
</li>
<li><p><code>--app &lt;APP_ID&gt;</code>: Your Firebase App ID for the specific platform (Android or iOS). You can find this in your Firebase Console under Project Settings -&gt; Your Apps.</p>
</li>
<li><p><code>--groups "group1,group2"</code>: Distribute to specific tester groups you've defined in the Firebase Console.</p>
</li>
<li><p><code>--release-notes "..."</code>: Add release notes for your testers.</p>
</li>
<li><p><code>--release-notes-file "notes.txt"</code>: Alternatively, specify a file containing release notes.</p>
</li>
</ul>
<p><strong>In-App Updates (using</strong> <code>firebase_app_distribution</code> Flutter plugin): The Flutter plugin allows you to check for updates directly within your app and prompt testers to install the latest version.</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:firebase_app_distribution/firebase_app_distribution.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AppDistributionService</span> </span>{
  <span class="hljs-keyword">final</span> FirebaseAppDistribution _appDistribution = FirebaseAppDistribution.instance;

  Future&lt;<span class="hljs-keyword">void</span>&gt; checkForUpdates() <span class="hljs-keyword">async</span> {
    <span class="hljs-comment">// Check if the current user is a tester</span>
    <span class="hljs-built_in">bool</span> isTester = <span class="hljs-keyword">await</span> _appDistribution.isTester();
    <span class="hljs-keyword">if</span> (!isTester) {
      <span class="hljs-built_in">print</span>(<span class="hljs-string">'Current user is not a tester.'</span>);
      <span class="hljs-keyword">return</span>;
    }

    <span class="hljs-comment">// Get the latest release information</span>
    AppDistributionRelease? release = <span class="hljs-keyword">await</span> _appDistribution.checkForUpdate();

    <span class="hljs-keyword">if</span> (release != <span class="hljs-keyword">null</span>) {
      <span class="hljs-built_in">print</span>(<span class="hljs-string">'New release available: <span class="hljs-subst">${release.displayVersion}</span> (<span class="hljs-subst">${release.buildVersion}</span>)'</span>);
      <span class="hljs-built_in">print</span>(<span class="hljs-string">'Release notes: <span class="hljs-subst">${release.releaseNotes}</span>'</span>);

      <span class="hljs-comment">// Prompt the user to update</span>
      <span class="hljs-comment">// You'd typically show a dialog here</span>
      <span class="hljs-comment">// Example: showUpdateDialog(context, release);</span>

      <span class="hljs-comment">// If you want to update directly (for in-app updates)</span>
      <span class="hljs-keyword">await</span> _appDistribution.updateRelease(); <span class="hljs-comment">// This will open the App Distribution tester app/web page</span>
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-built_in">print</span>(<span class="hljs-string">'No new updates available.'</span>);
    }
  }

  <span class="hljs-comment">// You can also authenticate testers directly if needed</span>
  Future&lt;<span class="hljs-keyword">void</span>&gt; signInTester() <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">await</span> _appDistribution.signInTester();
      <span class="hljs-built_in">print</span>(<span class="hljs-string">'Tester signed in!'</span>);
    } <span class="hljs-keyword">catch</span> (e) {
      <span class="hljs-built_in">print</span>(<span class="hljs-string">'Error signing in tester: <span class="hljs-subst">$e</span>'</span>);
    }
  }
}
</code></pre>
<p>Key concepts in app distribution:</p>
<ul>
<li><p><strong>Testers &amp; Groups:</strong> Manage who gets access to your pre-release builds.</p>
</li>
<li><p><strong>Releases:</strong> Track all your distributed builds, their versions, and release notes in the console.</p>
</li>
<li><p><strong>In-app Updates:</strong> The Flutter SDK allows testers to check for and install new builds without leaving your app, providing a seamless testing experience.</p>
</li>
<li><p><strong>Firebase Console (App Distribution section):</strong> The central place to upload builds, manage testers, view insights on adoption, and access release details.</p>
</li>
</ul>
<h2 id="heading-3-other-valuable-firebase-services-for-flutter">3. Other Valuable Firebase Services for Flutter</h2>
<p>Beyond the core services, Firebase offers many more tools that enhance Flutter applications:</p>
<h3 id="heading-firebase-analytics-understand-user-behavior">Firebase Analytics: Understand User Behavior</h3>
<p>Firebase Analytics collects usage and behavior data for your app. It's the foundation for many other Firebase services (like A/B Testing, Remote Config conditions, Crashlytics user segments).</p>
<h4 id="heading-step-1-add-dependency-8">Step 1: Add Dependency</h4>
<pre><code class="lang-yaml"><span class="hljs-attr">dependencies:</span>
  <span class="hljs-comment"># ...</span>
  <span class="hljs-attr">firebase_analytics:</span> <span class="hljs-string">^latest_version</span>
</code></pre>
<p>Run <code>flutter pub get</code>.</p>
<h4 id="heading-step-2-enabled-by-default">Step 2: Enabled by Default</h4>
<p>Analytics is usually enabled when you create your Firebase project and integrate FlutterFire.</p>
<p>Code explanation:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:firebase_analytics/firebase_analytics.dart'</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AppAnalytics</span> </span>{
  <span class="hljs-keyword">final</span> FirebaseAnalytics _analytics = FirebaseAnalytics.instance;

  <span class="hljs-comment">// Log a screen view</span>
  Future&lt;<span class="hljs-keyword">void</span>&gt; logScreenView(<span class="hljs-built_in">String</span> screenName) <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">await</span> _analytics.logScreenView(screenName: screenName);
    <span class="hljs-built_in">print</span>(<span class="hljs-string">'Screen view logged: <span class="hljs-subst">$screenName</span>'</span>);
  }

  <span class="hljs-comment">// Log a custom event</span>
  Future&lt;<span class="hljs-keyword">void</span>&gt; logCustomEvent(<span class="hljs-built_in">String</span> eventName, <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt; parameters) <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">await</span> _analytics.logEvent(name: eventName, parameters: parameters);
    <span class="hljs-built_in">print</span>(<span class="hljs-string">'Custom event logged: <span class="hljs-subst">$eventName</span> with params: <span class="hljs-subst">$parameters</span>'</span>);
  }

  <span class="hljs-comment">// Log a purchase event</span>
  Future&lt;<span class="hljs-keyword">void</span>&gt; logEcommercePurchase({
    <span class="hljs-keyword">required</span> <span class="hljs-built_in">String</span> transactionId,
    <span class="hljs-keyword">required</span> <span class="hljs-built_in">double</span> value,
    <span class="hljs-keyword">required</span> <span class="hljs-built_in">String</span> currency,
    <span class="hljs-built_in">List</span>&lt;AnalyticsEventItem&gt;? items,
  }) <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">await</span> _analytics.logPurchase(
      transactionId: transactionId,
      value: value,
      currency: currency,
      items: items,
    );
    <span class="hljs-built_in">print</span>(<span class="hljs-string">'Ecommerce purchase logged: <span class="hljs-subst">$transactionId</span>'</span>);
  }

  <span class="hljs-comment">// Set user properties (e.g., user type, subscription status)</span>
  Future&lt;<span class="hljs-keyword">void</span>&gt; setUserProperty(<span class="hljs-built_in">String</span> name, <span class="hljs-built_in">String</span> value) <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">await</span> _analytics.setUserProperty(name: name, value: value);
    <span class="hljs-built_in">print</span>(<span class="hljs-string">'User property set: <span class="hljs-subst">$name</span> = <span class="hljs-subst">$value</span>'</span>);
  }

  <span class="hljs-comment">// Set the current user ID</span>
  Future&lt;<span class="hljs-keyword">void</span>&gt; setUserId(<span class="hljs-built_in">String</span> id) <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">await</span> _analytics.setUserId(id: id);
    <span class="hljs-built_in">print</span>(<span class="hljs-string">'User ID set for analytics: <span class="hljs-subst">$id</span>'</span>);
  }
}
</code></pre>
<p>Key concepts:</p>
<ul>
<li><p><strong>Automatic Events:</strong> Analytics automatically logs some events (for example, <code>first_open</code>, <code>session_start</code>).</p>
</li>
<li><p><strong>Custom Events:</strong> You can define and log custom events with parameters to capture specific user interactions relevant to your app's goals (for example, <code>button_click</code>, <code>item_added_to_cart</code>).</p>
</li>
<li><p><strong>User Properties:</strong> Define characteristics of your user base (for example, <code>premium_user</code>, <code>app_language</code>) that you can use to segment users for analysis or targeting.</p>
</li>
<li><p><strong>Firebase Console (Analytics section):</strong> Provides detailed dashboards, funnels, user cohorts, and custom reports to understand how users engage with your app.</p>
</li>
</ul>
<h2 id="heading-4-firebase-local-emulators-developing-offline-and-faster">4. Firebase Local Emulators: Developing Offline and Faster</h2>
<p>Developing with cloud services can be slow due to deployment times and cost concerns. Firebase Local Emulators provide a suite of emulators for various Firebase services, allowing you to develop and test your Flutter app entirely offline and locally, without incurring any cloud costs or deployment delays.</p>
<h4 id="heading-step-1-install-firebase-cli-if-you-havent-already">Step 1: Install Firebase CLI (if you haven't already)</h4>
<pre><code class="lang-bash">npm install -g firebase-tools
</code></pre>
<h4 id="heading-step-2-initialize-emulators-in-your-project">Step 2: Initialize Emulators in your project</h4>
<p>Navigate to your Flutter project's root directory in the terminal and run:</p>
<pre><code class="lang-bash">firebase init emulators
</code></pre>
<p>This command will prompt you to select which Firebase emulators you want to set up (for example, Auth, Firestore, Functions, Hosting, Storage, Pub/Sub). Select the ones relevant to your project.</p>
<p>Then it will create an <code>emulator-settings.json</code> file (or similar) and update your <code>firebase.json</code> with emulator configurations.</p>
<h3 id="heading-running-emulators">Running Emulators:</h3>
<p>To start the emulators, simply run:</p>
<pre><code class="lang-bash">firebase emulators:start
</code></pre>
<p>This will launch the emulators and provide you with URLs for the Emulator UI (typically <a target="_blank" href="http://localhost:4000"><code>http://localhost:4000</code></a>) and the individual service endpoints.</p>
<h3 id="heading-connecting-flutter-to-emulators">Connecting Flutter to Emulators:</h3>
<p>To make your Flutter app connect to the local emulators instead of the actual Firebase cloud, you need to configure the <code>firebase_core</code> plugin to use the emulator hosts. This is typically done right after <code>Firebase.initializeApp()</code>.</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:firebase_core/firebase_core.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:cloud_firestore/cloud_firestore.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:firebase_auth/firebase_auth.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:firebase_storage/firebase_storage.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:cloud_functions/cloud_functions.dart'</span>;
<span class="hljs-comment">// import 'package:firebase_remote_config/firebase_remote_config.dart'; // Add if using Remote Config emulator</span>
<span class="hljs-comment">// import 'package:firebase_messaging/firebase_messaging.dart'; // Add if using Pub/Sub emulator</span>

<span class="hljs-keyword">import</span> <span class="hljs-string">'firebase_options.dart'</span>;

Future&lt;<span class="hljs-keyword">void</span>&gt; main() <span class="hljs-keyword">async</span> {
  WidgetsFlutterBinding.ensureInitialized();
  <span class="hljs-keyword">await</span> Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );

  <span class="hljs-comment">// --- Configure Firebase to use Local Emulators ---</span>
  <span class="hljs-comment">// Check if running in debug mode or specific environment to enable emulators</span>
  <span class="hljs-comment">// You might use a flavor-based approach or environment variables for this in production apps</span>
  <span class="hljs-keyword">if</span> (<span class="hljs-keyword">const</span> <span class="hljs-built_in">String</span>.fromEnvironment(<span class="hljs-string">'FLUTTER_APP_ENV'</span>) == <span class="hljs-string">'development'</span>) { <span class="hljs-comment">// Example: using an env variable</span>
    <span class="hljs-built_in">print</span>(<span class="hljs-string">'Connecting to Firebase Emulators...'</span>);

    <span class="hljs-comment">// Firestore Emulator</span>
    FirebaseFirestore.instance.settings = <span class="hljs-keyword">const</span> Settings(
      host: <span class="hljs-string">'localhost:8080'</span>, <span class="hljs-comment">// Default Firestore emulator port</span>
      sslEnabled: <span class="hljs-keyword">false</span>,
      persistenceEnabled: <span class="hljs-keyword">false</span>, <span class="hljs-comment">// Disable persistence for emulator</span>
    );

    <span class="hljs-comment">// Auth Emulator</span>
    <span class="hljs-keyword">await</span> FirebaseAuth.instance.useAuthEmulator(<span class="hljs-string">'localhost'</span>, <span class="hljs-number">9099</span>); <span class="hljs-comment">// Default Auth emulator port</span>

    <span class="hljs-comment">// Storage Emulator</span>
    <span class="hljs-keyword">await</span> FirebaseStorage.instance.useStorageEmulator(<span class="hljs-string">'localhost'</span>, <span class="hljs-number">9199</span>); <span class="hljs-comment">// Default Storage emulator port</span>

    <span class="hljs-comment">// Cloud Functions Emulator</span>
    FirebaseFunctions.instance.useFunctionsEmulator(<span class="hljs-string">'localhost'</span>, <span class="hljs-number">5001</span>); <span class="hljs-comment">// Default Functions emulator port</span>

    <span class="hljs-comment">// Optional: Remote Config Emulator (requires separate setup and API)</span>
    <span class="hljs-comment">// You typically point to a specific endpoint or use a local file for remote config emulation</span>
    <span class="hljs-comment">// The Remote Config emulator does not have a direct `useRemoteConfigEmulator` method</span>
    <span class="hljs-comment">// You'd typically load local JSON for development or use specific testing frameworks.</span>

    <span class="hljs-comment">// Optional: Pub/Sub Emulator for FCM (Cloud Messaging)</span>
    <span class="hljs-comment">// For FCM, you'll generally test with real devices and real FCM service</span>
    <span class="hljs-comment">// if you need full notification delivery. However, if your functions</span>
    <span class="hljs-comment">// react to Pub/Sub events that would normally be triggered by FCM,</span>
    <span class="hljs-comment">// you can emulate Pub/Sub.</span>
  }
  <span class="hljs-comment">// --- End of Emulator Configuration ---</span>

  runApp(<span class="hljs-keyword">const</span> MyApp());
}

<span class="hljs-comment">// ... rest of your MyApp and other Flutter code (AuthWrapper, etc.)</span>
</code></pre>
<p>Here’s what’s going on in this code:</p>
<ul>
<li><p><code>firebase init emulators</code>: Sets up your project for emulation.</p>
</li>
<li><p><code>firebase emulators:start</code>: Launches the selected emulators. The terminal output will show the URLs for each service's emulator.</p>
</li>
<li><p><code>FirebaseFirestore.instance.settings = Settings(...)</code>: For <strong>Firestore</strong>, you configure the <code>host</code>, disable SSL (because it's local), and often disable persistence.</p>
</li>
<li><p><code>FirebaseAuth.instance.useAuthEmulator(host, port)</code>: For <strong>Authentication</strong>, you explicitly tell the SDK to use the emulator host and port.</p>
</li>
<li><p><code>FirebaseStorage.instance.useStorageEmulator(host, port)</code>: Similarly for <strong>Storage</strong>.</p>
</li>
<li><p><code>FirebaseFunctions.instance.useFunctionsEmulator(host, port)</code>: For <strong>Cloud Functions</strong>, you direct callable functions to the local emulator.</p>
</li>
<li><p><strong>Remote Config Emulator:</strong> The <code>firebase_remote_config</code> plugin doesn't have a direct <code>useEmulator</code> method. For development, you often load default values or use mock data. For comprehensive testing, you might deploy to a test Firebase project or use specialized testing tools.</p>
</li>
<li><p><strong>Conditional Emulation:</strong> The example uses <code>const String.fromEnvironment('FLUTTER_APP_ENV') == 'development'</code> to conditionally enable emulators. This is a common pattern to avoid connecting to emulators in production builds. You would run your Flutter app with:</p>
<pre><code class="lang-bash">  flutter run --dart-define=<span class="hljs-string">'FLUTTER_APP_ENV=development'</span>
</code></pre>
</li>
</ul>
<p>Benefits of emulators:</p>
<ul>
<li><p><strong>Offline development:</strong> Work without an internet connection.</p>
</li>
<li><p><strong>Cost savings:</strong> No charges for read/write operations, function invocations, or storage.</p>
</li>
<li><p><strong>Faster iteration:</strong> Instantly see changes to your security rules, functions, and data without waiting for cloud deployments.</p>
</li>
<li><p><strong>Consistent testing:</strong> Create repeatable test environments with known data states.</p>
</li>
<li><p><strong>Isolated environments:</strong> Develop features in isolation without affecting your production data.</p>
</li>
</ul>
<h2 id="heading-5-continuous-integration-and-deployment-cicd-with-firebase-amp-flutter">5. Continuous Integration and Deployment (CI/CD) with Firebase &amp; Flutter</h2>
<p>For production Flutter applications, automating your build, test, and deployment process is critical. Firebase integrates well with popular CI/CD platforms to streamline this workflow.</p>
<h3 id="heading-key-concepts">Key Concepts:</h3>
<ul>
<li><p><strong>Build Automation:</strong> Automatically compiling your Flutter app (APK, AAB, IPA, Web) whenever code changes are pushed.</p>
</li>
<li><p><strong>Testing:</strong> Running unit, widget, and integration tests to catch bugs early.</p>
</li>
<li><p><strong>Deployment:</strong> Automatically releasing your app to Firebase Hosting, App Distribution, or even directly to app stores (though the latter is more complex).</p>
</li>
<li><p><strong>Firebase CLI:</strong> The backbone of Firebase CI/CD, as it enables programmatic interaction with Firebase services.</p>
</li>
<li><p><strong>Service Accounts:</strong> For automated systems, you'll use a <strong>Firebase Service Account</strong> instead of personal login credentials. This provides secure, non-interactive authentication.</p>
</li>
</ul>
<h3 id="heading-example-workflow-github-actions">Example Workflow (GitHub Actions):</h3>
<p>Here's a simplified example of a GitHub Actions workflow that builds a Flutter web app and deploys it to Firebase Hosting.</p>
<h4 id="heading-setup-firebase-service-account">Setup Firebase Service Account</h4>
<ol>
<li><p>In your <strong>Firebase Console</strong>, go to <strong>Project settings</strong> -&gt; <strong>Service accounts</strong>.</p>
</li>
<li><p>Click "Generate new private key" to download a JSON file (for example, <code>your-project-id-firebase-adminsdk-xxxxx-xxxxx.json</code>).</p>
</li>
<li><p><strong>In GitHub:</strong> Go to your repository's <strong>Settings</strong> -&gt; <strong>Secrets and variables</strong> -&gt; <strong>Actions</strong> -&gt; <strong>New repository secret</strong>.</p>
</li>
<li><p>Create a secret named <code>FIREBASE_SERVICE_ACCOUNT_KEY</code> (or similar) and paste the <em>entire content</em> of the downloaded JSON file into the value field. This keeps your key secure.</p>
</li>
</ol>
<p><strong>GitHub Actions Workflow File</strong> (<code>.github/workflows/main.yml</code>):</p>
<pre><code class="lang-yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">Deploy</span> <span class="hljs-string">Flutter</span> <span class="hljs-string">Web</span> <span class="hljs-string">to</span> <span class="hljs-string">Firebase</span> <span class="hljs-string">Hosting</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-comment"># Trigger on pushes to the main branch</span>

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">build_and_deploy:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span> <span class="hljs-comment"># Use a Linux runner</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">Flutter</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">subosito/flutter-action@v2</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">channel:</span> <span class="hljs-string">'stable'</span> <span class="hljs-comment"># or 'beta', 'master'</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">dependencies</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">flutter</span> <span class="hljs-string">pub</span> <span class="hljs-string">get</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Build</span> <span class="hljs-string">Flutter</span> <span class="hljs-string">Web</span> <span class="hljs-string">App</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">flutter</span> <span class="hljs-string">build</span> <span class="hljs-string">web</span> <span class="hljs-string">--release</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">Firebase</span> <span class="hljs-string">CLI</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">npm</span> <span class="hljs-string">install</span> <span class="hljs-string">-g</span> <span class="hljs-string">firebase-tools</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Deploy</span> <span class="hljs-string">to</span> <span class="hljs-string">Firebase</span> <span class="hljs-string">Hosting</span>
        <span class="hljs-comment"># Use Firebase CLI with the service account key</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">firebase</span> <span class="hljs-string">deploy</span> <span class="hljs-string">--only</span> <span class="hljs-string">hosting</span> <span class="hljs-string">--project</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.FIREBASE_PROJECT_ID</span> <span class="hljs-string">}}</span> <span class="hljs-string">--token</span> <span class="hljs-string">"$<span class="hljs-template-variable">{{ secrets.FIREBASE_SERVICE_ACCOUNT_KEY }}</span>"</span>
        <span class="hljs-comment"># Alternative using FIREBASE_TOKEN for simple cases (requires firebase login --no-localhost)</span>
        <span class="hljs-comment"># run: firebase deploy --only hosting --project ${{ secrets.FIREBASE_PROJECT_ID }}</span>
        <span class="hljs-attr">env:</span>
          <span class="hljs-comment"># If using FIREBASE_TOKEN instead of service account:</span>
          <span class="hljs-comment"># FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }}</span>
          <span class="hljs-attr">FIREBASE_PROJECT_ID:</span> <span class="hljs-string">your-firebase-project-id</span> <span class="hljs-comment"># Replace with your actual project ID</span>
</code></pre>
<p>Here’s what’s going on:</p>
<ul>
<li><p><code>on: push: branches: - main</code>: This workflow will run automatically whenever changes are pushed to the <code>main</code> branch.</p>
</li>
<li><p><code>runs-on: ubuntu-latest</code>: Specifies the operating system of the virtual machine that will run the job.</p>
</li>
<li><p><code>uses: actions/checkout@v4</code>: Checks out your repository code.</p>
</li>
<li><p><code>uses: subosito/flutter-action@v2</code>: Sets up the Flutter SDK on the runner.</p>
</li>
<li><p><code>flutter pub get</code>: Fetches all your Dart/Flutter dependencies.</p>
</li>
<li><p><code>flutter build web --release</code>: Builds the production-ready Flutter web application.</p>
</li>
<li><p><code>npm install -g firebase-tools</code>: Installs the Firebase CLI on the runner.</p>
</li>
<li><p><code>firebase deploy --only hosting --project ... --token "${{ secrets.FIREBASE_SERVICE_ACCOUNT_KEY }}"</code>: This is the deployment command.</p>
<ul>
<li><p><code>--only hosting</code>: Specifies that only the Hosting service should be deployed.</p>
</li>
<li><p><code>--project ${{ secrets.FIREBASE_PROJECT_ID }}</code>: Specifies your Firebase project ID. You might add this as another GitHub secret for flexibility.</p>
</li>
<li><p><code>--token "${{ secrets.FIREBASE_SERVICE_ACCOUNT_KEY }}"</code>: This is how the Firebase CLI authenticates with Firebase using the service account key. GitHub Actions securely injects the secret value. <em>Note: For basic hosting, sometimes just a</em> <code>FIREBASE_TOKEN</code> generated from <code>firebase login --no-</code><a target="_blank" href="http://localhost"><code>localhost</code></a> is used, but a service account is more robust for CI/CD.</p>
</li>
</ul>
</li>
</ul>
<h3 id="heading-further-cicd-enhancements">Further CI/CD Enhancements:</h3>
<ul>
<li><p><strong>Testing:</strong> Add steps for <code>flutter test</code> after <code>flutter pub get</code> to run your tests automatically.</p>
</li>
<li><p><strong>App Distribution:</strong> Integrate <code>firebase appdistribution:distribute</code> commands for Android APK/AAB or iOS IPA releases to testers.</p>
</li>
<li><p><strong>Cloud Functions Deployment:</strong> Add <code>firebase deploy --only functions</code> for backend updates.</p>
</li>
<li><p><strong>Multiple Environments:</strong> Use different Firebase projects for <code>dev</code>, <code>staging</code>, and <code>production</code> environments, and configure your CI/CD to deploy to the appropriate project based on branch (e.g., <code>develop</code> branch to <code>dev</code> project, <code>main</code> to <code>prod</code>).</p>
</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Firebase is more than just a collection of backend services – it's an ecosystem designed to accelerate application development and streamline operations. When paired with Flutter's prowess in building beautiful, natively compiled applications, you gain an incredibly productive development stack.</p>
<p>From the robust Firebase Authentication that handles user identity, through the real-time prowess of Cloud Firestore for data, to the scalable Cloud Storage for assets, Cloud Functions for serverless logic, and Firebase Hosting for web deployment – every piece fits together seamlessly.</p>
<p>Services like Remote Config and A/B Testing empower you to dynamically adapt and optimize your app, while Crashlytics and Performance Monitoring keep your app stable and performant. Finally, App Distribution simplifies beta testing, and the Local Emulators revolutionize your development workflow.</p>
<p>For developers looking to accelerate their workflow and leverage the latest in cloud-based development, Firebase Studio (which evolved from Project IDX) offers a compelling environment. It's an AI-assisted, online integrated development environment (IDE) built on Google Cloud and Visual Studio Code, providing a complete workspace in the browser.</p>
<p>By deeply understanding these services, mastering the Firebase Console for management, leveraging the Firebase CLI for automation, and embracing the evolving capabilities of environments like Firebase Studio for AI-powered assistance, Flutter developers are exceptionally well-equipped to build highly scalable, engaging, and resilient applications that stand out in today's digital landscape.</p>
<h3 id="heading-references">References:</h3>
<ol>
<li><strong>Official Documentation (Always the Primary Source):</strong></li>
</ol>
<ul>
<li><p><strong>Firebase Documentation (Overall):</strong> The comprehensive hub for all Firebase services. This is where you'll find the most up-to-date and accurate information for each product.</p>
<ul>
<li><a target="_blank" href="https://firebase.google.com/docs">https://firebase.google.com/docs</a></li>
</ul>
</li>
<li><p><strong>Flutter Documentation:</strong> The official guides for the Flutter SDK, covering UI, state management, platform integration, and more.</p>
<ul>
<li><a target="_blank" href="https://flutter.dev/docs">https://flutter.dev/docs</a></li>
</ul>
</li>
<li><p><strong>FlutterFire Documentation (Firebase for Flutter):</strong> This is <em>critically important</em> as it details how to specifically integrate Firebase services with Flutter. It includes setup guides, plugin usage, and Flutter-specific considerations.</p>
<ul>
<li><p><a target="_blank" href="https://firebase.google.com/docs/flutter/setup">https://firebase.google.com/docs/flutter/setup</a></p>
</li>
<li><p><a target="_blank" href="https://firebase.flutter.dev/">https://firebase.flutter.dev/</a> (This is often a more direct route to the FlutterFire-specific documentation for individual plugins)</p>
</li>
</ul>
</li>
</ul>
<ol start="2">
<li><strong>Specific Firebase Product Documentation (for "Deep Dive" Sections):</strong></li>
</ol>
<p>Depending on the specific "cutting-edge" aspects you want to explore, you'd dive into these:</p>
<ul>
<li><p><strong>Firebase Authentication:</strong> For user sign-up, login, and identity management (email/password, social logins, phone auth, anonymous).</p>
<ul>
<li><a target="_blank" href="https://firebase.google.com/docs/auth">https://firebase.google.com/docs/auth</a></li>
</ul>
</li>
<li><p><strong>Cloud Firestore:</strong> The flexible, scalable NoSQL database.</p>
<ul>
<li><a target="_blank" href="https://firebase.google.com/docs/firestore">https://firebase.google.com/docs/firestore</a></li>
</ul>
</li>
<li><p><strong>Firebase Realtime Database:</strong> The original NoSQL database, often used for high-frequency, real-time data needs.</p>
<ul>
<li><a target="_blank" href="https://firebase.google.com/docs/database">https://firebase.google.com/docs/database</a></li>
</ul>
</li>
<li><p><strong>Firebase Cloud Storage:</strong> For storing and serving user-generated content like images and videos.</p>
<ul>
<li><a target="_blank" href="https://firebase.google.com/docs/storage">https://firebase.google.com/docs/storage</a></li>
</ul>
</li>
<li><p><strong>Firebase Cloud Functions:</strong> For serverless backend logic, responding to Firebase events, or serving HTTP requests.</p>
<ul>
<li><a target="_blank" href="https://firebase.google.com/docs/functions">https://firebase.google.com/docs/functions</a></li>
</ul>
</li>
<li><p><strong>Firebase Hosting:</strong> For deploying your Flutter web app or static assets.</p>
<ul>
<li><a target="_blank" href="https://firebase.google.com/docs/hosting">https://firebase.google.com/docs/hosting</a></li>
</ul>
</li>
<li><p><strong>Firebase Remote Config:</strong> For dynamic app behavior and UI changes without app updates.</p>
<ul>
<li><a target="_blank" href="https://firebase.google.com/docs/remote-config">https://firebase.google.com/docs/remote-config</a></li>
</ul>
</li>
<li><p><strong>Firebase Cloud Messaging (FCM):</strong> For sending push notifications.</p>
<ul>
<li><a target="_blank" href="https://firebase.google.com/docs/cloud-messaging">https://firebase.google.com/docs/cloud-messaging</a></li>
</ul>
</li>
<li><p><strong>Firebase Analytics:</strong> For understanding user behavior and app performance.</p>
<ul>
<li><a target="_blank" href="https://firebase.google.com/docs/analytics">https://firebase.google.com/docs/analytics</a></li>
</ul>
</li>
<li><p><strong>Firebase Crashlytics:</strong> For real-time crash reporting.</p>
<ul>
<li><a target="_blank" href="https://firebase.google.com/docs/crashlytics">https://firebase.google.com/docs/crashlytics</a></li>
</ul>
</li>
<li><p><strong>Firebase Performance Monitoring:</strong> For insights into app performance.</p>
<ul>
<li><a target="_blank" href="https://firebase.google.com/docs/perf-mon">https://firebase.google.com/docs/perf-mon</a></li>
</ul>
</li>
<li><p><strong>Firebase App Distribution:</strong> For distributing pre-release versions to testers.</p>
<ul>
<li><a target="_blank" href="https://firebase.google.com/docs/app-distribution">https://firebase.google.com/docs/app-distribution</a></li>
</ul>
</li>
<li><p><strong>Firebase Local Emulator Suite:</strong> For local development and testing of Firebase services.</p>
<ul>
<li><a target="_blank" href="https://firebase.google.com/docs/emulator-suite">https://firebase.google.com/docs/emulator-suite</a></li>
</ul>
</li>
<li><p><strong>Firebase CLI:</strong> Command-line tools for managing Firebase projects.</p>
<ul>
<li><a target="_blank" href="https://firebase.google.com/docs/cli">https://firebase.google.com/docs/cli</a></li>
</ul>
</li>
</ul>
<ol start="3">
<li><strong>Best Practices and Advanced Topics:</strong></li>
</ol>
<ul>
<li><p><strong>Firebase Security Rules:</strong> Crucial for securing your Firestore and Cloud Storage data.</p>
<ul>
<li><a target="_blank" href="https://firebase.google.com/docs/rules">https://firebase.google.com/docs/rules</a></li>
</ul>
</li>
<li><p><strong>Firebase Admin SDK:</strong> For server-side interactions with Firebase (e.g., managing users, sending messages, data migrations).</p>
<ul>
<li><a target="_blank" href="https://firebase.google.com/docs/admin/setup">https://firebase.google.com/docs/admin/setup</a></li>
</ul>
</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Authenticate Your React App Using Firebase ]]>
                </title>
                <description>
                    <![CDATA[ Authentication is a fundamental aspect of modern web and mobile applications. It ensures that users can securely access an app while protecting their data. Firebase, a platform developed by Google, offers a simple and efficient way to add authenticat... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/authenticate-react-app-using-firebase/</link>
                <guid isPermaLink="false">66fca32291d1f0e8dbe5d676</guid>
                
                    <category>
                        <![CDATA[ authentication ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Firebase ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Ijeoma Igboagu ]]>
                </dc:creator>
                <pubDate>Wed, 02 Oct 2024 01:34:26 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/Uw_8vSroCSc/upload/a8799e4ad43b3b8fe966910f9171ccd3.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Authentication is a fundamental aspect of modern web and mobile applications. It ensures that users can securely access an app while protecting their data.</p>
<p>Firebase, a platform developed by Google, offers a simple and efficient way to add authentication to your app.</p>
<p>In this article, I’ll walk you through the steps to authenticate your app using Firebase. Whether you're working on a web or mobile application, Firebase provides a straightforward way to integrate various authentication methods. </p>
<p>By the end of this article, you'll have a fully functional authentication system that allows users to sign up, sign in, and manage their accounts securely.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-table-of-contents">Table of Contents</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-why-use-firebase-for-authentication">Why Use Firebase for Authentication?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-1-how-to-set-up-a-firebase-project">Step 1: How to Set Up a Firebase Project</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-2-how-to-install-firebase-in-your-project">Step 2: How to Install Firebase in Your Project</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-3-how-to-initialize-firebase-in-your-app">Step 3: How to Initialize Firebase in Your App</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-4-how-to-set-up-authentication-methods">Step 4: How to Set Up Authentication Methods</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-authentication-method-using-google">Authentication Method Using Google</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-5-how-to-upload-to-github">Step 5: How to Upload to GitHub</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before we begin, you need to have the following:</p>
<ul>
<li><strong>A Google Account</strong>: Firebase is a Google product, and you need a Google account to access the Firebase Console and use Firebase services. If you don’t have a Google account, <a target="_blank" href="https://support.google.com/mail/answer/56256?hl=en">you can create one here</a>.</li>
</ul>
<h2 id="heading-why-use-firebase-for-authentication"><strong>Why Use Firebase for Authentication?</strong></h2>
<p>Firebase Authentication provides backend services and easy-to-use SDKs to authenticate users to your app. It supports various authentication methods, including:</p>
<ul>
<li><p><strong>Email and password authentication</strong></p>
</li>
<li><p><strong>Google, Facebook, Twitter, and GitHub Authentication</strong></p>
</li>
<li><p><strong>Phone Number Authentication</strong></p>
</li>
<li><p><strong>Anonymous Authentication</strong></p>
</li>
</ul>
<p>These features make Firebase an excellent choice for developers who want to implement secure and reliable authentication without dealing with the complexities of building a custom authentication system.</p>
<p>Let’s get started with the setup!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727802131211/b57dce67-663e-4c03-baa2-21668b543d68.jpeg" alt="b57dce67-663e-4c03-baa2-21668b543d68" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h2 id="heading-step-1-how-to-set-up-a-firebase-project">Step 1: How to Set Up a Firebase Project</h2>
<p>Before using Firebase Authentication, you need to set up a Firebase project.</p>
<p><strong>i. Create a Firebase Project</strong></p>
<ul>
<li>Go to the <a target="_blank" href="https://firebase.google.com/">Firebase Console.</a></li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1723410569746/560dfa39-e8d5-4b22-bb84-94946daeac08.png" alt="Firebase website" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<ul>
<li>Click "Add Project" and follow the on-screen instructions to create a new project.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727812540013/eceb505e-ea69-43f0-a845-ad26d86d5c26.gif" alt="Creating a project base" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Once your project is created, you’ll be directed to the Firebase project dashboard.</p>
<p><strong>ii. Add Your App to the Project</strong></p>
<ul>
<li><p>In the Firebase console, click on the "Web" icon (&lt;/&gt;) to add a web app to your Firebase project.</p>
</li>
<li><p>Register your app with a nickname, and click "Register app."</p>
</li>
<li><p>You will be provided with a Firebase SDK snippet (Software Development Kit), which you'll need to add to your app.</p>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1723412408046/4bf3956f-1d7d-4dff-8a70-72a757c01d2b.gif" alt="Registering your project to firebase" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
</li>
</ul>
<h2 id="heading-step-2-how-to-install-firebase-in-your-project"><strong>Step 2: How to Install Firebase in Your Project</strong></h2>
<p>To start with Firebase Authentication, you'll first need to install Firebase in your project. Here's how you can do it:</p>
<ul>
<li><p>In your code editor, open the terminal for your project.</p>
</li>
<li><p>Run the following command to install Firebase:</p>
</li>
</ul>
<pre><code class="lang-javascript">npm install firebase
</code></pre>
<p>This command will add Firebase to your project, allowing you to use its authentication and other features.</p>
<h2 id="heading-step-3-how-to-initialize-firebase-in-your-app"><strong>Step 3: How to Initialize Firebase in Your App</strong></h2>
<p>After installing Firebase, the next step is to initialize it in your project using the configuration snippet provided in the Firebase console, commonly referred to as the Firebase SDK snippet.</p>
<p><strong>To set this up:</strong></p>
<ol>
<li><p>Create a folder named <strong>config</strong> in your project directory.</p>
</li>
<li><p>Inside the folder, create a file called <strong>firebase.js</strong>.</p>
</li>
<li><p>Paste the SDK snippet you obtained from the Firebase console into the <strong>firebase.js</strong> file.</p>
</li>
</ol>
<p>Here’s what your project setup should look like:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1723459271302/4773d484-b5a2-4cbe-9626-76765cafd9b8.png" alt="Pasting the SDK in your project" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>This code initializes Firebase in your app, enabling you to utilize Firebase authentication and other services, such as Firebase storage, for managing your data.</p>
<p><strong>Note:</strong> Ensure you generate your unique application key for your application to function correctly.</p>
<h2 id="heading-step-4-how-to-set-up-authentication-methods"><strong>Step 4: How to Set Up Authentication Methods</strong></h2>
<p>Firebase supports multiple authentication methods, like using Google, Facebook, GitHub, and so on.</p>
<p>But let’s set up email and password authentication as an example:</p>
<ul>
<li><p>Go to "Authentication" in the left-hand menu in the Firebase console.</p>
</li>
<li><p>Click on the "Sign-in method" tab.</p>
</li>
<li><p>Enable "Email/Password" under the "Sign-in providers" section.</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1723457322104/6914efdc-87cf-4fce-b84f-bd407b6b4918.gif" alt="Authentication using email and password" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>  Now that you've enabled email/password authentication, you can create a sign-up and a sign-in function in your app.</p>
<p>  Let’s create a working example of a sign-up function:</p>
<ul>
<li><p>In your project, create a file named <strong>sign-up.jsx</strong>.</p>
</li>
<li><p>Import the function needed to create a user from Firebase. The function you'll use to create a user is <code>createUserWithEmailAndPassword</code>.</p>
</li>
<li><p>Before creating a user, make sure to import the auth instance that is initialized in <strong>firebase.js</strong> into the <strong>sign-up.jsx</strong> file.</p>
</li>
</ul>
</li>
</ul>
<pre><code class="lang-javascript">    <span class="hljs-keyword">import</span> { auth } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../../config/firebase'</span>;
    <span class="hljs-keyword">import</span> { createUserWithEmailAndPassword } <span class="hljs-keyword">from</span> <span class="hljs-string">'firebase/auth'</span>;

    <span class="hljs-keyword">const</span> SignUp = <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-comment">// To create the user with email and password</span>
      <span class="hljs-keyword">const</span> handleUser = <span class="hljs-keyword">async</span> (e) =&gt; {
        e.preventDefault();
        <span class="hljs-keyword">try</span> {
          <span class="hljs-keyword">await</span> createUserWithEmailAndPassword(auth, email, password);
          alert(<span class="hljs-string">'User created successfully'</span>);
        } <span class="hljs-keyword">catch</span> (err) {
          <span class="hljs-built_in">console</span>.error(err);
        }
      };

      <span class="hljs-comment">// ... (rest of your SignUp component)</span>
    };
</code></pre>
<p>    In the return statement, I will use a form, so we need to import the <code>useState()</code> Hook to manage and track changes in the form's input fields.</p>
<pre><code class="lang-javascript">    &lt;div&gt;
      <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>Register your Account<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span></span>
      <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">onSubmit</span>=<span class="hljs-string">{handleCreateUser}</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">label</span>&gt;</span>Name<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">input</span>
            <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span>
            <span class="hljs-attr">id</span>=<span class="hljs-string">"name"</span>
            <span class="hljs-attr">name</span>=<span class="hljs-string">"name"</span>
            <span class="hljs-attr">value</span>=<span class="hljs-string">{name}</span>
            <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setName(e.target.value)}
          /&gt;
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">htmlFor</span>=<span class="hljs-string">"email"</span>&gt;</span>Email<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">input</span>
            <span class="hljs-attr">type</span>=<span class="hljs-string">"email"</span>
            <span class="hljs-attr">id</span>=<span class="hljs-string">"email"</span>
            <span class="hljs-attr">name</span>=<span class="hljs-string">"email"</span>
            <span class="hljs-attr">value</span>=<span class="hljs-string">{email}</span>
            <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setEmail(e.target.value)}
          /&gt;
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">htmlFor</span>=<span class="hljs-string">"password"</span>&gt;</span>Password<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">input</span>
            <span class="hljs-attr">type</span>=<span class="hljs-string">"password"</span>
            <span class="hljs-attr">id</span>=<span class="hljs-string">"password"</span>
            <span class="hljs-attr">name</span>=<span class="hljs-string">"password"</span>
            <span class="hljs-attr">value</span>=<span class="hljs-string">{password}</span>
            <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setPassword(e.target.value)}
          /&gt;
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">htmlFor</span>=<span class="hljs-string">"confirm_password"</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{styles.label}</span>&gt;</span>
            Confirm Password
          <span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">input</span>
            <span class="hljs-attr">type</span>=<span class="hljs-string">"password"</span>
            <span class="hljs-attr">id</span>=<span class="hljs-string">"confirm_password"</span>
            <span class="hljs-attr">name</span>=<span class="hljs-string">"confirm_password"</span>
            <span class="hljs-attr">value</span>=<span class="hljs-string">{confirmPassword}</span>
            <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setConfirmPassword(e.target.value)}
          /&gt;
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"checkbox"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"terms"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"terms"</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"mr-2"</span> /&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">htmlFor</span>=<span class="hljs-string">"terms"</span>&gt;</span>
              I agree to the <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"#"</span>&gt;</span>Terms and Conditions<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span>&gt;</span>Register<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span></span>
    &lt;/div&gt;
</code></pre>
<p>    Putting all code together (<strong>Sign-up.jsx</strong>):</p>
<pre><code class="lang-javascript">
    <span class="hljs-keyword">import</span> { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
    <span class="hljs-keyword">import</span> { auth } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../config/firebase'</span>;
    <span class="hljs-keyword">import</span> { createUserWithEmailAndPassword } <span class="hljs-keyword">from</span> <span class="hljs-string">'firebase/auth'</span>;

    <span class="hljs-keyword">const</span> SignUp = <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-keyword">const</span> [name, setName] = useState(<span class="hljs-string">''</span>);
      <span class="hljs-keyword">const</span> [email, setEmail] = useState(<span class="hljs-string">''</span>);
      <span class="hljs-keyword">const</span> [password, setPassword] = useState(<span class="hljs-string">''</span>);
      <span class="hljs-keyword">const</span> [confirmPassword, setConfirmPassword] = useState(<span class="hljs-string">''</span>);

      <span class="hljs-keyword">const</span> handleCreateUser = <span class="hljs-keyword">async</span> (e) =&gt; {
        e.preventDefault();
        <span class="hljs-keyword">try</span> {
          <span class="hljs-keyword">await</span> createUserWithEmailAndPassword(auth, email, password);
          alert(<span class="hljs-string">'User created successfully'</span>);
        } <span class="hljs-keyword">catch</span> (error) {
          <span class="hljs-built_in">console</span>.log(error);
        }
      };

      <span class="hljs-keyword">return</span> (
        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>Register your Account<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">onSubmit</span>=<span class="hljs-string">{handleCreateUser}</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">label</span>&gt;</span>Name<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">input</span>
                <span class="hljs-attr">type</span>=<span class="hljs-string">'text'</span>
                <span class="hljs-attr">id</span>=<span class="hljs-string">'name'</span>
                <span class="hljs-attr">name</span>=<span class="hljs-string">'name'</span>
                <span class="hljs-attr">value</span>=<span class="hljs-string">{name}</span>
                <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setName(e.target.value)}
              /&gt;
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">htmlFor</span>=<span class="hljs-string">'email'</span>&gt;</span>Email<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">input</span>
                <span class="hljs-attr">type</span>=<span class="hljs-string">'email'</span>
                <span class="hljs-attr">id</span>=<span class="hljs-string">'email'</span>
                <span class="hljs-attr">name</span>=<span class="hljs-string">'email'</span>
                <span class="hljs-attr">value</span>=<span class="hljs-string">{email}</span>
                <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setEmail(e.target.value)}
              /&gt;
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">htmlFor</span>=<span class="hljs-string">'password'</span>&gt;</span>Password<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">input</span>
                <span class="hljs-attr">type</span>=<span class="hljs-string">'password'</span>
                <span class="hljs-attr">id</span>=<span class="hljs-string">'password'</span>
                <span class="hljs-attr">name</span>=<span class="hljs-string">'password'</span>
                <span class="hljs-attr">value</span>=<span class="hljs-string">{password}</span>
                <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setPassword(e.target.value)}
              /&gt;
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">htmlFor</span>=<span class="hljs-string">'confirm_password'</span>&gt;</span>
                Confirm Password
              <span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">input</span>
                <span class="hljs-attr">type</span>=<span class="hljs-string">'password'</span>
                <span class="hljs-attr">id</span>=<span class="hljs-string">'confirm_password'</span>
                <span class="hljs-attr">name</span>=<span class="hljs-string">'confirm_password'</span>
                <span class="hljs-attr">value</span>=<span class="hljs-string">{confirmPassword}</span>
                <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setConfirmPassword(e.target.value)}
              /&gt;
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">input</span>
                  <span class="hljs-attr">type</span>=<span class="hljs-string">'checkbox'</span>
                  <span class="hljs-attr">id</span>=<span class="hljs-string">'terms'</span>
                  <span class="hljs-attr">name</span>=<span class="hljs-string">'terms'</span>
                  <span class="hljs-attr">className</span>=<span class="hljs-string">'mr-2'</span>
                /&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">htmlFor</span>=<span class="hljs-string">'terms'</span>&gt;</span>
                  I agree to the{' '}
                  <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">'#'</span>&gt;</span>
                    Terms and Conditions
                  <span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
                <span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
              <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">'submit'</span>&gt;</span>Register<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
      );
    };

    <span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> SignUp;
</code></pre>
<p>    Now that you've created the sign-up function, it's time to add a sign-in function so users can log into your app.</p>
<p>    Here's how to create a simple sign-in function:</p>
<ul>
<li><p>In your project, create a new file named <strong>sign-in.jsx</strong>.</p>
</li>
<li><p>Import the initialized <code>auth</code> instance from <strong>firebase.js</strong> into <strong>sign-in.jsx</strong>.</p>
</li>
<li><p>Use the <code>signInWithEmailAndPassword</code> function from Firebase to allow users to sign in.</p>
</li>
</ul>
<p>Here’s the structure for the sign-in function:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> { auth } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../config/firebase'</span>;
<span class="hljs-keyword">import</span> { signInWithEmailAndPassword } <span class="hljs-keyword">from</span> <span class="hljs-string">'firebase/auth'</span>;

<span class="hljs-keyword">const</span> SignIn = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> [email, setEmail] = useState(<span class="hljs-string">''</span>);
  <span class="hljs-keyword">const</span> [password, setPassword] = useState(<span class="hljs-string">''</span>);

  <span class="hljs-keyword">const</span> handleSignIn = <span class="hljs-keyword">async</span> (e) =&gt; {
    e.preventDefault();
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">await</span> signInWithEmailAndPassword(auth, email, password);
      alert(<span class="hljs-string">'Signed in successfully'</span>);
    } <span class="hljs-keyword">catch</span> (error) {
      <span class="hljs-built_in">console</span>.error(error);
    }
  };

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>Sign In<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">onSubmit</span>=<span class="hljs-string">{handleSignIn}</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">htmlFor</span>=<span class="hljs-string">"email"</span>&gt;</span>Email<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">input</span>
            <span class="hljs-attr">type</span>=<span class="hljs-string">"email"</span>
            <span class="hljs-attr">value</span>=<span class="hljs-string">{email}</span>
            <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setEmail(e.target.value)}
          /&gt;
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">htmlFor</span>=<span class="hljs-string">"password"</span>&gt;</span>Password<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">input</span>
            <span class="hljs-attr">type</span>=<span class="hljs-string">"password"</span>
            <span class="hljs-attr">value</span>=<span class="hljs-string">{password}</span>
            <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setPassword(e.target.value)}
          /&gt;
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span>&gt;</span>Sign In<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> SignIn;
</code></pre>
<p>The visual display of the result from the code above both sign-up and sign-in</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727813072045/5a91493d-69a0-4a90-98b2-61905b23e460.gif" alt="Visual Result of signup and signin put together" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h2 id="heading-authentication-method-using-google">Authentication Method Using Google</h2>
<p>As mentioned earlier, you can collect users' emails directly through a form before they use your app and other ways to authenticate the users.</p>
<p><strong>To use Google authentication:</strong></p>
<ul>
<li><p>In the Firebase console, navigate to "Authentication" in the left-hand menu.</p>
</li>
<li><p>Click on the "Sign-in method" tab.</p>
</li>
<li><p>Enable "Google" under the "Sign-in providers" section (for this tutorial, we'll stick with Google, though you can choose other providers).</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727813344687/f1838b83-9af9-42a7-bf98-6a7d617cedc3.gif" alt="Enabling Google Auth" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Now that you've enabled Google authentication, you can create a Google sign-up and sign-in function for your app.</p>
<p>Let's go through how to set up a Google sign-up function:</p>
<ul>
<li><p>First, create a file named <strong>Google.jsx</strong> in your project.</p>
</li>
<li><p>Import <code>auth</code> and <code>GoogleAuthProvider</code> from the <strong>firebase.js</strong> file</p>
</li>
</ul>
<pre><code class="lang-javascript"><span class="hljs-comment">// Import the functions you need from the SDKs you need</span>
<span class="hljs-keyword">import</span> { initializeApp } <span class="hljs-keyword">from</span> <span class="hljs-string">'firebase/app'</span>;
<span class="hljs-keyword">import</span> { getAuth, GoogleAuthProvider } <span class="hljs-keyword">from</span> <span class="hljs-string">'firebase/auth'</span>;


<span class="hljs-keyword">const</span> firebaseConfig = {
  <span class="hljs-attr">apiKey</span>: ....,
  <span class="hljs-attr">authDomain</span>: ....,
  <span class="hljs-attr">projectId</span>:.... ,
  <span class="hljs-attr">storageBucket</span>: .... ,
  <span class="hljs-attr">messagingSenderId</span>: .... ,
  <span class="hljs-attr">appId</span>: ....,
  <span class="hljs-attr">measurementId</span>: ....,
};
<span class="hljs-comment">// Initialize Firebase</span>
<span class="hljs-keyword">const</span> app = initializeApp(firebaseConfig);
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> auth= getAuth(app);
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> googleProvider = <span class="hljs-keyword">new</span> GoogleAuthProvider(app);
</code></pre>
<ul>
<li>Initialize the Google provider and export it for use in other parts of your application.</li>
</ul>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { auth, googleProvider } <span class="hljs-keyword">from</span> <span class="hljs-string">'./firebase'</span>;  <span class="hljs-comment">// Adjust the path to your Firebase config file</span>
<span class="hljs-keyword">import</span> { signInWithPopup } <span class="hljs-keyword">from</span> <span class="hljs-string">'firebase/auth'</span>;
</code></pre>
<ul>
<li>Import the necessary Firebase function to authenticate a user. Use the <code>signInWithPopup</code> method to authenticate users with Google.</li>
</ul>
<p>While there are other authentication methods available, <code>signInWithPopup</code> is preferable as it keeps users within the app, avoiding the need to open a new browser tab.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> signInWithGoogle = <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">await</span> signInWithPopup(auth, googleProvider);
    alert(<span class="hljs-string">'Signed in successfully with Google'</span>);
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Error signing in with Google'</span>, error);
  }
};
</code></pre>
<ul>
<li>In your return statement, create a button to trigger the Google sign-in.</li>
</ul>
<pre><code class="lang-javascript"><span class="hljs-keyword">return</span> (
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{signInWithGoogle}</span>&gt;</span>Sign in with Google<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
);
</code></pre>
<p>The visual display of the result from the code above:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727813634035/14c00c73-f289-480a-a396-3abd839b3a75.gif" alt="Using signInWithPop()" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Firebase allows you to sign users out of your application easily. Here's how you can implement a sign-out function:</p>
<ul>
<li><p>First, import the <code>signOut</code> function from Firebase.</p>
</li>
<li><p>Once imported, you can call <code>signOut</code> to log the user out of the app.</p>
</li>
</ul>
<p>Here’s a simple example:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { auth } <span class="hljs-keyword">from</span> <span class="hljs-string">'./config/firebase'</span>; <span class="hljs-comment">// Adjust the path based on your file structure</span>
<span class="hljs-keyword">import</span> { signOut } <span class="hljs-keyword">from</span> <span class="hljs-string">'firebase/auth'</span>;

<span class="hljs-keyword">const</span> handleSignOut = <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">await</span> signOut(auth);
    alert(<span class="hljs-string">'User signed out successfully'</span>);
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Error signing out:'</span>, error);
  }
};
</code></pre>
<p>With this function, users can easily log out of the app.</p>
<p>In the return statement, you'll typically have a button that triggers the <strong>handleSignOut</strong> function when clicked.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">return</span> (
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>Welcome to the app!<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{handleSignOut}</span>&gt;</span>Sign Out<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
</code></pre>
<p>The visual display of the result from the code above</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727813736679/21c2162b-b376-43b7-87cb-242d78acef38.gif" alt="signOut() visual display" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Make sure your Firebase project is configured to handle authentication correctly, including Google sign-in, to ensure a smooth sign-in and sign-out experience.</p>
<h2 id="heading-step-5-how-to-upload-to-github"><strong>Step 5: How to Upload to GitHub</strong></h2>
<p>Before pushing your project to GitHub, make sure to store your Firebase API key in an environment variable to keep it secure. This will prevent sensitive information from being exposed in your shared code.</p>
<p><strong>Creating a .env file</strong></p>
<ul>
<li>At the root of your application, create a <strong>.env</strong> file.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727813941388/5e647b8c-c76b-4671-b44b-21ac4dcddc89.png" alt="Storing the API keys in .env file" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<ul>
<li><p>Add your Firebase API key to the <strong>firebase.js</strong> file.</p>
</li>
<li><p>Use <code>import</code> or <code>process.env</code> to access your Firebase API key. Since the app was created with Vite, I used <code>import</code>.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727814253953/514db02f-44f8-44fc-b03c-4cf77cb5c4ba.png" alt="Firebase file" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<ul>
<li>Finally, update your <strong>.gitignore</strong> file to include the <strong>.env</strong> file. This step also protects other sensitive files and directories, like <strong>node_modules</strong>.</li>
</ul>
<pre><code class="lang-javascript"># Logs
logs
node_modules
.env
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In conclusion, this guide explains how to integrate Firebase Authentication into your app. Firebase simplifies adding authentication features such as email/password and Google login.</p>
<p>By setting up a Firebase project, installing, and initializing it in your app, you can efficiently build secure user sign-up and sign-in functionalities without the need to start from scratch or set up a server.</p>
<p>If you found this article helpful, share it with others who may also find it interesting.</p>
<p>Stay updated with my projects by following me on <a target="_blank" href="https://https//twitter.com/ijaydimples">Twitter</a>, <a target="_blank" href="https://www.linkedin.com/in/ijeoma-igboagu/">LinkedIn</a> and <a target="_blank" href="https://github.com/ijayhub">GitHub</a>.</p>
<p>The code I used for this tutorial article can be found on my <a target="_blank" href="https://github.com/ijayhub/authentication-example-tutorial">GitHub</a>.</p>
<p>Thank you for reading.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Full-Stack Kanban Task Management App With TypeScript, Next.js, Redux-toolkit, and Firebase ]]>
                </title>
                <description>
                    <![CDATA[ By Olasunkanmi Balogun In this in-depth tutorial, you'll learn how to build a full-stack Kanban task management app. Along the way, we'll explore the synergies between technologies like Next.js (featuring a dive into the app router), Next-auth for us... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/build-full-stack-app-with-typescript-nextjs-redux-toolkit-firebase/</link>
                <guid isPermaLink="false">66d4608a246e57ac83a2c7bd</guid>
                
                    <category>
                        <![CDATA[ Firebase ]]>
                    </category>
                
                    <category>
                        <![CDATA[ full stack ]]>
                    </category>
                
                    <category>
                        <![CDATA[ handbook ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Next.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ TypeScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Tue, 26 Mar 2024 21:44:04 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/03/Option-1.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Olasunkanmi Balogun</p>
<p>In this in-depth tutorial, you'll learn how to build a full-stack Kanban task management app. Along the way, we'll explore the synergies between technologies like <a target="_blank" href="https://nextjs.org"><code>Next.js</code></a> (featuring a dive into the app router), <a target="_blank" href="https://next-auth.js.org"><code>Next-auth</code></a> for user authentication, and <a target="_blank" href="https://firebase.google.com/">Firebase</a>, a backend as a service platform to save user data in a database. </p>
<p>We'll also cover how you can integrate Firebase Firestore with <a target="_blank" href="https://redux-toolkit.js.org/"><code>Redux Toolkit</code></a> which enables you to cache data you have retrieved from the database to improve performance. You will also learn how to manage state with Redux Toolkit.</p>
<p>To wrap it up, we will employ <a target="_blank" href="https://www.npmjs.com/package/react-beautiful-dnd"><code>React-beautiful-dnd</code></a>, a library that effortlessly integrates drag-and-drop interactions into our Kanban boards to enhance the user experience.</p>
<p>Here's what we'll cover:</p>
<ol>
<li>How to implement authentication with the <code>next-auth.js</code> library</li>
<li>How to set up and integrate the <code>Redux</code> store with Firestore in Next.js.</li>
<li>How to build and populate the Kanban app markup with data</li>
<li>How to implement Create, Read, Update, and Delete (CRUD) operations on boards and tasks.</li>
<li>How to implement drag and drop with <code>react-beautiful-dnd</code> library.</li>
</ol>
<h2 id="heading-prerequisites">Prerequisites</h2>
<ul>
<li>You should have prior experience working with the <code>Reactjs/Next.js</code> framework.</li>
<li>You should have an understanding of type annotations in TypeScript, and ultimately, working with <code>TypeScript</code> in React.</li>
<li>An understanding of DSA in <code>JavaScript</code> is a plus.</li>
<li>Experience with <code>Redux-toolkit</code> library will also be a plus. </li>
</ul>
<p>A few notes: </p>
<ul>
<li>This article will focus primarily on functionality, but we'll use <code>Tailwind CSS</code> for styling.</li>
<li>I'll also include comments with each code snippet provided throughout this article to explain the code better. Keep an eye out for them. </li>
</ul>
<h2 id="heading-table-of-contents">Table Of Contents</h2>
<ol>
<li><a class="post-section-overview" href="#heading-how-to-implement-authentication-with-next-authjs">How To Implement Authentication With next-auth.js</a></li>
<li><a class="post-section-overview" href="#heading-how-to-configure-the-redux-store">How to Configure the Redux Store</a></li>
<li><a class="post-section-overview" href="#heading-how-to-create-your-kanban-app-markup">How to Create Your Kanban App Markup</a></li>
<li><a class="post-section-overview" href="#heading-how-to-configure-firebase-firestore">How to Configure Firebase Firestore</a></li>
<li><a class="post-section-overview" href="#heading-how-to-add-initial-data-to-the-firestore-database">How to Add Initial Data to the Firestore Database</a></li>
<li><a class="post-section-overview" href="#heading-how-to-use-rtk-query-to-fetch-data-from-cloud-firestore">How to Use RTK Query to Fetch Data from Cloud Firestore</a></li>
<li><a class="post-section-overview" href="#heading-how-to-fetch-and-populate-data">How to Fetch and Populate Data</a><ul>
<li><a class="post-section-overview" href="#heading-how-to-populate-the-navbar">How to populate the navbar</a></li>
<li><a class="post-section-overview" href="#heading-how-to-populate-the-sidebar">How to populate the sidebar</a></li>
<li><a class="post-section-overview" href="#heading-how-to-populate-the-boardtasks-component">How to populate the BoardTasks component</a></li>
</ul>
</li>
<li><a class="post-section-overview" href="#heading-how-to-implement-crud-operations">How to Implement CRUD Operations</a><ul>
<li><a class="post-section-overview" href="#heading-how-to-add-and-edit-a-board">How to add and edit a board</a></li>
<li><a class="post-section-overview" href="#heading-how-to-add-and-edit-tasks">How to add and edit tasks</a></li>
<li><a class="post-section-overview" href="#heading-how-to-delete-boards-and-tasks">How to delete boards and tasks</a></li>
</ul>
</li>
<li><a class="post-section-overview" href="#heading-how-to-implement-drag-and-drop-functionality">How to Implement Drag and Drop Functionality</a></li>
<li><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></li>
</ol>
<p>When you are ready, let's dive in.</p>
<h2 id="heading-how-to-implement-authentication-with-next-authjs">How To Implement Authentication With <code>next-auth.js</code></h2>
<p>Begin by running the following command in your terminal to create a new <code>Next.js</code> project:</p>
<pre><code class="lang-npm">npx create-next-app@latest kanban-app-tutorial
</code></pre>
<p>Throughout the installation process, you will encounter prompts. Make sure you enable <code>TypeScript</code> and <code>Tailwind CSS</code>, as both will be integral to our project development.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/1-3.png" alt="Nextjs project installation prompts" width="600" height="400" loading="lazy"></p>
<p>Go ahead and clean out the redundant code that comes with the project. Delete the content in the <code>page.tsx</code> file and paste the code below as a placeholder:</p>
<pre><code class="lang-tsx">export default function Home() {
  return (
    &lt;main&gt;
      &lt;p&gt;Hi&lt;/p&gt;
    &lt;/main&gt;
  )
}
</code></pre>
<p>Also, edit the content in the <code>global.css</code> file and leave only the <code>Tailwind CSS</code> imports. </p>
<p>Once these modifications are complete, install the <code>next-auth.js</code> library with the following command:</p>
<pre><code>npm install next-auth
</code></pre><p>After successful installation, create an <code>api</code> folder in your root <code>app</code> folder, and inside it create an <code>auth</code> folder. Then, create a <code>[...nextauth]</code> folder inside the <code>auth</code> folder.</p>
<p>Finally, create two files named <code>route.ts</code> and <code>options.ts</code> inside the <code>[...nextauth]</code> folder. </p>
<p>Your file structure should look like the following:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/2-2.png" alt="Nextjs app file structure" width="600" height="400" loading="lazy"></p>
<p>Among the various <code>next-auth.js</code> providers, we will exclusively utilize the Google Provider to execute the authentication process.</p>
<p>In the <code>option.ts</code> file, paste the following code:</p>
<pre><code class="lang-tsx">import type { NextAuthOptions } from "next-auth";
import GoogleProvider from "next-auth/providers/google";

export const options: NextAuthOptions = {
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID as string,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
    }),
  ],
  secret: process.env.NEXTAUTH_URL,
};
</code></pre>
<p>Here, we imported the <code>NextAuthOptions</code> type provided by <code>next-auth</code> for the sake of type safety concerning the <code>options</code> variable.</p>
<p>In the above code, the <code>options</code> object is where whichever provider we want to utilize will be housed (the Google Provider in this case). </p>
<p>You can get your <code>clientId</code> and <code>clientSecret</code> values from the Google Cloud Platform. If you need a step-by-step guide on how to get them, refer to this guide. </p>
<p>Once you have gotten them, create a <code>.env</code> file in the root folder of your application and paste the values in their respective variables.</p>
<p>Lastly, create a secret key for the <code>NEXTAUTH_SECRET</code> variable using the following terminal command:</p>
<pre><code>openssl rand -base64 <span class="hljs-number">32</span>
</code></pre><p>Ultimately, your <code>.env</code> file should contain these variables and values:</p>
<pre><code>GOOGLE_CLIENT_ID = &lt;client ID value&gt;
GOOGLE_CLIENT_SECRET = &lt;client secret value&gt;
NEXT_AUTH_SECRET = &lt;next auth secret&gt;
</code></pre><p>Important: You’ll also need these environment variables in production. So, don’t forget to update your production environment variable in your project settings on Vercel.</p>
<p>Proceed to the <code>route.ts</code> file and paste the following code in it:</p>
<pre><code><span class="hljs-keyword">import</span> NextAuth <span class="hljs-keyword">from</span> <span class="hljs-string">"next-auth/next"</span>;
<span class="hljs-keyword">import</span> { options } <span class="hljs-keyword">from</span> <span class="hljs-string">"./options"</span>;

<span class="hljs-keyword">const</span> handler = NextAuth(options);

<span class="hljs-keyword">export</span> { handler <span class="hljs-keyword">as</span> GET, handler <span class="hljs-keyword">as</span> POST };
</code></pre><p>Here, we imported the <code>options</code> variable from the <code>option.ts</code> file and passed it as a parameter to the <code>NextAuth</code> function, assigning the result to the <code>handler</code> variable.</p>
<p>The final statement ensures that any GET or POST request sent to the <code>api/auth/[...nextauth]</code> route will be managed by <code>next-auth.js</code>.</p>
<p>However, authentication won't be initiated yet because we haven't informed <code>next-auth.js</code> about which pages should be protected. </p>
<p>To implement protected routes, generate a <code>middleware.ts</code> file in the root <code>src</code> folder and insert the following code:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">export</span> { <span class="hljs-keyword">default</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">'next-auth/middleware'</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> config = { matcher: [<span class="hljs-string">'/'</span>] }
</code></pre>
<p>The <code>matcher</code> property in the <code>config</code> object is an array containing the routes you want the <code>middleware</code> to protect. In this case, <code>'/'</code> designates the home page, indicating that the <code>middleware</code> protects the home page.</p>
<p>When you run your project server (with <code>npm run dev</code>), you should see an authentication page as seen below:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/3-3.png" alt="Nextauth.js auth page" width="600" height="400" loading="lazy"></p>
<p>Now, let's configure the <code>Redux</code> store in our application.</p>
<h2 id="heading-how-to-configure-the-redux-store">How to Configure the Redux Store</h2>
<p>To set up the Redux store in your application, follow these steps:</p>
<ol>
<li>Begin by installing the necessary packages. Run the following command in your terminal:</li>
</ol>
<pre><code class="lang-npm">npm install @reduxjs/toolkit react-redux
</code></pre>
<p>This installs the <code>Redux Toolkit</code> and <code>react-redux</code> for React bindings.</p>
<ol start="2">
<li>In the root <code>src</code> directory, create a folder named <code>redux</code>. Within this folder, create a <code>store.ts</code> file. Paste the following code into the <code>store.ts</code> file:</li>
</ol>
<pre><code class="lang-tsx">   // store.ts

   import { configureStore } from "@reduxjs/toolkit";
   import { setupListeners } from "@reduxjs/toolkit/dist/query";

   // Create the Redux store
   export const store = configureStore({
     reducer: {}, // Add your reducers here
   });

   // Setup listeners for refetch behaviors
   setupListeners(store.dispatch);

   // Define RootState and AppDispatch types
   export type RootState = ReturnType&lt;typeof store.getState&gt;;
   export type AppDispatch = typeof store.dispatch;
</code></pre>
<p>In this code snippet, <code>configureStore</code> is used to create the Redux store, and <code>setupListeners</code> is called to handle <code>refetchOnFocus</code> and <code>refetchOnReconnect</code> behaviours.</p>
<ol start="3">
<li>Now, create another file in the same <code>redux</code> folder named <code>hooks.ts</code> and add the following code:</li>
</ol>
<pre><code class="lang-tsx">// hooks.ts
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
import type { RootState, AppDispatch } from "./store";
// Typed versions of useDispatch and useSelector hooks

export const useAppDispatch = () =&gt; useDispatch&lt;AppDispatch&gt;();
export const useAppSelector: TypedUseSelectorHook&lt;RootState&gt; = useSelector;
</code></pre>
<p>This code creates typed versions of the <code>useDispatch</code> and <code>useSelector</code> hooks to ensure type safety when interacting with the Redux store.</p>
<ol start="4">
<li>Still in the <code>redux</code> folder, create a file named <code>provider.tsx</code> with the following code snippet:</li>
</ol>
<pre><code class="lang-tsx">// provider.tsx
'use client'
import { store } from "./store";
import { Provider } from "react-redux";

// Custom provider component
export function Providers({ children }: { children: React.ReactNode }) {
   return &lt;Provider store={store}&gt;{children}&lt;/Provider&gt;;
 }
</code></pre>
<p>This file defines a custom provider component to wrap around your application components.</p>
<ol start="5">
<li>In your application layout file (<code>src/app/layout.tsx</code>), import the <code>Providers</code> component and wrap it around your main layout as seen below:</li>
</ol>
<pre><code class="lang-tsx">// layout.tsx

import type { Metadata } from 'next'
import { Plus_Jakarta_Sans } from "next/font/google";
import './globals.css'
import { Providers } from "@/components/redux/provider";

//font we'll use throughout the project
const pjs = Plus_Jakarta_Sans({ subsets: ["latin"], display: "swap" });
// Metadata definition
export const metadata: Metadata = {
   title: 'Create Next App',
   description: 'Generated by create next app',
  }

// RootLayout component
export default function RootLayout({
   children,
   }: {
   children: React.ReactNode
 }) {
   return (
      &lt;html lang="en" className={pjs.className}&gt;
        &lt;body&gt;
          &lt;Providers&gt;
            {children}
          &lt;/Providers&gt;
        &lt;/body&gt;
      &lt;/html&gt;
  );
}
</code></pre>
<p>By wrapping your components with the <code>Providers</code> component, you ensure that every component in your application has access to the Redux store.</p>
<p>Up to this point, your folder structure should look like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/4-1.png" alt="nextjs folder app structure" width="600" height="400" loading="lazy"></p>
<p>With these steps, you have successfully integrated the Redux store into your application, and you are ready to create <a target="_blank" href="https://redux-toolkit.js.org/tutorials/quick-start#create-a-redux-state-slice">slices</a> for your application.</p>
<p>Before diving into the implementation of slices, let's create the markup for our application.</p>
<h2 id="heading-how-to-create-your-kanban-app-markup">How to Create Your Kanban App Markup</h2>
<p>This section guides you through the process of building the markup for your Kanban app. By the end of this section, your markup should resemble the image below:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/8-1.png" alt="Kanban app markup" width="600" height="400" loading="lazy"></p>
<p>Let's start by creating the navbar component.</p>
<ol>
<li>Begin by establishing a <code>components</code> folder within the <code>app</code> directory. Inside it, create a <code>Navbar.tsx</code> file and insert the following code:</li>
</ol>
<pre><code class="lang-tsx">// src/app/components/Navbar.tsx

export default function Navbar() {

return (
  &lt;nav className="bg-white border flex h-24"&gt;
    &lt;div className="flex-none w-[18.75rem] border-r-2 flex items-center pl-[2.12rem]"&gt;
      &lt;p className="font-bold text-3xl"&gt; Kanban App &lt;/p&gt;
    &lt;/div&gt;

   &lt;div className="flex justify-between w-full items-center pr-[2.12rem]"&gt;
       &lt;p className="text-black text-2xl font-bold pl-6"&gt;
         Board Name
       &lt;/p&gt;

      &lt;div className="flex items-center space-x-3"&gt;
        &lt;button className="bg-blue-500 text-black px-4 py-2 flex rounded-3xl items-center space-x-2"&gt;
           &lt;p&gt;+ Add New Task&lt;/p&gt;
        &lt;/button&gt;
          &lt;div className="flex items-center"&gt;
            &lt;button className="text-3xl mb-4"&gt;...&lt;/button&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/nav&gt;
  )}
</code></pre>
<ol start="2">
<li>Next, render the <code>Navbar</code> component in the <code>src/app/layout.tsx</code> file:</li>
</ol>
<pre><code class="lang-tsx">   import type { Metadata } from 'next'
   import { Providers } from "@/components/redux/provider";
   import Navbar from './components/Navbar';
   import { Plus_Jakarta_Sans } from "next/font/google";
   import './globals.css'

   const pjs = Plus_Jakarta_Sans({ subsets: ["latin"], display: "swap" });

   export const metadata: Metadata = {
    title: 'Create Next App',
    description: 'Generated by create next app',
   }

   export default function RootLayout({
    children,
   }: {
    children: React.ReactNode
   }) {
   return (
    &lt;html lang="en" className={pjs.className}&gt;
      &lt;body&gt;
        &lt;Providers&gt;
          &lt;Navbar /&gt;  {/* Render the component here */}
          {children}
        &lt;/Providers&gt;
      &lt;/body&gt;
    &lt;/html&gt;
    )}
</code></pre>
<p>Now, the <code>Navbar</code> component is available globally across all pages in the application since it's rendered in the root layout component.</p>
<p>After implementing these changes, upon signing in to your application on <code>localhost:3000</code>, you should observe the UI as depicted in the image below. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/5-4.png" alt="Navbar markup" width="600" height="400" loading="lazy"></p>
<p>The placeholder "Current board name" in the navbar will eventually be replaced with the name of an active board once we populate the app with data.</p>
<p>The "Add New Task" button is designed to open the "Add new tasks" modal, and the ellipsis next to it will trigger a dropdown for editing and deleting a board. The implementation of this dropdown is the focus of the next step.</p>
<ol start="3">
<li>Create a <code>Dropdown.tsx</code> file in the same <code>components</code> folder, and paste the following code into it:</li>
</ol>
<pre><code class="lang-tsx">   //src/app/components/Dropdown.tsx

   interface IDropdown {
    show: boolean
   }

   export default function Dropdown({ show }: IDropdown) {

    return (
      &lt;div
        className={`${
          show ? "block" : "hidden"
        } w-48 absolute top-full bg-white
         border shadow-lg right-0 py-2 rounded-2xl`}
      &gt;
        &lt;div className="hover:bg-gray-300"&gt;
          &lt;button className="text-sm px-4 py-2"&gt;Edit Board&lt;/button&gt;
        &lt;/div&gt;
        &lt;div className="hover:bg-gray-300"&gt;
          &lt;button className="text-sm px-4 py-2"&gt;
            Delete Board
          &lt;/button&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    )}
</code></pre>
<p>This component takes a <code>show</code> parameter of type <code>boolean</code> as a prop. The dropdown content is displayed when <code>show</code> is <code>true</code> and hidden when it's <code>false</code>.</p>
<p>Now, proceed to the <code>Navbar.tsx</code> file and update the code to render the <code>Dropdown</code> component. Pay attention to the comments in the code snippet below to get a grasp of the updates here:</p>
<pre><code class="lang-tsx">   //src/app/components/Navbar.tsx

   'use client' // we made this a client component since we have to make use of useState

   import Dropdown from "./Dropdown";
   import { useState } from 'react'

   export default function Navbar() {

   const [show, setShow] = useState&lt;boolean&gt;(false); // this will manage the state of the show variable

   return (
    &lt;nav className="bg-white border flex h-24"&gt;
      &lt;div className="flex-none w-[18.75rem] border-r-2 flex items-center pl-[2.12rem]"&gt;
        &lt;p className="font-bold text-3xl"&gt; Kanban App &lt;/p&gt;
      &lt;/div&gt;

      &lt;div className="flex justify-between w-full items-center pr-[2.12rem]"&gt;
        &lt;p className="text-black text-2xl font-bold pl-6"&gt;Current board name&lt;/p&gt;

        &lt;div className="flex items-center space-x-3"&gt;
          &lt;button className="bg-blue-500 text-black px-4 py-2 flex rounded-3xl items-center space-x-2"&gt;
            &lt;p&gt;+ Add New Task&lt;/p&gt;
          &lt;/button&gt;
          &lt;div className="relative flex items-center"&gt;
            &lt;button 
            onClick={() =&gt; setShow(!show)} // trigger function that shows dropdown here
            className="text-3xl mb-4"&gt;...&lt;/button&gt;
            &lt;Dropdown show={show}/&gt;  {/* render dropdown here and pass show as prop */}
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/nav&gt;
    )}
</code></pre>
<p>After you make these adjustments in your <code>Navbar</code> component, you can now toggle the dropdown by clicking on the ellipsis:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/6-1.gif" alt="Dropdown toggle" width="600" height="400" loading="lazy"></p>
<p>In the next step, we'll implement components that make up the body of our application, specifically the sidebar components and board that displays the tasks.</p>
<ol start="4">
<li>To implement the sidebar, create a <code>Sidebar.tsx</code> file within the same <code>components</code> directory. Paste the following code into it:</li>
</ol>
<pre><code class="lang-tsx">   // src/app/components/Sidebar.tsx

   export default function Sidebar() {
   return (
    &lt;aside className="w-[18.75rem] flex-none dark:bg-dark-grey h-full py-6 pr-6"&gt;
      &lt;p className="text-medium-grey pl-[2.12rem] text-[.95rem] font-semibold uppercase pb-3"&gt;
        {`All Boards (0)`}
      &lt;/p&gt;
      &lt;div className="cursor-pointer flex items-center rounded-tr-full rounded-br-full bg-blue-500 space-x-2 pl-[2.12rem] py-3 pb-3"&gt;
        &lt;p className="text-white text-lg capitalize"&gt;Current board name&lt;/p&gt;
      &lt;/div&gt;
      &lt;button className="flex items-center space-x-2 pl-[2.12rem] py-3"&gt;
        &lt;p className="text-base font-bold capitalize text-main-purple"&gt;
          + Create New Board
        &lt;/p&gt;
      &lt;/button&gt;
    &lt;/aside&gt;
   );
   }
</code></pre>
<ol start="5">
<li>Following this, create another file named <code>BoardTasks.tsx</code> and paste the code below to it. This component will contain the contents of an active board task. Since the app is not yet populated with data, we'll use a placeholder that will be substituted by actual tasks later.</li>
</ol>
<pre><code class="lang-tsx">   // src/app/components/BoardTasks.tsx

   export default function BoardTasks() {
   return (
    &lt;div className="overflow-x-auto overflow-y-auto w-full bg-stone-200"&gt;
      &lt;div className="w-full h-full flex justify-center items-center"&gt;
        &lt;div className="flex flex-col items-center"&gt;
          &lt;p className="text-black text-sm"&gt;
            This board is empty. Create a new column to get started.
          &lt;/p&gt;
          &lt;button className="bg-blue-500 text-black px-4 py-2 flex mt-6 rounded-3xl items-center space-x-2"&gt;
            &lt;p&gt;+ Add New Column&lt;/p&gt;
          &lt;/button&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    );
   }
</code></pre>
<ol start="6">
<li>Then, paste the following code in your <code>src/app/page.tsx</code> file to render both the <code>Sidebar</code> and <code>BoardTasks</code> components:</li>
</ol>
<pre><code class="lang-tsx">   import Sidebar from "./components/Sidebar";
   import BoardTasks from "./components/BoardTasks";

   export default function Home() {
   return (
    &lt;main className="flex h-full"&gt;
      &lt;Sidebar /&gt;
      &lt;BoardTasks /&gt;
    &lt;/main&gt;
   );
   }
</code></pre>
<p>Up to this point, your file structure should resemble the following:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/7-2.png" alt="Nextjs app file structure" width="600" height="400" loading="lazy"></p>
<ol start="7">
<li>Finally, in the root <code>layout.tsx</code> file, update the style of the <code>body</code> tag as shown below:</li>
</ol>
<pre><code class="lang-tsx"> // src/app/layout.tsx
   // rest of the code here
   export default function RootLayout({
   children,
   }: {
   children: React.ReactNode;
   }) {
   return (
    &lt;html lang="en" className={pjs.className}&gt;
      &lt;body className='pb-24 h-screen overflow-hidden'&gt; {/* update style here*/}
        {/* rest of the code here */}
      &lt;/body&gt;
    &lt;/html&gt;
   );
   }
</code></pre>
<p>This adjustment ensures that the content in the <code>BoardTasks</code> component is scrollable on both the x and y axis if it exceeds the length and breadth of the screen.</p>
<p>With this, the markup for our app is complete. Your UI should resemble this if you have been following along:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/8-2.png" alt="Complete Kanban app markup" width="600" height="400" loading="lazy"></p>
<p>The sidebar will display the number of boards and the names of available boards in the app. Clicking different boards in the sidebar will switch to the selected board, and clicking "Create New Board" in the sidebar opens the "Add New Board" modal.</p>
<p>Right next to the sidebar, the tasks in each board will be displayed in columns. The current screen will be displayed if the board has no tasks yet. The "+Add New Column" button will open a modal used to add a column to a board.</p>
<p>All these features will be activated as we populate the application with data.</p>
<p>Moving forward, the next section will guide you in integrating Firebase Firestore into your application.</p>
<h2 id="heading-how-to-configure-firebase-firestore">How to Configure Firebase Firestore</h2>
<p>To integrate Firestore into your application, you'll need to create a Firebase project using the <a target="_blank" href="https://console.firebase.google.com/u/0/?_gl=1*1r24b4a*_ga*MTkyMjc0OTE3NC4xNjc4MDIwMDMw*_ga_CW55HF8NVT*MTcwMDMxMTAwNC4xNjUuMS4xNzAwMzExNDU0LjQ3LjAuMA..">Firebase console</a>. Feel free to name the project according to your preference, but for the sake of this tutorial, let's name it "Kanban-app-tutorial."</p>
<p>Once the project is created, you'll be prompted to register your app. After registration, install Firebase in your application. Install the Firebase package with the following command in your terminal:</p>
<pre><code class="lang-npm">npm install firebase
</code></pre>
<p>Now, you need to initialize Cloud Firestore in your application. Create a folder named <code>utils</code> and within it, create a <code>firebaseConfig.ts</code> file. Paste your Firebase configuration into it as shown below:</p>
<pre><code class="lang-tsx">import { initializeApp } from "firebase/app";
import { getFirestore } from "firebase/firestore";

// Your web app's Firebase configuration
const firebaseConfig = {
 // Paste your Firebase config here
};

// Initialize Firebase
const app = initializeApp(firebaseConfig);
// Initialize Firestore and export it
export const db = getFirestore(app);
</code></pre>
<p>Finally, navigate to your newly created project on the cloud platform and create a Cloud Firestore database. Following this, proceed to the "Rules" tab and modify the read and write rules from false to true as illustrated in the image:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/9-1.png" alt="Firestore rules tab" width="600" height="400" loading="lazy"></p>
<p>This will enable anyone to add data to the database without restrictions. Note that this is not recommended for production – we are implementing it like this for the purpose of this article.  </p>
<p>With this setup complete, we can now begin adding data to the Cloud Firestore.</p>
<h2 id="heading-how-to-add-initial-data-to-the-firestore-database">How to Add Initial Data to the Firestore Database</h2>
<p>Our goal is to ensure that users aren't greeted with an empty board when they complete the authentication process. Instead, we want to present them with dummy task data that they can interact with, allowing them to explore the application's features.</p>
<p>Also, we aim to make this data user-specific, forming the foundation for each user to build upon by creating new boards and tasks. </p>
<p>To accomplish this, when a new user signs in, we'll generate a new document in the database for that user.</p>
<p>Here's a breakdown of our approach:</p>
<ol>
<li><p><strong>Check if the user is new</strong>:
We need to determine whether the user is signing in for the first time. This way, we can automatically create a new document for the user in the database.</p>
</li>
<li><p><strong>Create a new user document</strong>:
If the user is new, we proceed to create a new data entry in the database specifically for that user.</p>
</li>
</ol>
<p>To begin, create a <code>data.js</code> file inside the <code>utils</code> folder we created earlier (this will contain our dummy data for a board). Paste the provided data code into it.</p>
<pre><code class="lang-tsx">//used to generate new id
export const id = () =&gt; Math.random().toString(36).substring(2, 10);

export const data = {
  "boards": [
    {
      id: id(),
      name: "Roadmap",
      columns: [
        {
          id: id(),
          name: "Now",
          tasks: [
            {
              id: id(),
              title: "Launch version one",
              status: "Now"
            },
            {
              id: id(),
              title: "Review early feedback and plan next steps for roadmap",
              status: "Now"
            }
          ]
        },
        {
          id: id(),
          name: "Next",
          tasks: []
        },
        {
          id: id(),
          name: "Later",
          tasks: []
        }
      ]
    }
  ]
}
</code></pre>
<p>Now, navigate to the <code>src/app/page.tsx</code> file and modify it as demonstrated below:</p>
<pre><code class="lang-tsx">"use client";
import Sidebar from "./components/Sidebar";
import BoardTasks from "./components/BoardTasks";
// Firestore methods: collection and getDocs for document reference, addDoc for adding a document
import { collection, getDocs, addDoc } from "firebase/firestore";
// Connect our app to Firestore
import { db } from "./utils/firebaseConfig";
import { useEffect, useState } from "react";
// Import getSession from next-auth library to retrieve signed-in user details
import { getSession } from "next-auth/react";
// Import data from data.json, used to initialize the Firestore database for new users
import { data } from "./utils/data.json";

export default function Home() {
  // Manage user details in this state. Key index in TypeScript ensures type safety.
  const [userDetails, setUserDetails] = useState&lt;{ [key: string]: any }&gt;();

  // Get user session using getSession. Contains user's name and email, then passed to user details state.
  const getUserSession = async () =&gt; {
    const session = await getSession();
    if (session) {
      setUserDetails(session.user);
    }
  };

  const handleAddDoc = async () =&gt; {
    if (userDetails) {
      // Execute code inside curly braces only when `userDetails` is true.

      // Reference to the document with the user's email to check its existence in the database.
      const docRef = collection(db, "users", userDetails.email, "tasks");
      const getDos = await getDocs(docRef);

      // If the document exists, terminate the program.
      if (getDos.docs.length &gt; 0) {
   ;     return;
      } else {
        // If not, submit a new document containing the data from data.json for the user in the database.
        try {
          await addDoc(
            collection(db, "users", userDetails.email, "tasks"),
            data
          );
        } catch (e) {
          console.error("Error adding document: ", e);
        }
      }
    }
  };

  useEffect(() =&gt; {
    getUserSession(); // Call getUserSession function after the page renders.
  }, []);

  useEffect(() =&gt; {
    handleAddDoc(); // Call handleAddDoc function after the user details update.
  }, [userDetails]);

  return (
    &lt;main className="flex h-full"&gt;
      &lt;Sidebar /&gt;
      &lt;BoardTasks /&gt;
    &lt;/main&gt;
  );
}
</code></pre>
<p>This code ensures that when a user logs in, their details are fetched and checked. If it's a new user, a new document with initial dummy data is added to the Firestore database under the user's email. Make sure you've read through the comments I added if you need any further explanation.</p>
<p>Upon visiting your project console, you'll notice the presence of a document created for the signed-in user (which is you):</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/10-1.png" alt="Firestore document presence" width="600" height="400" loading="lazy"></p>
<p>The initial setup is now complete, enabling us to fetch data and initiate the population of our application. But before directly interacting with the data, we'll employ RTK query, which is included in the Redux toolkit package, as an intermediary.</p>
<p>This approach not only eliminates the need to write data fetching and caching logic in various components repeatedly, but also eliminates background revalidation, so we don't need explicit manual refreshes. </p>
<p>The next section will explore this process. </p>
<h2 id="heading-how-to-use-rtk-query-to-fetch-data-from-cloud-firestore">How to Use <code>RTK Query</code> to Fetch Data from Cloud Firestore</h2>
<p>Here, we'll begin the process of creating slices for the reducer, starting with the development of the slice dedicated to data fetching.</p>
<p>Within the <code>src/redux</code> directory, create a new folder named <code>services</code>.</p>
<p>Inside the newly created <code>services</code> folder, establish a file named <code>apiSlice.ts</code>. Copy and paste the provided code into this file:</p>
<pre><code class="lang-tsx">   import { createApi, fakeBaseQuery } from "@reduxjs/toolkit/query/react";
   import { getSession } from "next-auth/react";
   import { collection, getDocs } from "firebase/firestore";
   import { db } from "@/components/app/utils/firebaseConfig";

   // Create the Firestore API using createApi
   export const fireStoreApi = createApi({
   reducerPath: "firestoreApi", // Specifies the path for the reducer
   baseQuery: fakeBaseQuery(), // Utilizes fakeBaseQuery because Firebase has no traditional REST API endpoint
   tagTypes: ["Tasks"], // Defines tag types for caching purposes
   endpoints: (builder) =&gt; ({
    fetchDataFromDb: builder.query&lt;{ [key: string]: any }[], void&gt;({
      // Utilizes builder.query for making requests; builder.mutation can be used for CRUD operations
      async queryFn() {
        // Employs queryFn since we are not fetching data from a conventional API;
        // This allows us to include arbitrary code, as long as we return our data in the { data: results } format

        try {
          const session = await getSession();
          const { user } = session!;
            const ref = collection(db, `users/${user?.email}/tasks`);
            const querySnapshot = await getDocs(ref);
            return { data: querySnapshot.docs.map((doc) =&gt; doc.data()) };
            // Data must be returned in this format when using queryFn

        } catch (e) {
          return { error: e };
        }
      },
      providesTags: ["Tasks"], // Specifies tags for caching
    }),
   }),
   });

   // Export hooks for using the created endpoint
   export const { useFetchDataFromDbQuery } = fireStoreApi;
</code></pre>
<p>This code establishes a Firestore API using <code>createApi</code>, defining an endpoint for fetching data. The use of <code>fakeBaseQuery</code> is intentional, considering Firebase doesn't have a conventional base URL. </p>
<p>The code also integrates caching and invalidation through tags. In this slice, we've specified <code>tagTypes</code> as <code>'Tasks'</code>. In subsequent sections, we'll explore how invalidation and refetching can be done through tags.</p>
<p>In the slice, <code>endpoints</code> can be perceived as API endpoints. Functions defined within this <code>endpoints</code> function will be exported in the form of <code>use...Query</code> if it's a <code>builder.query</code> function (as in this case, <code>useFetchDataFromDbQuery</code>), and <code>use...Mutation</code> if it's a <code>builder.mutation</code> function (more on this later).</p>
<p>Now, we'll lay the foundation for incorporating the slices we generate into the Redux store. Since we will create multiple <code>slices</code> in the future, it's prudent to compile them into a dedicated file using <code>combineReducers</code>.</p>
<p>Next, create a <code>rootReducer.ts</code> file within the <code>src/redux</code> folder. Embed the following code snippet into this file to integrate the previously created <code>apiSlice</code>:</p>
<pre><code class="lang-tsx">  import { combineReducers } from "@reduxjs/toolkit";
   import { fireStoreApi } from "./services/apiSlice";

   export const rootReducer = combineReducers({
    [fireStoreApi.reducerPath]: fireStoreApi.reducer,
   });
</code></pre>
<p>In this snippet, we imported the earlier-created <code>apiSlice</code> and include it in the <code>combineReducers</code> function, specifying the key-value pair as <code>[fireStoreApi.reducerPath]: fireStoreApi.reducer</code>. </p>
<p>This configuration ensures that the state managed by the <code>apiSlice</code> is effectively integrated into the Redux store. </p>
<p>Finally, we'll add the <code>rootReducer</code> to the Redux store here. Navigate to the <code>src/redux/store.ts</code> and modify it like below:</p>
<pre><code class="lang-tsx">import { configureStore } from "@reduxjs/toolkit";
   import { setupListeners } from "@reduxjs/toolkit/dist/query";
   import { rootReducer } from "./rootReducer";
   import { fireStoreApi } from "./services/apiSlice";

   export const store = configureStore({
    reducer: rootReducer,
    middleware: (getDefaultMiddleware) =&gt; getDefaultMiddleware().concat(fireStoreApi.middleware),
   });
   setupListeners(store.dispatch)
   export type RootState = ReturnType&lt;typeof store.getState&gt;;
   export type AppDispatch = typeof store.dispatch;
</code></pre>
<p>Here, we integrate our <code>rootReducer</code> into the store and pass the <code>fireStoreApi.middleware</code> to the <code>middleware</code> prop of the <code>configureStore</code> function. This ensures that the Redux store uses the <code>middleware</code> for making requests to Firestore.</p>
<p>Now, we can safely start the process of fetching and populating our application with data, which will be the focus of the upcoming section.</p>
<h2 id="heading-how-to-fetch-and-populate-data">How to Fetch and Populate Data</h2>
<p>Our approach begins with populating data in the <code>Navbar</code> component, followed by the <code>Sidebar</code>, and finally, the <code>BoardTasks</code>.</p>
<h3 id="heading-how-to-populate-the-navbar">How to populate the navbar</h3>
<p>For the Navbar, we want to display the name of the current board. But since we'll need this information in other parts of the app, we'll also store it centrally in the Redux store.</p>
<p>To achieve this, we'll create a new slice called <code>appSlice</code>, which will manage the state related to the current board name. This slice will also be responsible for handling logic and state unrelated to API calls.</p>
<p>First, create a <code>features</code> folder within the <code>src/redux</code> directory.</p>
<p>Inside the features folder, create a file named <code>appSlice.ts</code> and paste the following code:</p>
<pre><code class="lang-tsx">   import { createSlice, PayloadAction } from "@reduxjs/toolkit";
   import { RootState } from "../store";

   // Define the initial state for the slice
   const initialState = {
    currentBoardName: "",
   };

   export const features = createSlice({
   // Name of the slice
   name: "features",
   initialState,
   // Functions that update the initialState are written inside the reducers object
   reducers: {
    // This function updates the board name when called
    setPageTitle: (state, action: PayloadAction&lt;string&gt;) =&gt; {
      state.currentBoardName = action.payload;
    },
   },
   });

   // Export the functions defined inside the reducers here
   export const { setPageTitle } = features.actions;

   // Selector function to retrieve the current board name from the state
   export const getPageTitle = (state: RootState) =&gt; state.features.currentBoardName;

   // Export the reducer for use in the Redux store
   export default features.reducer;
</code></pre>
<p>This code defines the <code>appSlice</code> slice, which includes the initial state, <code>reducers</code>, and <code>actions</code> for managing the current board name.</p>
<p>To make the <code>appSlice</code> available globally, we must integrate it into the Redux store. Open the <code>src/redux/rootReducer.ts</code> file and modify it as follows:</p>
<pre><code class="lang-tsx">   // src/redux/rootReducer.ts
   import { combineReducers } from "@reduxjs/toolkit";
   import { fireStoreApi } from "./services/apiSlice";
   import  featuresReducer  from "./features/appSlice";

   export const rootReducer = combineReducers({
   //add the features slice here
   features: featuresReducer,
   [fireStoreApi.reducerPath]: fireStoreApi.reducer,
   });
</code></pre>
<p>This updated <code>rootReducer</code> now includes the <code>featuresReducer</code>, making the <code>appSlice</code> available throughout the application.</p>
<p>Next, we need to update the <code>Navbar</code> component to fetch the current board name from the Redux store and display it. Open the <code>app/components/Navbar.tsx</code> file and make the following changes:</p>
<pre><code class="lang-tsx">  'use client' 

   import Dropdown from "./Dropdown";
   import { useState, useEffect } from 'react'
   // Import Redux functions and selectors for managing board names
   import { setCurrentBoardName, getCurrentBoardName } from '../../redux/features/appSlice'
   import { useAppDispatch, useAppSelector } from '@/components/redux/hooks'
   // Import the data-fetching hook from the API slice
   import { useFetchDataFromDbQuery } from "@/components/redux/services/apiSlice";

   export default function Navbar() {
    const [show, setShow] = useState&lt;boolean&gt;(false);
   // Destructuring assignment to extract data from the useFetchDataFromDbQuery hook
   const { data } = useFetchDataFromDbQuery();
   // Access the Redux dispatch function for calling actions
   const dispatch = useAppDispatch();

   // Effect hook to run when the data updates
   useEffect(() =&gt; {
    if (data) {
      // When a user signs in, set the currentBoardName to the first board's name
      const activeBoard = data[0].boards[0];
      dispatch(setCurrentBoardName(activeBoard.name));
    }
   }, [data]);

   // Select the current board name from the Redux store
   const currentBoardName = useAppSelector(getCurrentBoardName);

   return (
    &lt;nav className="bg-white border flex h-24"&gt;
      &lt;div className="flex-none w-[18.75rem] border-r-2 flex items-center pl-[2.12rem]"&gt;
        &lt;p className="font-bold text-3xl"&gt; Kanban App &lt;/p&gt;
      &lt;/div&gt;

      &lt;div className="flex justify-between w-full items-center pr-[2.12rem]"&gt;
        {/* populate the current board name in the navbar */}
        &lt;p className="text-black text-2xl font-bold pl-6"&gt;{currentBoardName}&lt;/p&gt;

        &lt;div className="flex items-center space-x-3"&gt;
          &lt;button className="bg-blue-500 text-black px-4 py-2 flex rounded-3xl items-center space-x-2"&gt;
            &lt;p&gt;+ Add New Task&lt;/p&gt;
          &lt;/button&gt;
          &lt;div className="relative flex items-center"&gt;
            &lt;button onClick={() =&gt; setShow(!show)} className="text-3xl mb-4"&gt;
              ...
            &lt;/button&gt;
            &lt;Dropdown show={show} /&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/nav&gt;
   );
   }
</code></pre>
<p>After these updates, your navbar should now display the name of the current board, which is "Roadmap":</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/11-1.png" alt="Display board name on navar" width="600" height="400" loading="lazy"></p>
<h3 id="heading-how-to-populate-the-sidebar">How to populate the sidebar</h3>
<p>Once populated with data, the sidebar will display the number of boards and the names of the available boards in the application. Clicking on different boards in the sidebar will switch the view to the selected board. </p>
<p>While we currently only have one board available in the data, we'll lay the groundwork for these features to support multiple boards in the future. </p>
<p>Navigate to the <code>Sidebar</code> component and make the following edits as seen below:</p>
<pre><code class="lang-tsx">import { useState } from "react";
import { useAppDispatch } from "@/components/redux/hooks";
import { useFetchDataFromDbQuery } from "@/components/redux/services/apiSlice";
import { setCurrentBoardName } from "@/components/redux/features/appSlice";

export default function Sidebar() {
  // State to keep track of the index of the active board during navigation
  const [active, setActive] = useState&lt;number&gt;(0);

  const { data } = useFetchDataFromDbQuery();
  const dispatch = useAppDispatch();

  // Function to handle navigation through boards
  const handleNav = (index: number, name: string) =&gt; {
    setActive(index);
    dispatch(setCurrentBoardName(name));
  };

  return (
    &lt;aside className="w-[18.75rem] flex-none dark:bg-dark-grey h-full py-6 pr-6"&gt;
      {data &amp;&amp; (
        &lt;&gt;
          {/* Display the number of boards available in the data */}
          &lt;p className="text-medium-grey pl-[2.12rem] text-[.95rem] font-semibold uppercase pb-3"&gt;
            {`All Boards (${data[0]?.boards.length})`}
          &lt;/p&gt;
          {/* Display the names of each board */}
          {data[0]?.boards.map(
            (board: { [key: string]: any }, index: number) =&gt; {
              const { name, id } = board;
              const isActive = index === active; // Check if the board is active
              return (
                &lt;div
                  key={id}
                  onClick={() =&gt; handleNav(index, name)} // Handle navigation through boards on click
                  className={`${
                    isActive ? 'rounded-tr-full rounded-br-full bg-blue-500 text-white' : 'text-black'
                  } cursor-pointer flex items-center 
                  space-x-2 pl-[2.12rem] py-3 pb-3`}
                &gt;
                  &lt;p className="text-lg capitalize"&gt;{name}&lt;/p&gt;
                &lt;/div&gt;
              );
            }
          )}
        &lt;/&gt;
      )}
      &lt;button className="flex items-center space-x-2 pl-[2.12rem] py-3"&gt;
        &lt;p className="text-base font-bold capitalize text-main-purple"&gt;
          + Create New Board
        &lt;/p&gt;
      &lt;/button&gt;
    &lt;/aside&gt;
  );
}
</code></pre>
<p>With the above code, we have prepared the sidebar for handling multiple boards in the future. When multiple boards are available in the data, the sidebar will dynamically display them, allowing users to switch between them seamlessly.</p>
<p>Up to this point, your sidebar UI should reflect these updates:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/12-1.png" alt="Populated sidebar" width="600" height="400" loading="lazy"></p>
<p>Moving forward, in the next section we'll populate the <code>BoardTasks</code> component.</p>
<h3 id="heading-how-to-populate-the-boardtasks-component">How to populate the <code>BoardTasks</code> component</h3>
<p>In this section, the goal is to present a maximum of seven task columns on the screen. If there are fewer than seven columns, we'll display an option to add more. Also, we'll want to have an indication of an empty column for columns without tasks.</p>
<p>Each task card should feature edit and delete icons. These will serve as placeholders for forthcoming modal functionalities.</p>
<p>To implement these changes, go to the <code>BoardTasks</code> component and make the following updates:</p>
<pre><code class="lang-tsx">import { useEffect, useState } from "react";
import { useFetchDataFromDbQuery } from "@/components/redux/services/apiSlice";
import { useAppSelector } from "@/components/redux/hooks";
import { getCurrentBoardName } from "@/components/redux/features/appSlice";
import { MdEdit, MdDelete } from "react-icons/md";

// Define types for the tasks data
interface ITask {
  title: string;
  description: string;
  status: string;
}

// Define types for the data in each column
interface Column {
  name: string;
  tasks?: ITask[];
}

export default function BoardTasks() {
  // Get loading state and data from the useFetchDataFromDbQuery endpoint
  const { isLoading, data } = useFetchDataFromDbQuery();
  // Manage column data in columns state
  const [columns, setColumns] = useState&lt;Column[]&gt;([]);
  // Get active board name from the redux store
  const activeBoard = useAppSelector(getCurrentBoardName);

  // Once data fetches successfully, this function in the useEffect runs
  useEffect(() =&gt; {
    if (data !== undefined) {
      const [boards] = data;
      if (boards) {
        // Get the data of the active board
        const activeBoardData = boards.boards.find(
          (board: { name: string }) =&gt; board.name === activeBoard
        );
        if (activeBoardData) {
          const { columns } = activeBoardData;
          setColumns(columns);
        }
      }
    }
  }, [data, activeBoard]);

  return (
    &lt;div className="overflow-x-auto overflow-y-auto w-full p-6 bg-stone-200"&gt;
      {/* If data has not been fetched successfully, display a loading state, else display the column of tasks */}
      {isLoading ? (
        &lt;p className="text-3xl w-full text-center font-bold"&gt;Loading tasks...&lt;/p&gt;
      ) : (
        &lt;&gt;
          {/* If columns of tasks isn't empty: display the tasks, else display the prompt to add a new column */}
          {columns.length &gt; 0 ? (
            &lt;div className="flex space-x-6"&gt;
              {columns.map((column) =&gt; {
                const { id, name, tasks } = column;
                return (
                  &lt;div key={id} className="w-[17.5rem] shrink-0"&gt;
                    &lt;p className="text-black"&gt;{`${name} (${
                      tasks ? tasks?.length : 0
                    })`}&lt;/p&gt;

                    {tasks &amp;&amp;
                      // Display the tasks if there are tasks in the column, if not, display an empty column
                      (tasks.length &gt; 0 ? (
                        tasks.map((task) =&gt; {
                          const { id, title, status } = task;

                          return (
                            &lt;div
                              key={id}
                              className="bg-white p-6 rounded-md mt-6 flex items-center justify-between border"
                            &gt;
                              &lt;p&gt;{title}&lt;/p&gt;
                              &lt;div className="flex items-center space-x-1"&gt;
                                &lt;MdEdit className="text-lg cursor-pointer" /&gt;
                                &lt;MdDelete className="text-lg cursor-pointer text-red-500" /&gt;
                              &lt;/div&gt;
                            &lt;/div&gt;
                          );
                        })
                      ) : (
                        &lt;div className="mt-6 h-full rounded-md border-dashed border-4 border-white" /&gt;
                      ))}
                  &lt;/div&gt;
                );
              })}
              {/* If the number of columns of tasks is less than 7, display an option to add more columns */}
              {columns.length &lt; 7 ? (
                &lt;div className="rounded-md bg-white w-[17.5rem] mt-12 shrink-0 flex justify-center items-center"&gt;
                  &lt;p className="cursor-pointer font-bold text-black text-2xl"&gt;
                    + New Column
                  &lt;/p&gt;
                &lt;/div&gt;
              ) : (
                ""
              )}
            &lt;/div&gt;
          ) : (
            &lt;div className="w-full h-full flex justify-center items-center"&gt;
              &lt;div className="flex flex-col items-center"&gt;
                &lt;p className="text-black text-sm"&gt;
                  This board is empty. Create a new column to get started.
                &lt;/p&gt;
                &lt;button className="bg-blue-500 text-black px-4 py-2 flex mt-6 rounded-3xl items-center space-x-2"&gt;
                  &lt;p&gt;+ Add New Column&lt;/p&gt;
                &lt;/button&gt;
              &lt;/div&gt;
            &lt;/div&gt;
          )}
        &lt;/&gt;
      )}
    &lt;/div&gt;
  );
}
</code></pre>
<p>After you make these edits, your UI should now reflect the changes as demonstrated in the GIF below:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/13.gif" alt="Populated boardTasks component" width="600" height="400" loading="lazy"></p>
<p>Next, we'll turn our attention to implementing CRUD (Create, Read, Update, and Delete) operations throughout our application.</p>
<h2 id="heading-how-to-implement-crud-operations">How to Implement CRUD Operations</h2>
<p>Before we dive into implementing CRUD functionalities throughout our app, we need to establish the <code>updateBoardToDb</code> mutation endpoint within the <code>apiSlice</code>. This endpoint will allow us to make necessary updates to our database for CRUD actions.</p>
<p>Integrate the following code into your <code>redux/services/apiSlice.ts</code> file to include the mutation endpoint:</p>
<pre><code class="lang-tsx">import { createApi, fakeBaseQuery } from "@reduxjs/toolkit/query/react";
import { getSession } from "next-auth/react";
// additionally import the doc and updateDoc method from firestore to get user document reference and update the document, respectively
import { collection, doc, getDocs, updateDoc } from "firebase/firestore";
import { db } from "@/components/app/utils/firebaseConfig";

export const fireStoreApi = createApi({
  reducerPath: "firestoreApi",
  baseQuery: fakeBaseQuery(),
  tagTypes: ["Tasks"],
  endpoints: (builder) =&gt; ({
    fetchDataFromDb: builder.query&lt;{ [key: string]: any }[], void&gt;({
      async queryFn() {
        try {
          const session = await getSession();
          if (session?.user) {
            const { user } = session;
            const ref = collection(db, `users/${user.email}/tasks`);
            const querySnapshot = await getDocs(ref);
            return { data: querySnapshot.docs.map((doc) =&gt; doc.data()) };
          }
        } catch (e) {
          return { error: e };
        }
      },
      providesTags: ["Tasks"],
    }),
    // endpoint for CRUD actions
    updateBoardToDb: builder.mutation({
      async queryFn(boardData) {
        try {
          const session = await getSession();
          if (session?.user) {
            const { user } = session;
            const ref = collection(db, `users/${user.email}/tasks`);
            const querySnapshot = await getDocs(ref);
            const boardId = querySnapshot.docs.map((doc) =&gt; {
              return doc.id;
            });
            await updateDoc(doc(db, `users/${user.email}/tasks/${boardId}`), {
              boards: boardData,
            });
          }
          return { data: null };
        } catch (e) {
          return { error: e };
        }
      },
      invalidatesTags: ["Tasks"], // this will be used to invalidate the initially fetched data. 
      // Data will have to be refetched once this enpoint has been called
    }),
  }),
});

// Export hooks for using the created endpoint
export const { useFetchDataFromDbQuery, useUpdateBoardToDbMutation } =
  fireStoreApi;
</code></pre>
<p>Upon calling the <code>useUpdateBoardToDbMutation</code> endpoint, our database data will be updated accordingly. </p>
<p>Following each update, Redux seamlessly performs background refreshes to ensure we're operating with the latest data. This functionality is enabled by the <code>invalidatesTags</code> property we passed to the <code>updateBoardToDb</code> endpoint.</p>
<p>Having successfully implemented the CRUD endpoint, our next step is to implement the features for adding and editing boards.</p>
<h3 id="heading-how-to-add-and-edit-a-board">How to add and edit a board</h3>
<p>Once we've completed the UI implementation, the modal for adding a new board should resemble the following:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/14.png" alt="add board modal" width="600" height="400" loading="lazy"></p>
<p>Similarly, for editing a board:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/15.png" alt="edit board modal" width="600" height="400" loading="lazy"></p>
<p>If you look at the images above, you can see that both modals share a striking resemblance, differing only in their titles. </p>
<p>This presents an excellent opportunity to implement the DRY (Don't Repeat Yourself) concept in programming. In a few steps, we'll explore how to leverage a single modal to fulfill both purposes.</p>
<p>First, we'll use the <a target="_blank" href="https://www.npmjs.com/package/react-modal"><code>react-modal</code></a>  library to create a custom modal component. This allows us to avoid building from scratch. </p>
<p>To begin, install the <code>react-modal</code> library by running the following command:</p>
<pre><code class="lang-npm"> npm i react-modal
</code></pre>
<p>Then create a <code>Modal.tsx</code> file in the <code>app/components</code> directory and add the provided code. This code defines a custom modal component with styling.</p>
<pre><code class="lang-tsx">import ReactModal from "react-modal";

interface ModalProps {
  children?: React.ReactNode;
  isOpen: boolean;
  onRequestClose: () =&gt; void;
}

ReactModal.setAppElement("*");

export function Modal({ children, isOpen, onRequestClose }: ModalProps) {
  const modalStyle = {
    overlay: {
      zIndex: "900000",
      backgroundColor: "rgba(0,0,0,0.45)",
      display: "flex",
      justifyContent: "center",
      alignItems: "center",
    },
    content: {
      top: "50%",
      left: "50%",
      right: "auto",
      bottom: "auto",
      marginRight: "-50%",
      transform: "translate(-50%, -50%)",
      padding: "0px",
      borderRadius: ".5rem",
      width: "auto",
      backgroundColor:  "#fff",
      border: "none",
    },
  };

  return (
    &lt;ReactModal
      onRequestClose={onRequestClose}
      isOpen={isOpen}
      style={modalStyle}
    &gt;
      {children}
    &lt;/ReactModal&gt;
  );
}

interface ModalBody {
  children: React.ReactNode;
}

export function ModalBody({ children }: ModalBody) {
  return &lt;form className="w-[21.4rem] md:w-[30rem] p-8"&gt;{children}&lt;/form&gt;;
}
</code></pre>
<p>In this code, we have implemented and styled the overlay and body (content) of the modal.</p>
<p>Now, create a folder named <code>AddAndEditBoardModal.tsx</code> and paste the provided code into it as a placeholder. Don't worry about the red squiggly lines you get in your code editor for now – we'll address them in a bit.</p>
<pre><code class="lang-tsx">   import { Modal, ModalBody } from "./Modal";

   export default function AddAndEditBoardModal() {

    return (
      &lt;Modal isOpen onRequestClose&gt;
        &lt;ModalBody&gt;
         &lt;p&gt;Add and Edit Board Modal&lt;/p&gt;
        &lt;/ModalBody&gt;
      &lt;/Modal&gt;
    );
   }
</code></pre>
<p>In this code, we imported our custom modal component, and we've wrapped it around a placeholder text.</p>
<p>Next, render the newly created modal component in the <code>app/page.tsx</code> component:</p>
<pre><code class="lang-tsx">   // rest of imports here
   import AddAndEditBoardModal from "./components/AddAndEditBoardModal";
   // rest of the code here
   export default function Home() {
   return (
    &lt;main className="flex h-full"&gt;
      &lt;Sidebar /&gt;
      &lt;BoardTasks /&gt;
      {/* render modal component here */}
      &lt;AddAndEditBoardModal /&gt;
    &lt;/main&gt;
   );
   }
</code></pre>
<p>In this step, we've created a placeholder for the <code>AddAndEditBoardModal</code> component and rendered it in the <code>Page.tsx</code> component. </p>
<p>Next, we'll implement the functions to trigger the modal and manage the open and close state in the redux store to maintain clean code and avoid prop drilling.</p>
<p>Navigate to your <code>redux/features/appSlice.ts</code> file and update it with the code below:</p>
<pre><code class="lang-tsx">   import { createSlice, PayloadAction } from "@reduxjs/toolkit";
   import { RootState } from "../store";

   const initialState = {
   currentBoardName: "",
   // Manage the state for opening and closing the Add and Edit Board modal
   isAddAndEditBoardModal: { isOpen: false, variant: "" },
   };

   export const features = createSlice({
    name: "features",
    initialState,

    reducers: {
     setCurrentBoardName: (state, action: PayloadAction&lt;string&gt;) =&gt; {
      state.currentBoardName = action.payload;
    },
    // Open the Add and Edit Board modal with a specified variant (add or edit)
    openAddAndEditBoardModal: (state, { payload }) =&gt; {
      state.isAddAndEditBoardModal.isOpen = true;
      // Set the kind of modal to open (add board or edit board) based on the variant parameter
      state.isAddAndEditBoardModal.variant = payload;
    },
    // Close the Add and Edit Board modal
    closeAddAndEditBoardModal: (state) =&gt; {
      state.isAddAndEditBoardModal.isOpen = false;
      state.isAddAndEditBoardModal.variant = "";
    },
   },
   });
   export const {
   setCurrentBoardName,
   openAddAndEditBoardModal,
   closeAddAndEditBoardModal,
   } = features.actions;
   export const getCurrentBoardName = (state: RootState) =&gt; state.features.currentBoardName;
   // Selector functions to retrieve isOpen value of state from the isAddAndRditBoardModal state
   export const getAddAndEditBoardModalValue = (state: RootState) =&gt; state.features.isAddAndEditBoardModal.isOpen;
   // Selector functions to retrieve isOpen value of state from the isAddAndRditBoardModal state
   export const getAddAndEditBoardModalVariantValue = (state: RootState) =&gt; state.features.isAddAndEditBoardModal.variant;
   // Export the reducer for use in the Redux store
   export default features.reducer;
</code></pre>
<p>Then, navigate back to the <code>AddAndEditBoardModal.tsx</code> component and update it as seen below:</p>
<pre><code class="lang-tsx">   import { Modal, ModalBody } from "./Modal";
   import { useAppSelector, useAppDispatch } from "@/components/redux/hooks";
   //import needed functions from the appSlice
   import {
   getAddAndEditBoardModalValue,
   getAddAndEditBoardModalVariantValue,
   closeAddAndEditBoardModal,
   } from "@/components/redux/features/appSlice";

   export default function AddAndEditBoardModal() {
   // get the variant of the modal
   const modalVariant = useAppSelector(getAddAndEditBoardModalVariantValue);
   const dispatch = useAppDispatch();
   // opens that modal is isOpen evaluates to true
   const isOpen = useAppSelector(getAddAndEditBoardModalValue);
   // close the modal
   const closeModal = () =&gt; dispatch(closeAddAndEditBoardModal());

   return (
    &lt;Modal isOpen={isOpen} onRequestClose={closeModal}&gt;
      &lt;ModalBody&gt;
        {/* display the variant(title) of the modal */}
        &lt;p&gt;{modalVariant}&lt;/p&gt;
      &lt;/ModalBody&gt;
    &lt;/Modal&gt;
   );
   }
</code></pre>
<p>Following these updates, we can safely implement the trigger for the add and edit board modal.</p>
<p>Next, navigate to the <code>Sidebar</code> component and update the button with the "+ Create new board" text so it opens the "Add Board" modal when clicked: </p>
<pre><code class="lang-tsx">   // add this to the imports
   import { openAddAndEditBoardModal } from "@/components/redux/features/appSlice";

   export default function Sidebar() {
    // rest of code here
   return (
     &lt;aside className="w-[18.75rem] flex-none dark:bg-dark-grey h-full py-6 pr-6"&gt;
       {/* rest of code here */}
       {/* trigger the create new board modal */}
       &lt;button
         onClick={() =&gt; dispatch(openAddAndEditBoardModal("Add New Board"))}
         className="flex items-center space-x-2 pl-[2.12rem] py-3"
       &gt;
         &lt;p className="text-base font-bold capitalize text-main-purple"&gt;
           + Create New Board
         &lt;/p&gt;
       &lt;/button&gt;
     &lt;/aside&gt;
   );
   }
</code></pre>
<p>Now, upon clicking the "+ Create new board" button in the sidebar, the modal containing the "Add new board" text should appear. You should also be able to close it by clicking on the overlay:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/16.gif" alt="Add new board modal pops up" width="600" height="400" loading="lazy"></p>
<p>Next, we'll implement the trigger for the edit board modal.</p>
<p>Navigate to the <code>app/components/Dropdown.tsx</code> component and update the "Edit board" button as follows:</p>
<pre><code class="lang-tsx">   import { useAppDispatch } from '@/components/redux/hooks'
   import { openAddAndEditBoardModal } from '@/components/redux/features/appSlice';

   interface IDropdown {
    show: boolean
   }

   export default function Dropdown({ show }: IDropdown) {

    const dispatch = useAppDispatch()

    return (
      &lt;div
        className={`${
          show ? "block" : "hidden"
        } w-48 absolute top-full bg-white
         border shadow-lg right-0 py-2 rounded-2xl`}
      &gt;
        &lt;div className="hover:bg-gray-300"&gt;
        {/* trigger Edit Board modal here */}
          &lt;button
           onClick={() =&gt; dispatch(openAddAndEditBoardModal('Edit Board'))}
           className="text-sm px-4 py-2"&gt;Edit Board&lt;/button&gt;
        &lt;/div&gt;
        &lt;div className="hover:bg-gray-300"&gt;
          &lt;button className="text-sm px-4 py-2"&gt;
            Delete Board
          &lt;/button&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    );
   }
</code></pre>
<p>After making this update, clicking on the "Edit board" button in the dropdown will open the edit board modal, as illustrated in the GIF below:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/17.gif" alt="Edit board modal pops up" width="600" height="400" loading="lazy"></p>
<p>The option to add a new column to the <code>BoardTasks</code> component should also open this modal when clicked. So navigate to the <code>BoardTasks</code> component and import the <code>openAddEditBoardModal</code> function and <code>useAppDispatch</code> hook from <code>appSlice</code> and redux hooks, respectively. </p>
<p>Then declare the dispatch function in the component with this statement: <code>const dispatch = useAppDispatch()</code></p>
<p>Finally, update the "+New Column" <code>div</code> element to open the "Edit board" modal when clicked:</p>
<pre><code class="lang-tsx">   // rest of the code 
    &lt;div
    onClick={() =&gt; dispatch(openAddAndEditBoardModal("Edit Board"))
    className="rounded-md bg-white w-[17.5rem] mt-12 shrink-0 flex justify-center items-center"&gt;
   &lt;p className="cursor-pointer font-bold text-black text-2xl"&gt;  + New Column &lt;/p&gt;
   &lt;/div&gt;
   //rest of the code
</code></pre>
<p>After these updates, the "Edit board" modal should open up when the "+New Column" card is clicked:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/18.gif" alt="Edit board modal pops up" width="600" height="400" loading="lazy"></p>
<p>In the upcoming steps, we'll construct the complete markup and functionalities for our modal.</p>
<p>Referring to the images of both modals presented at the start of this section, in the "Add New Board" modal, the fields for board and column names should be blank. In contrast, the "Edit Board" modal should display the existing name and columns of the board and should be editable.</p>
<p>The "+ Add New Column" button in both modals allows the addition of more fields to the board's columns, and subsequently, the updated data is sent to the database.  </p>
<p>Keep in mind that, given the frontend-centric nature of this project, a significant portion of the business logic will be handled on the front-end. However, don’t worry; we will take this snippet by snippet until we completely implement all features.</p>
<p>To begin, update the <code>AddAndEditBoardModal</code> component by pasting the code below:</p>
<pre><code class="lang-tsx">import { useState, useEffect } from "react";
import { Modal, ModalBody } from "./Modal";
import { useAppSelector, useAppDispatch } from "@/components/redux/hooks";
//import needed functions from the appSlice
import {
  getAddAndEditBoardModalValue,
  getAddAndEditBoardModalVariantValue,
  closeAddAndEditBoardModal,
  getCurrentBoardName,
} from "@/components/redux/features/appSlice";
import {
  useFetchDataFromDbQuery,
  useUpdateBoardToDbMutation,
} from "@/components/redux/services/apiSlice";
import { FaTimes } from "react-icons/fa";
import { id } from '../utils/data'
// define types for boarddata
interface IBoardData {
  id: string,
  name: string;
  columns: {
    id: string;
    name: string;
    columns?: { name: string; tasks?: { [key: string]: any }[] };
  }[];
}
// dummy add board data for the "Add board" modal
let addBoardData = {
  id: id(),
  name: "",
  columns: [
    {
      id: id(),
      name: "",
      tasks:
 [],
    },
  ],};

export default function AddAndEditBoardModal() {
// rest of the code
}
</code></pre>
<p>Here, we have made the necessary imports and defined a type for board data - which we will use when populating the modal. We also implemented dummy data for the add board modal. We will see how this will be of use in a bit. </p>
<p>Next, go to the <code>AddAndEditBoardModal</code> function and paste the following code into it to declare variables and state values. The comments explain the future use of each of the declarations.</p>
<pre><code class="lang-tsx"> //manage the board data state
  const [boardData, setBoardData] = useState&lt;IBoardData&gt;();
  // check if the board name field is empty
  const [isBoardNameEmpty, setIsBoardNameEmpty] = useState&lt;boolean&gt;(false);
  // will be used to check if any of the board column field is empty
  const [emptyColumnIndex, setEmptyColumnIndex] = useState&lt;number&gt;();

  // get the variant of the modal
  const modalVariant = useAppSelector(getAddAndEditBoardModalVariantValue);
  // check the type of the open modal, whether Add new board, or Edit board
  const isVariantAdd = modalVariant === "Add New Board";
  const dispatch = useAppDispatch();
  // opens that modal if isOpen evaluates to true
  const isOpen = useAppSelector(getAddAndEditBoardModalValue);
  const currentBoardTitle = useAppSelector(getCurrentBoardName);
  // close the modal
  const closeModal = () =&gt; dispatch(closeAddAndEditBoardModal());
  // Fetch data from the database to populate the edit board modal
  let { data } = useFetchDataFromDbQuery();
  // Mutation hook for updating the board in the database
  const [updateBoardToDb, { isLoading }] = useUpdateBoardToDbMutation();
</code></pre>
<p>Here, we’ll implement the functions that will be responsible for the modal’s functionality. Paste the following code just below the declarations above:</p>
<pre><code class="lang-tsx">  // Effect to set initial data for the modal based on the variant
  useEffect(() =&gt; {
    if (data) {

      if (isVariantAdd) {
        setBoardData(addBoardData);
      } else {
        const activeBoard = data[0].boards.find(
          (board: { name: string }) =&gt; board.name === currentBoardTitle
        );
        setBoardData(activeBoard);
      }
    }
  }, [data, modalVariant]);

  // Effect to clear error messages after a certain time
  useEffect(() =&gt; {
    const timeoutId = setTimeout(() =&gt; {
      setIsBoardNameEmpty(false);
      setEmptyColumnIndex(undefined);
    }, 3000);
    return () =&gt; clearTimeout(timeoutId);
  }, [emptyColumnIndex, isBoardNameEmpty]);

  // Handler for board name change
  const handleBoardNameChange = (e: React.ChangeEvent&lt;HTMLInputElement&gt;) =&gt; {
    if (boardData) {
      const newName = { ...boardData, name: e.target.value };
      setBoardData(newName);
    }
  };

  // Handler for column name change. These kind of functions are called closures

  const handleColumnNameChange = (index: number) =&gt; {
    return function (e: React.ChangeEvent&lt;HTMLInputElement&gt;) {
      // handle change for create new board modal
      if (boardData) {
        const modifyColumns = boardData.columns.map((column, columnIndex) =&gt; {
          if (columnIndex === index) {
            return { ...column, name: e.target.value };
          }
          return column;
        });
        const modifiedColumn = { ...boardData, columns: modifyColumns };
        setBoardData(modifiedColumn);
      }
    };
  };

  // Handler for adding a new column to the form
  const handleAddNewColumn = () =&gt; {
    // max columns we want to have in a board is 7
    if (boardData &amp;&amp; boardData.columns.length &lt; 6) {
      // Make a copy of the existing boardData
      const updatedBoardData = { ...boardData };
      // Create a new column object
      const newColumn = { id: id(), name: "", tasks: [] };
      // Push the new column to the columns array in the copy
      updatedBoardData.columns = [...updatedBoardData.columns, newColumn];
      // Update the state with the modified copy
      setBoardData(updatedBoardData);
    }
  };

  // Handler for deleting a column in the form
  const handleDeleteColumn = (index: number) =&gt; {
    if (boardData) {
      const filteredColumns = boardData.columns.filter(
        (_column, columnIndex) =&gt; columnIndex !== index
      );
      setBoardData({ ...boardData, columns: filteredColumns });
    }
  };

  // Handler for adding a new board to the database
  const handleAddNewBoardToDb = (e: React.FormEvent&lt;HTMLButtonElement&gt;) =&gt; {
    e.preventDefault();

    // check if any of the column names are empty before submiting
    const emptyColumnStringChecker = boardData?.columns.some(
      (column) =&gt; column.name === ""
    ); 

    //condition to run if the board name is empty
    if (boardData?.name === "") {
      setIsBoardNameEmpty(true);
    }

    //if any of the column names is empty, update the emptyColumnIndex with its index
    if (emptyColumnStringChecker) {
      const emptyColumn = boardData?.columns.findIndex(
        (column) =&gt; column.name == ""
      );
      setEmptyColumnIndex(emptyColumn);
    }

    if (boardData?.name !== "" &amp;&amp; !emptyColumnStringChecker) {
      //submit to the database after verifying that the board name and none of the column names aren't empty
      if (data) {
        let [boards] = data;
        const addBoard = [...boards.boards, boardData];
        boards = addBoard;
        updateBoardToDb(boards);
      }
    }
  };

  // Handler for editing a board in the database
  const handleEditBoardToDb = (e: React.FormEvent&lt;HTMLButtonElement&gt;) =&gt; {
    e.preventDefault();
    const emptyColumnStringChecker = boardData?.columns.some(
      (column) =&gt; column.name === ""
    );
    //condition to run if the board name is empty
    if (boardData?.name === "") {
      setIsBoardNameEmpty(true);
    }
    //if any of the column names is empty, update the emptyColumnIndex with its index
    if (emptyColumnStringChecker) {
      const emptyColumn = boardData?.columns.findIndex(
        (column) =&gt; column.name == ""
      );
      setEmptyColumnIndex(emptyColumn);
    }
    //submit to the database after verifying that the board name and none of the column names aren't empty
    if (boardData?.name !== "" &amp;&amp; !emptyColumnStringChecker) {
      if (data) {
        const [boards] = data;
        const boardsCopy = [...boards.boards]; 
        const activeBoardIndex = boardsCopy.findIndex(
          (board: { name: string }) =&gt; board.name === currentBoardTitle
        );
        const updatedBoard = {
          ...boards.boards[activeBoardIndex],
          name: boardData!.name,
          columns: boardData!.columns,
        } ;
        boardsCopy[activeBoardIndex] = updatedBoard;
        updateBoardToDb(boardsCopy);
      }
    }
  };
</code></pre>
<p>Finally, update the return statement of the component by pasting the below code snippet into it:</p>
<pre><code class="lang-tsx">return (
    &lt;Modal isOpen={isOpen} onRequestClose={closeModal}&gt;
      &lt;ModalBody&gt;
        {boardData &amp;&amp; (
          &lt;&gt;
            {/* display the variant(title) of the modal */}
            &lt;p className="text-lg font-bold"&gt;{modalVariant}&lt;/p&gt;
            &lt;div className="py-6"&gt;
              &lt;div&gt;
                &lt;label htmlFor="boardName" className="text-sm"&gt;
                  Board Name
                &lt;/label&gt;
                &lt;div className="pt-2"&gt;
                  &lt;input
                    id="boardName"
                    className={`${
                      isBoardNameEmpty ? "border-red-500" : "border-stone-200"
                    } border w-full p-2 rounded text-sm cursor-pointer focus:outline-none`}
                    placeholder="Name"
                    value={boardData.name}
                    onChange={handleBoardNameChange}
                  /&gt;
                &lt;/div&gt;
                {/* display this error if the board name is empty */}
                {isBoardNameEmpty ? (
                  &lt;p className="text-xs text-red-500"&gt;
                    Board name cannot be empty
                  &lt;/p&gt;
                ) : (
                  ""
                )}
              &lt;/div&gt;

              &lt;div className="mt-6"&gt;
                &lt;label htmlFor="" className="text-sm"&gt;
                  Board Column
                &lt;/label&gt;
                {boardData &amp;&amp;
                  boardData.columns.map(
                    (column: { name: string, id: string }, index: number) =&gt; {
                      let { name, id } = column;
                      return (
                        &lt;div key={id} className="pt-2"&gt;
                          &lt;div className="flex items-center space-x-2"&gt;
                            &lt;input
                              className={`${
                                emptyColumnIndex === index
                                  ? "border-red-500"
                                  : "border-stone-200"
                              } border border-stone-200 focus:outline-none text-sm cursor-pointer w-full p-2 rounded`}
                              placeholder="e.g Doing"
                              onChange={(e) =&gt; handleColumnNameChange(index)(e)}
                              value={name!}
                            /&gt;
                            &lt;div&gt;
                              &lt;FaTimes
                                onClick={() =&gt; handleDeleteColumn(index)}
                              /&gt;
                            &lt;/div&gt;
                          &lt;/div&gt;
                          {/* display this error if the board name is empty */}
                          {emptyColumnIndex === index ? (
                            &lt;p className="text-xs text-red-500"&gt;
                              Column name cannot be empty
                            &lt;/p&gt;
                          ) : (
                            ""
                          )}
                        &lt;/div&gt;
                      );
                    }
                  )}
                &lt;div className="mt-3"&gt;
                  &lt;button
                    type="button"
                    onClick={handleAddNewColumn}
                    className="bg-stone-200 rounded-3xl py-2 w-full text-sm font-bold"
                  &gt;
                    &lt;p&gt;+ Add New Column&lt;/p&gt;
                  &lt;/button&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div className="pt-6"&gt;
                &lt;button
                  type="submit"
                  onClick={(e: React.FormEvent&lt;HTMLButtonElement&gt;) =&gt; {
                    // function to run depending on the variant of the modals
                    isVariantAdd
                      ? handleAddNewBoardToDb(e)
                      : handleEditBoardToDb(e);
                  }}
                  className="bg-blue-500 rounded-3xl py-2 w-full text-sm font-bold"
                &gt;
                  {/* text to display depending on the variant of the modal */}
                  &lt;p&gt;
                    {isLoading
                      ? "Loading"
                      : `${isVariantAdd ? "Create New Board" : "Save Changes"}`}
                  &lt;/p&gt;
                &lt;/button&gt;
              &lt;/div&gt;
            &lt;/div&gt;
          &lt;/&gt;
        )}
      &lt;/ModalBody&gt;
    &lt;/Modal&gt;
  );
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/19.gif" alt="Add board to the database" width="600" height="400" loading="lazy"></p>
<p>In the above GIF, we introduced a "Marketing" board with "Todo" and "Doing" columns to our app. You can also see the real-time update of the boards in the sidebar.</p>
<p>Likewise, you can perform edits on a board:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/20.gif" alt="Edit board" width="600" height="400" loading="lazy"></p>
<p>Here, a new column, "After," was added to the "Roadmap" board.</p>
<p>In the upcoming section, we will implement the "Add new task" and "Edit task" functionalities.</p>
<h3 id="heading-how-to-add-and-edit-tasks">How to add and edit tasks</h3>
<p>Once you've completed this section, the "Add New Task" modal should resemble the following:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/21.png" alt="Add new task complete modal" width="600" height="400" loading="lazy"></p>
<p>Similarly, for the "Edit Task" modal:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/22.png" alt="Edit task complete modal" width="600" height="400" loading="lazy"></p>
<p>You'll see that these modals share similarities, so we will implement them using the same approach employed in the previous section.</p>
<p>We'll start by updating the <code>initialState</code> object in our <code>appSlice</code> to manage the state of the "Add and Edit tasks" modal. </p>
<pre><code class="lang-ts">   <span class="hljs-keyword">const</span> initialState = {
   <span class="hljs-comment">//add and edit tasks modal state</span>
   isAddAndEditTaskModal: { isOpen: <span class="hljs-literal">false</span>, variant: <span class="hljs-string">""</span>, title: <span class="hljs-string">""</span>, index: <span class="hljs-number">-1</span>, name: <span class="hljs-string">""</span>},
   };
</code></pre>
<p>The keys <code>title</code> and <code>index</code> will respectively store the title and index of the task being edited, while the <code>name</code> key will retrieve the name of the task's column. We'll explore how to utilize this information to edit a task in the upcoming steps.</p>
<p>Next, include the following functions in the <code>reducers</code> object. These will be the functions that will be called to open and close the modal:</p>
<pre><code class="lang-ts">    <span class="hljs-comment">// Open the Add and Edit task modal with a specified variant (add or edit), title, description, status</span>
    openAddAndEditTaskModal: <span class="hljs-function">(<span class="hljs-params">state, { payload }</span>) =&gt;</span> {
      state.isAddAndEditTaskModal.isOpen = <span class="hljs-literal">true</span>;
      state.isAddAndEditTaskModal.variant = payload.variant;
      state.isAddAndEditTaskModal.title = payload.title;
      state.isAddAndEditTaskModal.index = payload.index;
     state.isAddAndEditTaskModal.name = payload.name;
    },
    <span class="hljs-comment">// Close the Add and Edit task modal</span>
    closeAddAndEditTaskModal: <span class="hljs-function">(<span class="hljs-params">state</span>) =&gt;</span> {
      state.isAddAndEditTaskModal.isOpen = <span class="hljs-literal">false</span>;
      state.isAddAndEditTaskModal.variant = <span class="hljs-string">""</span>;
      state.isAddAndEditTaskModal.title = <span class="hljs-string">""</span>;
      state.isAddAndEditTaskModal.index = <span class="hljs-string">""</span>;
      state.isAddAndEditTaskModal.name = <span class="hljs-string">""</span>;
    },
</code></pre>
<p>Lastly, include the newly implemented functions and the selector functions in the exports:</p>
<pre><code class="lang-ts">   <span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> {
   openAddAndEditTaskModal,
   closeAddAndEditTaskModal,
   <span class="hljs-comment">//rest of the imports</span>
   } = features.actions;

   <span class="hljs-comment">// Selector function to retrieve isOpen state value  </span>
   <span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getAddAndEditTaskModalValue = <span class="hljs-function">(<span class="hljs-params">state: RootState</span>) =&gt;</span> state.features.isAddAndEditTaskModal.isOpen;
   <span class="hljs-comment">// Selector function to retrieve variant state value </span>
   <span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getAddAndEditTaskModalVariantValue = <span class="hljs-function">(<span class="hljs-params">state: RootState</span>) =&gt;</span> state.features.isAddAndEditTaskModal.variant;
   <span class="hljs-comment">// Selector function to retrieve title state value</span>
   <span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getAddAndEditTaskModalTitleValue = <span class="hljs-function">(<span class="hljs-params">state: RootState</span>) =&gt;</span> state.features.isAddAndEditTaskModal.title;
   <span class="hljs-comment">// Selector function to retrieve index state value</span>
   <span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getAddAndEditTaskModalIndexValue = <span class="hljs-function">(<span class="hljs-params">state: RootState</span>) =&gt;</span> state.features.isAddAndEditTaskModal.index;
   <span class="hljs-comment">// Selector function to retrieve name state value</span>
   <span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getAddAndEditTaskModalNameValue = <span class="hljs-function">(<span class="hljs-params">state: RootState</span>) =&gt;</span> state.features.isAddAndEditTaskModal.name;
   <span class="hljs-comment">//rest of the imports</span>
</code></pre>
<p>Now, we'll implement the <code>onClick</code> functions that enable users to interact with the modal and perform task-related actions. These functions will allow users to open the "Add new task" modal from the navbar and the "Edit task" modal by clicking the edit icon within individual task cards.</p>
<p>In the <code>components/Navbar</code>, include the <code>openAddAndEditTaskModal</code> among the imported functions from the <code>appSlice</code>:</p>
<pre><code class="lang-ts"> <span class="hljs-keyword">import</span> { setCurrentBoardName, getCurrentBoardName, openAddAndEditTaskModal } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../redux/features/appSlice'</span>
</code></pre>
<p>Then, modify the "+Add new task" button to incorporate an <code>onClick</code> function that triggers the "Add new task" modal:</p>
<pre><code class="lang-tsx">    &lt;button 
     type='button'
     onClick={() =&gt; dispatch(openAddAndEditTaskModal({variant: 'Add New Task'}))}
     className="bg-blue-500 text-black px-4 py-2 flex rounded-3xl items-center space-x-2"&gt;
         &lt;p&gt;+ Add New Task&lt;/p&gt;
    &lt;/button&gt;
</code></pre>
<p>Next, navigate to the <code>BoardTasks</code> component, where we will also implement the trigger for the "Edit task" modal.</p>
<p>Here, include the <code>openAddAndEditTaskModal</code> function among the imported functions from the <code>appSlice</code>:</p>
<pre><code class="lang-tsx">import { openAddAndEditBoardModal, openAddAndEditTaskModal } from "@/components/redux/features/appSlice";
</code></pre>
<p>Then, update the <code>&lt;MdEdit/&gt;</code> React icon to incorporate the <code>onClick</code> function that triggers the "Edit Task" modal:</p>
<pre><code class="lang-tsx">    &lt;MdEdit
    onClick={() =&gt;
    dispatch(
      openAddAndEditTaskModal({
        variant: "Edit Task", title, index, name
      }),
    )
   }
   className="text-lg cursor-pointer"
   /&gt;;
</code></pre>
<p>Next, we'll create the Add and Edit Board modal component, also integrating its functionalities.</p>
<p>As depicted in the modal images presented at the start of this section, within the "Add New Task" modal, the title field is intended for the task's title a user wishes to add, and the status field should exclusively contain the accurate names of the columns. Any attempt to input a column name that doesn't exist will result in an error.</p>
<p>In the "Edit Task" modal, the title and status fields will display the current title and status of a task. Altering the title will update the task's title while modifying the status will relocate it to the desired column.</p>
<p>To begin, within your <code>src/app/components</code> directory, create a file named <code>AddAndEditTaskModal.tsx</code>, and firstly, insert the provided code to make the necessary imports, type definitions, and initial data for the add task modal:</p>
<pre><code class="lang-tsx"> "use client";

import { useEffect, useState } from "react";
import { Modal, ModalBody } from "./Modal";
import { useAppDispatch, useAppSelector } from "@/components/redux/hooks";
import {
  getAddAndEditTaskModalValue,
  getAddAndEditTaskModalVariantValue,
  getAddAndEditTaskModalTitle,
  closeAddAndEditTaskModal,
  getCurrentBoardName,
  getAddAndEditTaskModalIndex,
  getAddAndEditTaskModalName,
} from "@/components/redux/features/appSlice";
import {
  useFetchDataFromDbQuery,
  useUpdateBoardToDbMutation,
} from "@/components/redux/services/apiSlice";
import { id } from '../utils/data'

interface ITaskData {
  id: string,
  title: string;
  status: string;
}
// initial task data for the add task modal
let initialTaskData: ITaskData = {
  id: id(),
  title: "",
  status: "",
};

export default function AddOrEditTaskModal() {
//variable declarations, functions, JSX
}
</code></pre>
<p>Next, go to the <code>AddAndEditTaskModal</code> function and paste the following code into it to declare variables and state values. The comments provided explain the future use of each of the declarations.</p>
<pre><code class="lang-tsx">  let { data } = useFetchDataFromDbQuery();
  let [updateBoardToDb, { isLoading }] = useUpdateBoardToDbMutation();
  const [taskData, setTaskData] = useState&lt;ITaskData&gt;();
  const [isTaskTitleEmpty, setIsTaskTitleEmpty] = useState&lt;boolean&gt;();
  const [isTaskStatusEmpty, setIsTaskStatusEmpty] = useState&lt;boolean&gt;();
  const [statusExists, setStatusExists] = useState&lt;boolean&gt;(true);
  const [columnNames, setColumnNames] = useState&lt;[]&gt;();
  const dispatch = useAppDispatch();
  const isModalOpen = useAppSelector(getAddAndEditTaskModalValue);
  const modalVariant = useAppSelector(getAddAndEditTaskModalVariantValue);
  const isVariantAdd = modalVariant === "Add New Task";
  const closeModal = () =&gt; dispatch(closeAddAndEditTaskModal());
  const currentBoardTitle = useAppSelector(getCurrentBoardName);
  // get task title, index and name from redux store
  const currentTaskTitle = useAppSelector(getAddAndEditTaskModalTitle);
  const currentTaskIndex = useAppSelector(getAddAndEditTaskModalIndex);
  const initialTaskColumn = useAppSelector(getAddAndEditTaskModalName);
</code></pre>
<p>Here, we’ll implement functions responsible for the modal functionality. Just below the variable definitions above, paste the following functions:</p>
<pre><code class="lang-tsx">  // Effect to set initial data for the modal based on the variant
  useEffect(() =&gt; {
    if (data) {
      const activeBoard = data[0].boards.find(
        (board: { name: string }) =&gt; board.name === currentBoardTitle
      );
      if (activeBoard) {
        const { columns } = activeBoard;
        const columnNames = columns.map(
          (column: { name: string }) =&gt; column.name
        );

        if (columnNames) {
          setColumnNames(columnNames);
        }

        if (isVariantAdd) {
          setTaskData(initialTaskData);
        }

        else {
          const activeTask = columns
            .map((column: { tasks: [] }) =&gt; column.tasks)
            .flat()
            .find((task: { title: string }) =&gt; task.title === currentTaskTitle);
          setTaskData(activeTask);
        }
      }
    }
  }, [data, modalVariant]);

  // Effect to clear error messages after a certain time
  useEffect(() =&gt; {
    const timeoutId = setTimeout(() =&gt; {
      setIsTaskStatusEmpty(false);
      setIsTaskStatusEmpty(false);
      setStatusExists(true);
    }, 3000);
    return () =&gt; clearTimeout(timeoutId);
  }, [isTaskStatusEmpty, isTaskTitleEmpty, statusExists]);

  // Handler for task title change
  const handleTaskTitleChange = (e: React.ChangeEvent&lt;HTMLInputElement&gt;) =&gt; {
    if (taskData) {
      const newTitle = { ...taskData, title: e.target.value };
      setTaskData(newTitle);
    }
  };

  // Handler for task status change
  const handleTaskStatusChange = (e: React.ChangeEvent&lt;HTMLInputElement&gt;) =&gt; {
    if (taskData) {
      const newTitle = { ...taskData, status: e.target.value };
      setTaskData(newTitle);
    }
  };

  // Handler to add new task to the db
  const handleAddNewTaskToDb = (e: React.FormEvent&lt;HTMLButtonElement&gt;) =&gt; {

    e.preventDefault();
    const { title, status } = taskData!;

    if (!title) {
      setIsTaskTitleEmpty(true);
    }

    if (!status) {
      setIsTaskStatusEmpty(true);
    }

    // check if the status input exists among the existing columns
    const doesStatusExists = columnNames?.some(
      (column) =&gt; column === taskData?.status
    );

    if (!doesStatusExists) {
      setStatusExists(false);
    }

    // if all conditions are met
    if (title &amp;&amp; status &amp;&amp; doesStatusExists) {
      if (data) {
        const [boards] = data;
        const boardsCopy = [...boards.boards];
        const activeBoard = boardsCopy.find(
          (board: { name: string }) =&gt; board.name === currentBoardTitle
        );
        const activeBoardIndex = boardsCopy.findIndex(
          (board: { name: string }) =&gt; board.name === currentBoardTitle
        );
        const { columns } = activeBoard;
        // find the column in the board to update
        const getStatusColumn = columns?.find(
          (column: { name: string }) =&gt; column.name === status
        );
        const getStatusColumnIndex = columns?.findIndex(
          (column: { name: string }) =&gt; column.name === status
        );
        // desctructure tasks in a column. "Now" for example.
        const { tasks } = getStatusColumn;
        const addNewTask = [...tasks, { id: id(), title, status }]; //add new task
        const updatedStatusColumn = { ...getStatusColumn, tasks: addNewTask };
        //update the columns in a board
        const columnsCopy = [...columns];
        columnsCopy[getStatusColumnIndex] = updatedStatusColumn;
        const updatedBoard = {
          ...boards.boards[activeBoardIndex],
          columns: columnsCopy,
        };
        //update the board in the db
        boardsCopy[activeBoardIndex] = updatedBoard;
        updateBoardToDb(boardsCopy);
      }
    }
  };

  const handleEditTaskToDb = (e: React.FormEvent&lt;HTMLButtonElement&gt;) =&gt; {
    e.preventDefault();
    const { title, status } = taskData!;
    if (!title) {
      setIsTaskTitleEmpty(true);
    }
    if (!status) {
      setIsTaskStatusEmpty(true);
    }
    // check if the status input exists among the existing status
    const doesStatusExists = columnNames?.some(
      (column) =&gt; column === taskData?.status
    );
    if (!doesStatusExists) {
      setStatusExists(false);
    }
    if (title &amp;&amp; status &amp;&amp; doesStatusExists) {
      if (data) {
        const [boards] = data;
        const boardsCopy = [...boards.boards];
        const activeBoard = boardsCopy.find(
          (board: { name: string }) =&gt; board.name === currentBoardTitle
        );
        const activeBoardIndex = boardsCopy.findIndex(
          (board: { name: string }) =&gt; board.name === currentBoardTitle
        );
        const { columns } = activeBoard;
        const getStatusColumnIndex = columns?.findIndex(
          (column: { name: string }) =&gt; column.name === status
        );

        // Check if the task status to edit is equal to the column.name
        if (status === initialTaskColumn) {
          const updatedStatusColumn = {
            ...columns[getStatusColumnIndex],
            tasks: columns[getStatusColumnIndex]?.tasks?.map(
              (task: any, index: number) =&gt; {
                if (index === currentTaskIndex) {
                  return { title, status };
                }
                return task;
              }
            ),
          };
          const columnsCopy = [...columns];
          columnsCopy[getStatusColumnIndex] = updatedStatusColumn;
          const updatedBoard = {
            ...boards.boards[activeBoardIndex],
            columns: columnsCopy,
          };
          //update the board in the db
          boardsCopy[activeBoardIndex] = updatedBoard;
          updateBoardToDb(boardsCopy);
        } else {
          // Find the column with the name in the task status and append the edited task
          const getStatusColumn = columns?.find(
            (column: { name: string }) =&gt; column.name === status
          );
          // delete task from previous column
          const getPrevStatusColumn = columns?.find(
            (column: { name: string }) =&gt; column.name === initialTaskColumn
          );
          const getPrevStatusColumnIndex = columns?.findIndex(
            (column: { name: string }) =&gt; column.name === initialTaskColumn
          );
          //update the previous column of the task
          const updatedPrevStatusColumn = {
            ...getPrevStatusColumn,
            tasks: getPrevStatusColumn?.tasks.filter(
              (_task: [], index: number) =&gt; index !== currentTaskIndex
            ),
          };
          // update the new column of the task
          const updatedStatusColumn = {
            ...getStatusColumn,
            tasks: [...getStatusColumn?.tasks, { title, status }],
          };
          const columnsCopy = [...columns];
          columnsCopy[getStatusColumnIndex] = updatedStatusColumn;
          columnsCopy[getPrevStatusColumnIndex] = updatedPrevStatusColumn;
          const updatedBoard = {
            ...boards.boards[activeBoardIndex],
            columns: columnsCopy,
          };
          //update the board in the db
          boardsCopy[activeBoardIndex] = updatedBoard;
          updateBoardToDb(boardsCopy);
        }
      }
    }
  };
</code></pre>
<p>Finally, in this component, paste the code below to implement the JSX of the modal:</p>
<pre><code class="lang-tsx">return (
    &lt;Modal isOpen={isModalOpen} onRequestClose={closeModal}&gt;
      &lt;ModalBody&gt;
        &lt;p className="font-bold text-lg"&gt;{modalVariant}&lt;/p&gt;
        &lt;div className="py-6"&gt;
          &lt;div&gt;
            &lt;label htmlFor="title" className="text-sm"&gt;
              Title
            &lt;/label&gt;
            &lt;div className="pt-2"&gt;
              &lt;input
                id="title"
                className={`${
                  isTaskTitleEmpty ? "border-red-500" : "border-stone-200"
                } border w-full p-2 rounded text-sm cursor-pointer focus:outline-none`}
                placeholder="Name"
                value={taskData?.title}
                onChange={handleTaskTitleChange}
              /&gt;
            &lt;/div&gt;
            {isTaskTitleEmpty ? (
              &lt;p className="text-xs text-red-500"&gt;Task title cannot be empty&lt;/p&gt;
            ) : (
              ""
            )}
          &lt;/div&gt;

          &lt;div className="mt-3"&gt;
            &lt;label htmlFor="status" className="text-sm"&gt;
              Status
            &lt;/label&gt;
            &lt;div className="pt-2"&gt;
              &lt;input
                id="status"
                className={`${
                  isTaskStatusEmpty || !statusExists
                    ? "border-red-500"
                    : "border-stone-200"
                } border w-full p-2 rounded text-sm cursor-pointer focus:outline-none`}
                placeholder={columnNames?.join(", ")}
                value={taskData?.status}
                onChange={handleTaskStatusChange}
              /&gt;
            &lt;/div&gt;
            {isTaskStatusEmpty ? (
              &lt;p className="text-xs text-red-500"&gt;
                Task status cannot be empty
              &lt;/p&gt;
            ) : !statusExists ? (
              &lt;p className="text-xs text-red-500"&gt;Column does not exist&lt;/p&gt;
            ) : (
              ""
            )}
          &lt;/div&gt;
          &lt;div className="pt-6"&gt;
            &lt;button
              type="submit"
              onClick={(e: React.FormEvent&lt;HTMLButtonElement&gt;) =&gt; {
                // function to run depending on the variant of the modals
                isVariantAdd ? handleAddNewTaskToDb(e) : handleEditTaskToDb(e);
              }}
              className="bg-blue-500 rounded-3xl py-2 w-full text-sm font-bold"
            &gt;
              &lt;p&gt;
                {isLoading
                  ? "Loading"
                  : `${isVariantAdd ? "Create Task" : "Save Changes"}`}
              &lt;/p&gt;
            &lt;/button&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/ModalBody&gt;
    &lt;/Modal&gt;
  );
</code></pre>
<p>Lastly, import and render the component in your <code>src/app/page.tsx</code> file as seen below:</p>
<pre><code class="lang-tsx">   //rest of the imports
   import AddAndEditTaskModal from "./components/AddAndEditTaskModal";
     //rest of the code
     return (
    &lt;main className="flex h-full"&gt;
      &lt;Sidebar /&gt;
      &lt;BoardTasks /&gt;
      &lt;AddAndEditBoardModal /&gt;
      &lt;AddAndEditTaskModal/&gt;  //render here
    &lt;/main&gt;
    );
</code></pre>
<p>With this functionality, you can effortlessly add tasks to any desired columns. For instance, let's add a new task titled "Buy tomatoes" to the "Next" column:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/23.gif" alt="Add buy tomatoes task to the board" width="600" height="400" loading="lazy"></p>
<p>Likewise, we'll illustrate the task-editing feature by changing the column of "Launch version two" from "Now" to "Later":</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/24.gif" alt="Edit task column" width="600" height="400" loading="lazy"></p>
<p>Finally, in the next section, we'll implement the delete functionalities for both boards and tasks. </p>
<h3 id="heading-how-to-delete-boards-and-tasks">How to delete boards and tasks</h3>
<p>By the end of this section, the "Delete Board" modal should look like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/25.png" alt="Delete board markup" width="600" height="400" loading="lazy"></p>
<p>Likewise, the "Delete Task" modal:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/26.png" alt="Delete task markup" width="600" height="400" loading="lazy"></p>
<p>As you can see, these modals share similarities, so we will use the same methodology as we did for the previous modal implementations.</p>
<p>To begin, let's update the <code>initialState</code> object in our <code>appSlice</code> to manage the state of the "Delete Board and Tasks" modal. Integrate the <code>isDeleteBoardAndTaskModal</code> state into the <code>initialState</code> object as illustrated below:</p>
<pre><code class="lang-ts">    <span class="hljs-keyword">const</span> initialState = {
      <span class="hljs-comment">//rest of the state</span>
      isDeleteBoardAndTaskModal: { isOpen: <span class="hljs-literal">false</span>, variant: <span class="hljs-string">""</span>,  title:<span class="hljs-string">''</span>, status: <span class="hljs-string">""</span>, index: <span class="hljs-number">-1</span> },
</code></pre>
<p>Next, include the following functions in the <code>reducers</code> object. These functions will be invoked to open and close the modal:</p>
<pre><code class="lang-ts">    <span class="hljs-comment">// Open the delete board and task modal with a specified variant (delete board or task)</span>
   openDeleteBoardAndTaskModal: <span class="hljs-function">(<span class="hljs-params">state, { payload }</span>) =&gt;</span> {
      state.isDeleteBoardAndTaskModal.isOpen = <span class="hljs-literal">true</span>;
      state.isDeleteBoardAndTaskModal.variant = payload.variant;
      state.isDeleteBoardAndTaskModal.title = payload.title;
      state.isDeleteBoardAndTaskModal.status = payload.status;
      state.isDeleteBoardAndTaskModal.index = payload.index;
    },
   <span class="hljs-comment">// Close the delete board and task modal</span>
   closeDeleteBoardAndTaskModal: <span class="hljs-function">(<span class="hljs-params">state</span>) =&gt;</span> {
      state.isDeleteBoardAndTaskModal.isOpen = <span class="hljs-literal">false</span>;
      state.isDeleteBoardAndTaskModal.variant = <span class="hljs-string">""</span>;
      state.isDeleteBoardAndTaskModal.title = <span class="hljs-string">""</span>;
      state.isDeleteBoardAndTaskModal.status = <span class="hljs-string">""</span>;
      state.isDeleteBoardAndTaskModal.index = <span class="hljs-number">-1</span>;
    },
</code></pre>
<p>Lastly, include the newly implemented functions and the selector functions in the exports:</p>
<pre><code class="lang-ts"> <span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> {
    openDeleteBoardAndTaskModal,
    closeDeleteBoardAndTaskModal,
   } = features.actions;

   <span class="hljs-comment">// Delete task and board</span>
   <span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getDeleteBoardAndTaskModalValue = <span class="hljs-function">(<span class="hljs-params">state: RootState</span>) =&gt;</span> state.features.isDeleteBoardAndTaskModal.isOpen;
   <span class="hljs-comment">// Selector function to retrieve variant state value </span>
   <span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getDeleteBoardAndTaskModalVariantValue = <span class="hljs-function">(<span class="hljs-params">state: RootState</span>) =&gt;</span> state.features.isDeleteBoardAndTaskModal.variant;
   <span class="hljs-comment">// Selector function to retrieve title state value </span>
   <span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getDeleteBoardAndTaskModalTitle = <span class="hljs-function">(<span class="hljs-params">state: RootState</span>) =&gt;</span> state.features.isDeleteBoardAndTaskModal.title;
   <span class="hljs-comment">// Selector function to retrieve status state value</span>
   <span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getDeleteBoardAndTaskModalStatus = <span class="hljs-function">(<span class="hljs-params">state: RootState</span>) =&gt;</span> state.features.isDeleteBoardAndTaskModal.status;
   <span class="hljs-comment">// Selector function to retrieve index state value</span>
   <span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getDeleteBoardAndTaskModalIndex = <span class="hljs-function">(<span class="hljs-params">state: RootState</span>) =&gt;</span> state.features.isDeleteBoardAndTaskModal.index;
</code></pre>
<p>Following that, we'll implement the <code>onClick</code> functions to enable users to interact with the modal and execute delete-related actions. These functions will permit users to open the "Delete board" modal from the dropdown in the navbar and the "Delete task" modal by clicking the delete icon within individual task cards.</p>
<p>In the <code>components/Dropdown.tsx</code> file, add the <code>openDeleteBoardAndTaskModal</code> function to the list of imported functions from the <code>appSlice</code>:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> { openDeleteBoardAndTaskModal } <span class="hljs-keyword">from</span> <span class="hljs-string">'@/components/redux/features/appSlice'</span>;
</code></pre>
<p>Then adjust the "Delete board" button to incorporate the <code>onClick</code> function to open the modal. This action will trigger the “Delete board” modal:</p>
<pre><code class="lang-ts">      &lt;div className=<span class="hljs-string">"hover:bg-gray-300"</span>&gt;
         &lt;button
            onClick={<span class="hljs-function">() =&gt;</span> dispatch(openDeleteBoardAndTaskModal({variant: <span class="hljs-string">"Delete this board?"</span>}))}
            className=<span class="hljs-string">"text-sm px-4 py-2"</span>&gt;
            Delete Board
         &lt;/button&gt;
      &lt;/div&gt;
</code></pre>
<p>Move on to the <code>BoardTasks</code> component, and similarly, include the function for deleting tasks and boards among the imports from the <code>appSlice</code>:</p>
<pre><code class="lang-ts">      <span class="hljs-keyword">import</span> {
         <span class="hljs-comment">//other imports</span>
         openDeleteBoardAndTaskModal
      } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/components/redux/features/appSlice"</span>;
</code></pre>
<p>Adjust the delete React icon to include the <code>onClick</code> function to open the modal:</p>
<pre><code class="lang-tsx">      &lt;MdDelete
         onClick={() =&gt;
            dispatch(
               openDeleteBoardAndTaskModal({
                  variant: "Delete this Task?",
                  status,
                  index,
               }),
            )
         }
         className="text-lg cursor-pointer text-red-500"
      /&gt;;
</code></pre>
<p>Now we'll start building the markup for the delete board and tasks modal, coupled with the implementation of its functionalities.</p>
<p>In your <code>app/components</code> folder, create a file named <code>DeleteBoardAndTask</code> modal and paste the provided code inside of it:</p>
<pre><code class="lang-tsx">   import { Modal, ModalBody } from "./Modal";
   import { useAppDispatch, useAppSelector } from "@/components/redux/hooks";
   import {
   closeDeleteBoardAndTaskModal,
   getDeleteBoardAndTaskModalValue,
   getDeleteBoardAndTaskModalVariantValue,
   getDeleteBoardAndTaskModalTitle,
   getDeleteBoardAndTaskModalIndex,
   getDeleteBoardAndTaskModalStatus,
   getCurrentBoardName,
   } from "@/components/redux/features/appSlice";
   import {
   useFetchDataFromDbQuery,
   useUpdateBoardToDbMutation,
   } from "@/components/redux/services/apiSlice";

   export default function DeleteBoardAndTaskModal() {
     //variable declarations, functions, JSX
   }
</code></pre>
<p>Next, go to the <code>DeleteBoardAndTaskModal</code> function and paste the following code into it to declare variables and state values. The comments provided explain the future use of each of the declarations.</p>
<pre><code class="lang-tsx">   const dispatch = useAppDispatch();
   const isModalOpen = useAppSelector(getDeleteBoardAndTaskModalValue);
   const closeModal = () =&gt; dispatch(closeDeleteBoardAndTaskModal());
   const currentBoardName = useAppSelector(getCurrentBoardName);
   const modalVariant = useAppSelector(getDeleteBoardAndTaskModalVariantValue);
   const taskTitle = useAppSelector(getDeleteBoardAndTaskModalTitle);
   const taskIndex = useAppSelector(getDeleteBoardAndTaskModalIndex);
   const taskStatus = useAppSelector(getDeleteBoardAndTaskModalStatus);
   let { data } = useFetchDataFromDbQuery();
   const [updateBoardToDb, { isLoading }] = useUpdateBoardToDbMutation();
</code></pre>
<p>Here, we’ll implement the function responsible for the modal functionality. Just below the variable definitions above, paste the following function:</p>
<pre><code class="lang-tsx">   const handleDelete = (e: React.FormEvent&lt;HTMLButtonElement&gt;) =&gt; {
    e.preventDefault();
    if (data) {
      if (modalVariant === "Delete this board?") {
        // Implement the logic for deleting the board
        if (currentBoardName) {
          //  Assuming data is available, you need to handle the logic to update the data
          const [boards] = data;
          const updatedBoards = boards.boards.filter(
            (board: { name: string }) =&gt; board.name !== currentBoardName
          );
          updateBoardToDb(updatedBoards);
        }
      } else {
        // Implement the logic for deleting a task
        if (taskIndex !== undefined &amp;&amp; taskStatus &amp;&amp; currentBoardName) {
          const [boards] = data;
          //  Handle the logic to update the tasks
          const updatedBoards = boards.boards.map(
            (board: {
              name: string;
              columns: [{ name: string; tasks: [] }];
            }) =&gt; {
            // check the board active board
              if (board.name === currentBoardName) {
                // loop through the columns of the board to find the column in which the task to edit is
                const updatedColumns = board.columns.map((column) =&gt; {
                  if (column.name === taskStatus) {
                    // delete the the task
                    const updatedTasks = column.tasks.filter(
                      (_, index: number) =&gt; index !== taskIndex
                    );
                    return { ...column, tasks: updatedTasks };
                  }
                  return column;
                });
                return { ...board, columns: updatedColumns };
              }
              return board;
            }
          );
          updateBoardToDb(updatedBoards);
        }
      }
    }
   };
</code></pre>
<p>Finally, in this component, paste the code below to implement the JSX of the modal:</p>
<pre><code>   <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Modal</span> <span class="hljs-attr">isOpen</span>=<span class="hljs-string">{isModalOpen}</span> <span class="hljs-attr">onRequestClose</span>=<span class="hljs-string">{closeModal}</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">ModalBody</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-red font-bold text-lg"</span>&gt;</span>{modalVariant}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"pt-6"</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-sm text-medium-grey leading-6"</span>&gt;</span>
            {modalVariant === "Delete this board?"
              ? `Are you sure you want to delete the '${currentBoardName}' board? This action will remove all columns
                and tasks and cannot be reversed.`
              : `Are you sure you want to delete the '${taskTitle}' tasks? This action cannot be reversed.`}
          <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"pt-6 flex space-x-2"</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"w-1/2"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">button</span>
              <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span>
              <span class="hljs-attr">onClick</span>=<span class="hljs-string">{(e:</span> <span class="hljs-attr">React.FormEvent</span>&lt;<span class="hljs-attr">HTMLButtonElement</span>&gt;</span>) =&gt;
                handleDelete(e)
              }
              className="bg-red-500 rounded-3xl py-2 w-full text-sm font-bold"
            &gt;
              {" "}
              {isLoading ? "Loading" : "Delete"}
            <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"w-1/2"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">button</span>
              <span class="hljs-attr">onClick</span>=<span class="hljs-string">{closeModal}</span>
              <span class="hljs-attr">className</span>=<span class="hljs-string">"bg-stone-200 rounded-3xl py-2 w-full text-sm font-bold"</span>
            &gt;</span>
              Cancel
            <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">ModalBody</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Modal</span>&gt;</span>
   );
   }</span>
</code></pre><p>After making this update, import the component in the <code>page.tsx</code> and render it as seen below:</p>
<pre><code class="lang-tsx">     //rest of the imports
     import DeleteBoardOrTaskModal from "./components/DeleteBoardAndTaskModal";
     //rest of the code
       return (
    &lt;main className="flex h-full"&gt;
      &lt;Sidebar /&gt;
      &lt;BoardTasks /&gt;
      &lt;AddAndEditBoardModal /&gt;
      &lt;AddAndEditTaskModal/&gt;
      &lt;DeleteBoardAndTaskModal/&gt;
    &lt;/main&gt;
    );
</code></pre>
<p>After rendering the component, you can now delete a board. As an example, we’ll delete the “Marketing” board we previously created:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/27.gif" alt="Delete board functionality" width="600" height="400" loading="lazy"></p>
<p>Likewise, you can delete a task:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/28.gif" alt="Delete task functionality" width="600" height="400" loading="lazy"></p>
<p>In the next section, we’ll explore the implementation of the drag and drop functionality with the <code>react-beautiful-dnd</code> library.</p>
<h2 id="heading-how-to-implement-drag-and-drop-functionality">How to Implement Drag and Drop Functionality</h2>
<p>At the end of this section, you should be able to move tasks between columns and across columns. </p>
<p>To begin, install the <code>react-beautiful-dnd</code> library with the following command:</p>
<pre><code class="lang-npm">npm i react-beautiful-dnd
</code></pre>
<p>It’s worth noting that <a target="_blank" href="https://github.com/atlassian/react-beautiful-dnd/issues/2399#issuecomment-1111169234">the react-beautiful-dnd library does not work inside the <code>StrictMode</code> wrapper</a> which is <a target="_blank" href="https://nextjs.org/docs/pages/api-reference/next-config-js/reactStrictMode">enabled by default in the app router</a>. So we have to create a custom hook which will enable us use the <code>react-beautiful-dnd</code> library safely with <code>StrictMode</code>. </p>
<p>Create a file named <code>StrictModeDroppable.tsx</code> inside your <code>src/app/components</code> folder and paste the provided code below inside of it:</p>
<pre><code class="lang-tsx">import { useEffect, useState } from "react";
import { Droppable, DroppableProps } from "react-beautiful-dnd";

export const StrictModeDroppable = ({ children, ...props }: DroppableProps) =&gt; {
  const [enabled, setEnabled] = useState(false);

  useEffect(() =&gt; {
    const animation = requestAnimationFrame(() =&gt; setEnabled(true));

    return () =&gt; {
      cancelAnimationFrame(animation);
      setEnabled(false);
    };
  }, []);

  if (!enabled) {
    return null;
  }

  return &lt;Droppable {...props}&gt;{children}&lt;/Droppable&gt;;
};
</code></pre>
<p>This way, we have made it compatible with <code>StrictMode</code>, allowing us to safely implement the drag and drop feature.</p>
<p>Next, navigate to the <code>BoardTasks.tsx</code> component and update it with the code below:</p>
<p>Firstly, import needed components from the <code>react-beautiful-dnd</code> library and also from our custom <code>StrictModeDroppable.tsx</code> component:</p>
<pre><code class="lang-tsx">//import useRef hook
import { useEffect, useState, useRef } from "react";
import { DragDropContext, Draggable } from "react-beautiful-dnd";
// import Droppable from the custom hook
import { StrictModeDroppable as Droppable } from "./StrictModeDroppable";
</code></pre>
<p>After updating the imports, go to the BoardTasks function and include the following functions:</p>
<pre><code class="lang-tsx">// check if it’s the first render
const initialRender = useRef(true);


  const handleDragEnd = async ({ destination, source }: any) =&gt; {
    // Check if the destination is not null (i.e., it was dropped in a valid droppable)
    if (!destination) return;


    // get a deep nested copy of the columns state
    const newColumns = columns.map((column) =&gt; ({
      ...column,
      tasks: [...column.tasks], // Create a new array for tasks
    }));


    // Find the source and destination columns based on their droppableIds
    const sourceColumnIndex = newColumns.findIndex(
      (col) =&gt; col.id === source.droppableId
    );
    const destinationColumnIndex = newColumns.findIndex(
      (col) =&gt; col.id === destination.droppableId
    );


    // Task that was dragged
    const itemMoved = newColumns[sourceColumnIndex]?.tasks[source.index];


    // Remove from its source
    newColumns[sourceColumnIndex].tasks.splice(source.index, 1);


    // Insert into its destination
    newColumns[destinationColumnIndex].tasks.splice(
      destination.index,
      0,
      itemMoved
    );


    // Update the state
    setColumns(newColumns);
  };


  useEffect(() =&gt; {
    // Check if it's the initial render, to avoid sending the data to the backend on mount
    if (!initialRender.current) {
      // Update the backend with the new order
      try {
        if (data) {
          const [boards] = data;
          const boardsCopy = [...boards.boards];
          const activeBoardIndex = boardsCopy.findIndex(
            (board: { name: string }) =&gt; board.name === currentBoardTitle
          );
          const updatedBoard = {
            ...boards.boards[activeBoardIndex],
            columns,
          };
          boardsCopy[activeBoardIndex] = updatedBoard;
          updateBoardToDb(boardsCopy);
        }
      } catch (error) {
        // Handle error
        console.error("Error updating board:", error);
      }
    } else {
      // Set initial render to false after the first render
      initialRender.current = false;
    }
  }, [columns]);
</code></pre>
<p>So far here, we implemented a function which will be triggered after a task has been dragged. After each trigger of this function, the columns data is being updated and sent to the Cloud Firestore via the <code>useEffect</code> hook. I added some more comments in the code to help you understand better. </p>
<p>Finally, update the JSX in the return statement as seen below:</p>
<pre><code class="lang-tsx"> return (
    &lt;div className="overflow-x-auto overflow-y-auto w-full p-6 bg-stone-200"&gt;
      {/* If data has not been fetched successfully, display a loading state, else display the column of tasks */}
      {isLoading ? (
        &lt;p className="text-3xl w-full text-center font-bold"&gt;
          Loading tasks...
        &lt;/p&gt;
      ) : (
        &lt;&gt;
          {/* If columns of tasks isn't empty: display the tasks, else display the prompt to add a new column */}
          {columns.length &gt; 0 ? (
            &lt;DragDropContext onDragEnd={handleDragEnd}&gt;
              &lt;div className="flex space-x-6"&gt;
                {columns.map((column, index) =&gt; {
                  const { id, name } = column;
                  return (
                    &lt;div key={id} className="w-[17.5rem] shrink-0"&gt;
                      &lt;p className="text-black"&gt;{`${column.name} (${
                        column.tasks ? column.tasks?.length : 0
                      })`}&lt;/p&gt;
                      &lt;Droppable droppableId={id}&gt;
                        {(provided) =&gt; (
                          &lt;div
                            ref={provided.innerRef}
                            {...provided.droppableProps}
                            className="h-full"
                          &gt;
                            {column.tasks &amp;&amp;
                              // Display the tasks if there are tasks in the column, if not, display an empty column
                              (column.tasks.length &gt; 0 ? (
                                column.tasks.map((task, index) =&gt; {
                                  const { id, title, status } = task;
                                  return (
                                    &lt;Draggable
                                      key={id}
                                      draggableId={id}
                                      index={index}
                                    &gt;
                                      {(provided) =&gt; (
                                        &lt;div
                                          ref={provided.innerRef}
                                          {...provided.draggableProps}
                                          {...provided.dragHandleProps}
                                          className="bg-white p-6 rounded-md mt-6 flex items-center justify-between border"
                                        &gt;
                                          &lt;p&gt;{task.title}&lt;/p&gt;
                                          &lt;div className="flex items-center space-x-1"&gt;
                                            &lt;MdEdit
                                              onClick={() =&gt;
                                                dispatch(
                                                  openAddAndEditTaskModal({
                                                    variant: "Edit Task",
                                                    title,
                                                    index,
                                                    name,
                                                  })
                                                )
                                              }
                                              className="text-lg cursor-pointer"
                                            /&gt;
                                            &lt;MdDelete
                                              onClick={() =&gt;
                                                dispatch(
                                                  openDeleteBoardAndTaskModal({
                                                    variant:
                                                      "Delete this task?",
                                                    title,
                                                    status,
                                                    index,
                                                  })
                                                )
                                              }
                                              className="text-lg cursor-pointer text-red-500"
                                            /&gt;
                                          &lt;/div&gt;
                                        &lt;/div&gt;
                                      )}
                                    &lt;/Draggable&gt;
                                  );
                                })
                              ) : (
                                &lt;div className="mt-6 h-full rounded-md border-dashed border-4 border-white" /&gt;
                              ))}
                            {provided.placeholder}
                          &lt;/div&gt;
                        )}
                      &lt;/Droppable&gt;
                    &lt;/div&gt;
                  );
                })}
                {/* If the number of columns of tasks is less than 7, display an option to add more columns */}
                {columns.length &lt; 7 ? (
                  &lt;div
                    onClick={() =&gt;
                      dispatch(openAddAndEditBoardModal("Edit Board"))
                    }
                    className="rounded-md bg-white w-[17.5rem] mt-12 shrink-0 flex justify-center items-center"
                  &gt;
                    &lt;p className="cursor-pointer font-bold text-black text-2xl"&gt;
                      + New Column
                    &lt;/p&gt;
                  &lt;/div&gt;
                ) : (
                  ""
                )}
              &lt;/div&gt;
            &lt;/DragDropContext&gt;
          ) : (
            &lt;div className="w-full h-full flex justify-center items-center"&gt;
              &lt;div className="flex flex-col items-center"&gt;
                &lt;p className="text-black text-sm"&gt;
                  This board is empty. Create a new column to get started.
                &lt;/p&gt;
                &lt;button className="bg-blue-500 text-black px-4 py-2 flex mt-6 rounded-3xl items-center space-x-2"&gt;
                  &lt;p&gt;+ Add New Column&lt;/p&gt;
                &lt;/button&gt;
              &lt;/div&gt;
            &lt;/div&gt;
          )}
        &lt;/&gt;
      )}
    &lt;/div&gt;
  );
</code></pre>
<p>In the code snippet above, we wrapped <code>DragDropContext</code> around the columns of tasks with its <code>onDragEnd</code> attribute, which accepts the <code>handleDragEnd</code> function, which will be triggered after a task has been dragged. </p>
<p>Don’t forget that after each trigger of this function, the columns data is being updated and sent to the Cloud Firestore via the <code>useEffect</code> hook. </p>
<p>Each column of task is also wrapped around the <code>Droppable</code> component. This signifies that this is a location you can drop a task. It also accepts a <code>droppableId</code> attribute which we passed the <code>id</code> of each column to it. </p>
<p>Each task card is also wrapped around the <code>Draggable</code> component, this makes them draggable within and among columns. </p>
<p>With these changes, we have easily implemented the drag and drop feature for our app:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/29.gif" alt="drag and drop functionality" width="600" height="400" loading="lazy"></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>This tutorial guided you through implementing authentication using the <code>next-auth</code> library, setting up a Redux store, and integrating Firebase with its <code>RTK Query</code> in Next.js applications. </p>
<p>You also learned to implement CRUD operations in a Kanban task management app, and looked into form validations with JavaScript. </p>
<p>And finally, we covered the implementation of drag-and-drop functionality using the <code>react-beautiful-dnd</code> library.</p>
<p>Across the tutorial, we also leveraged existing libraries to streamline development rather than building everything from scratch.</p>
<p>If you want to see all the code, you can visit the project's GitHub repository <a target="_blank" href="https://github.com/SiR-PENt/kanban-app-tutorial">here</a>. Feel free to fork the project and open a PR if you feel the need for any improvements. If you’d also like to play around with the live site, you can find it <a target="_blank" href="https://kanban-app-tutorial.vercel.app">here</a>. </p>
<p>If you'd also like to explore this project with more advanced features, like dark mode, sleeker UI design, and better functionalities, visit it <a target="_blank" href="https://kanban-task-management-app-delta.vercel.app">here</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Code and Deploy an Instagram Clone with React and Firebase ]]>
                </title>
                <description>
                    <![CDATA[ Creating an Instagram clone is a fun project that can help you master important front end development skills.   We just published a course on the freeCodeCamp.org YouTube channel that teaches you how to create an Instagram clone using React and Fireb... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/code-and-deploy-an-instagram-clone-with-react-and-firebase/</link>
                <guid isPermaLink="false">66b20147a2135cc2539a2143</guid>
                
                    <category>
                        <![CDATA[ Firebase ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ youtube ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Beau Carnes ]]>
                </dc:creator>
                <pubDate>Thu, 30 Nov 2023 16:25:58 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/11/instagram2.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Creating an Instagram clone is a fun project that can help you master important front end development skills.  </p>
<p>We just published a course on the freeCodeCamp.org YouTube channel that teaches you how to create an Instagram clone using React and Firebase. Burak Orkmez developed this course.</p>
<p>This course is perfect for those who want to dive into the world of front-end and back-end development by building a real-world, interactive application. It's a journey that combines the dynamic capabilities of React for building user interfaces with the robust back-end features provided by Firebase.</p>
<p>The course covers a wide spectrum of development skills, starting with setting up React and Chakra UI for the front-end design. It walks you through the process of creating essential components of a social media platform, including authentication pages, sidebars, home pages, and profile pages. The focus is on crafting a user-friendly and visually appealing interface, reminiscent of Instagram’s iconic design.</p>
<p>On the back-end side, Firebase plays a crucial role. You'll learn how to set up Firebase to handle various back-end services like user authentication, data storage, and real-time updates. This includes implementing features such as signing up with email and password, Google authentication, and creating a robust authentication system.</p>
<p>One of the core aspects of the course is teaching how to build and manage user interactions. This includes the ability for users to follow and unfollow each other, search for user profiles, suggest users, and create, delete, and like posts, all in real time. Additionally, the course covers advanced functionalities like creating and managing comments on posts, rendering post captions, and fetching feed posts.</p>
<p>React, a JavaScript library for building user interfaces, is at the heart of this course. It's used for developing the front-end, ensuring that the Instagram clone is not just functional but also has a sleek, responsive design. Firebase, a platform developed by Google for creating mobile and web applications, serves as the back-end. It provides a suite of tools for managing databases, user authentication, hosting, and more, making it an ideal choice for real-time applications like social media platforms.</p>
<p>By the end of the course, you'll not only have a deeper understanding of React and Firebase but also a fully functional Instagram clone to showcase your newly acquired skills.</p>
<p>Watch the full course <a target="_blank" href="https://youtu.be/RMScMwY2B6Q">on the freeCodeCamp.org YouTube channel</a> (8-hour watch).</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/RMScMwY2B6Q" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Secure User Authentication Flow in Flutter with Firebase and Bloc State Management ]]>
                </title>
                <description>
                    <![CDATA[ User authentication is critical to mobile app development. It helps make sure that only authorized users can access sensitive information and perform actions within an application. In this tutorial, we will explore how to build secure user authentica... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/user-authentication-flow-in-flutter-with-firebase-and-bloc/</link>
                <guid isPermaLink="false">66be1f69a1b2d9ba40be180e</guid>
                
                    <category>
                        <![CDATA[ BLoC ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Firebase ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Flutter ]]>
                    </category>
                
                    <category>
                        <![CDATA[ State Management  ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Justice Nwogu ]]>
                </dc:creator>
                <pubDate>Tue, 21 Nov 2023 15:21:20 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/11/Group-1--3-.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>User authentication is critical to mobile app development. It helps make sure that only authorized users can access sensitive information and perform actions within an application.</p>
<p>In this tutorial, we will explore how to build secure user authentication in Flutter using Firebase for authentication and the Bloc state management pattern for handling application state. By the end, you'll have a solid understanding of how to integrate Firebase authentication and implement a secure login and sign-up process using Bloc.</p>
<h3 id="heading-prerequisites">Prerequisites:</h3>
<p>To get the most out of this tutorial, you should have the following:</p>
<ul>
<li>A good understanding of Flutter and Dart</li>
<li>A Firebase account: Create a Firebase account if you don't have one. You can set up a Firebase project through the <a target="_blank" href="https://console.firebase.google.com/">Firebase Console</a>.</li>
</ul>
<h2 id="heading-how-firebase-authentication-works"><strong>How Firebase Authentication Works</strong></h2>
<p>Firebase Authentication is a powerful service that simplifies the process of authenticating users in your app. It supports various authentication methods, including email/password, social media, and more.</p>
<p>One of the key advantages of Firebase Authentication is its built-in security features, such as secure storage of user credentials and encryption of sensitive data.</p>
<h2 id="heading-flowchart-description"><strong>FlowChart Description</strong></h2>
<p>Let's visualize the flow of actions using a flowchart to understand the concept you are going to learn. Take a look at the diagram below to get a better understanding:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/11/Flowcharts.png" alt="Image" width="600" height="400" loading="lazy">
<em>Img 1: The flowchart of the app</em></p>
<p>The image above is a flowchart to visualize the flow of the app let's discuss what each parts represents. The rounded rectangles represent the starting and ending points of the flow; the purple rectangles represent the screens; the light blue rectangles represent the processes that take place; and finally, the rhombus represents decision-making.</p>
<ul>
<li>The application starts at the <code>AuthenticationFlowScreen</code>.</li>
<li>The <code>StreamBuilder</code> listens to authentication state changes.</li>
<li>If a user is authenticated, it directs to the <code>HomeScreen</code>; otherwise, it leads to the <code>SignupScreen</code>.</li>
<li><code>AuthenticationBloc</code> manages user authentication events and states.</li>
<li>When the user signs up (<code>SignUpUser</code> event is triggered):</li>
<li>It initiates the authentication loading state (<code>AuthenticationLoadingState</code>).</li>
<li>Calls <code>signUpUser</code> from <code>AuthService</code> for user registration.</li>
<li>If successful, it emits  <code>AuthenticationSuccessState</code> with user data; otherwise, emits <code>AuthenticationFailureState</code>.</li>
<li>When the user initiates the sign-out process (<code>SignOut</code> event is triggered):</li>
<li>It starts the authentication loading state (<code>AuthenticationLoadingState</code>).</li>
<li>Calls <code>signOutUser</code> from <code>AuthService</code> to sign the user out.</li>
<li>If an error occurs during sign-out, it logs the error message.</li>
</ul>
<h2 id="heading-project-setup"><strong>Project Setup</strong></h2>
<p>To get started with Firebase Authentication, you must set up Firebase in your Flutter project.</p>
<p>Follow these steps to add Firebase and bloc to your project:</p>
<h3 id="heading-add-dependencies-to-your-project">Add Dependencies to Your Project</h3>
<p>Open your project in your preferred code editor.</p>
<p>Add the following dependencies to your <code>pubspec.yaml</code> file:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">dependencies:</span>
<span class="hljs-attr">firebase_core:</span> <span class="hljs-string">^2.20.0</span>
<span class="hljs-attr">firebase_auth:</span> <span class="hljs-string">^4.12.0</span>
<span class="hljs-attr">flutter_bloc:</span> <span class="hljs-string">^8.1.3</span>
</code></pre>
<p>Then save the <code>pubspec.yaml</code> file to fetch the dependencies.</p>
<h3 id="heading-configure-firebase">Configure Firebase</h3>
<p>Create a new Firebase project through the Firebase <a target="_blank" href="https://www.freecodecamp.org/news/p/9b9114d1-6fce-4349-a755-fdaa04b2d4ae/console.firebase.google.com">Console</a>. Click on authentication in the project, and follow the provided instructions.</p>
<p>For more information, you can go through the Firebase <a target="_blank" href="https://firebase.google.com/docs/auth">website</a>.</p>
<h3 id="heading-initialize-firebase">Initialize Firebase</h3>
<p>First, open the <code>main.dart</code> file in the <code>lib</code> folder.</p>
<p>Add the following code to the file to initialize Firebase:</p>
<pre><code class="lang-dart">
<span class="hljs-keyword">void</span> main() <span class="hljs-keyword">async</span> {
WidgetsFlutterBinding.ensureInitialized();
<span class="hljs-keyword">await</span> Firebase.initializeApp(
  options: DefaultFirebaseOptions.currentPlatform
);
</code></pre>
<p>The code above shows the code for running the app. There's nothing unusual about this code except that we have added some code to the <code>void main</code> to initialize Firebase.</p>
<h3 id="heading-the-user-model">The User Model</h3>
<p>Before creating the Firebase class to communicate with the Firebase service, let's define a UserModel to represent the user data.</p>
<p>Start by creating a <code>user.dart</code> file in your project's <code>lib</code> directory.</p>
<p>Then add the code below in the file:</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserModel</span> </span>{
<span class="hljs-keyword">final</span> <span class="hljs-built_in">String?</span> id;
<span class="hljs-keyword">final</span> <span class="hljs-built_in">String?</span> email;
<span class="hljs-keyword">final</span> <span class="hljs-built_in">String?</span> displayName;
UserModel({ <span class="hljs-keyword">this</span>.id, <span class="hljs-keyword">this</span>.email, <span class="hljs-keyword">this</span>.displayName, });
}
</code></pre>
<p>Now that you have set up Firebase and created a user model, you need to create a service class to communicate with Firebase directly.</p>
<h3 id="heading-the-authentication-service">The Authentication Service</h3>
<p> Create a folder called <code>services</code>, create a file in this folder called <code>authentication.dart</code> You can now add this code to the file.</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:firebase_auth/firebase_auth.dart'</span>;

<span class="hljs-keyword">import</span> <span class="hljs-string">'../models/user.dart'</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AuthService</span> </span>{
  <span class="hljs-keyword">final</span> FirebaseAuth _firebaseAuth = FirebaseAuth.instance;


  <span class="hljs-comment">/// <span class="markdown">create user</span></span>
  Future&lt;UserModel?&gt; signUpUser(
    <span class="hljs-built_in">String</span> email,
    <span class="hljs-built_in">String</span> password,
  ) <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">final</span> UserCredential userCredential =
          <span class="hljs-keyword">await</span> _firebaseAuth.createUserWithEmailAndPassword(
        email: email.trim(),
        password: password.trim(),
      );
      <span class="hljs-keyword">final</span> User? firebaseUser = userCredential.user;
      <span class="hljs-keyword">if</span> (firebaseUser != <span class="hljs-keyword">null</span>) {
        <span class="hljs-keyword">return</span> UserModel(
          id: firebaseUser.uid,
          email: firebaseUser.email ?? <span class="hljs-string">''</span>,
          displayName: firebaseUser.displayName ?? <span class="hljs-string">''</span>,
        );
      }
    } <span class="hljs-keyword">on</span> FirebaseAuthException <span class="hljs-keyword">catch</span> (e) {
      <span class="hljs-built_in">print</span>(e.toString());
    }
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">null</span>;
  } 

   <span class="hljs-comment">///<span class="markdown">signOutUser </span></span>
   Future&lt;<span class="hljs-keyword">void</span>&gt; signOutUser() <span class="hljs-keyword">async</span> {
      <span class="hljs-keyword">final</span> User? firebaseUser = FirebaseAuth.instance.currentUser;
    <span class="hljs-keyword">if</span> (firebaseUser != <span class="hljs-keyword">null</span>) {
      <span class="hljs-keyword">await</span> FirebaseAuth.instance.signOut();
    }
  }
  <span class="hljs-comment">// ... (other methods)}</span>
}
</code></pre>
<p>The code snippet above is a method to create a user in the app using Firebase. With this method, the <code>signUpUser</code> method takes two string parameters: <code>email</code> and <code>password</code> respectively. Then you call the Firebase method to create a user using the parameters we added.</p>
<p>Now that you know how to create the signup method, you can also create the login method. The class ultimately portrays the communication between Firebase and the app.</p>
<p>The next part is to connect the service to your state management, which we'll see how to do now.</p>
<h2 id="heading-how-bloc-state-management-works"><strong>How Bloc State Management Works</strong></h2>
<p>Bloc is a popular state management pattern for Flutter that helps manage complex application states predictably and in a testable way. Bloc stands for "<strong>Business Logic Component</strong>" and it divides the business logic and the UI. Bloc will be the bridge between your app and Firebase.</p>
<p>There's an extension for <a target="_blank" href="https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc">VScode</a> that creates the boilerplate code for Bloc. You can use the extension to speed up the development process.</p>
<h3 id="heading-set-up-firebase-authentication-bloc">Set Up Firebase Authentication Bloc</h3>
<p>Bloc consists of events and states. Let's first create the states and events for the Bloc. Then we'll create a <code>AuthenticationBloc</code> that will handle the logic using the events, states, and service we have created.</p>
<h4 id="heading-the-authenticationstate-class">The <code>AuthenticationState</code> class</h4>
<p>The <code>AuthenticationState</code> class is responsible for the authentication process's different states. As we will see in the code, there are initial, loading, success, and failure states to ensure we know what happens during the authentication process.</p>
<p>First, create an <code>authentication_state.dart</code> file in your project's <code>bloc</code> directory.</p>
<pre><code class="lang-dart"><span class="hljs-keyword">part</span> of <span class="hljs-string">'authentication_bloc.dart'</span>;


<span class="hljs-keyword">abstract</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AuthenticationState</span> </span>{
  <span class="hljs-keyword">const</span> AuthenticationState();

  <span class="hljs-built_in">List</span>&lt;<span class="hljs-built_in">Object</span>&gt; <span class="hljs-keyword">get</span> props =&gt; [];
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AuthenticationInitialState</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">AuthenticationState</span> </span>{}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AuthenticationLoadingState</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">AuthenticationState</span> </span>{
 <span class="hljs-keyword">final</span> <span class="hljs-built_in">bool</span> isLoading;

  AuthenticationLoadingState({<span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.isLoading});
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AuthenticationSuccessState</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">AuthenticationState</span> </span>{
  <span class="hljs-keyword">final</span> UserModel user;

  <span class="hljs-keyword">const</span> AuthenticationSuccessState(<span class="hljs-keyword">this</span>.user);
  <span class="hljs-meta">@override</span>
  <span class="hljs-built_in">List</span>&lt;<span class="hljs-built_in">Object</span>&gt; <span class="hljs-keyword">get</span> props =&gt; [user];
}
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AuthenticationFailureState</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">AuthenticationState</span> </span>{
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> errorMessage;

  <span class="hljs-keyword">const</span> AuthenticationFailureState(<span class="hljs-keyword">this</span>.errorMessage);

  <span class="hljs-meta">@override</span>
  <span class="hljs-built_in">List</span>&lt;<span class="hljs-built_in">Object</span>&gt; <span class="hljs-keyword">get</span> props =&gt; [errorMessage];
}
</code></pre>
<p>Let's break down the code:</p>
<p><code>AuthenticationState</code> abstract class:</p>
<ul>
<li><code>AuthenticationState</code> is the base class for different states where the authentication process can be at any time.</li>
<li>It contains a method <code>props</code> that returns a list of objects. This method is used for equality checking when comparing instances of this class.</li>
</ul>
<p><code>AuthenticationInitialState</code> class:</p>
<ul>
<li><code>AuthenticationInitialState</code> represents the initial state of the authentication process.</li>
</ul>
<p><code>AuthenticationLoadingState</code> class:</p>
<ul>
<li><code>AuthenticationLoadingState</code> represents a state where the authentication process is in progress, and the UI might show a loading indicator.</li>
<li>It takes a boolean parameter, <code>isLoading</code>, to indicate whether or not the authentication process is currently loading.</li>
</ul>
<p><code>AuthenticationSuccessState</code> class:</p>
<ul>
<li><code>AuthenticationSuccessState</code> represents a state where the authentication process has been completed.</li>
<li>It includes a user property of type UserModel representing the authenticated user.</li>
</ul>
<p><code>AuthenticationFailureState</code> class:</p>
<ul>
<li><code>AuthenticationFailureState</code>  represents a state where the authentication process has failed.</li>
<li>It includes an <code>error message</code> property containing information about the failure.</li>
</ul>
<h4 id="heading-the-authenticationevent-class">The <code>AuthenticationEvent</code> class</h4>
<p>The <code>AuthenticationEvent</code> is responsible for the events the <code>AuthenticationBloc</code> will perform. In this case, it is the sign-in event. You can write the other events, like sign-up and sign-out, here.</p>
<p>Create a <code>authentication_Event.dart</code> file in your project's <code>bloc</code> directory.</p>
<pre><code class="lang-dart"><span class="hljs-keyword">part</span> of <span class="hljs-string">'authentication_bloc.dart'</span>;



<span class="hljs-keyword">abstract</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AuthenticationEvent</span> </span>{
  <span class="hljs-keyword">const</span> AuthenticationEvent();

  <span class="hljs-built_in">List</span>&lt;<span class="hljs-built_in">Object</span>&gt; <span class="hljs-keyword">get</span> props =&gt; [];

}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SignUpUser</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">AuthenticationEvent</span> </span>{
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> email;
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> password;

  <span class="hljs-keyword">const</span> SignUpUser(<span class="hljs-keyword">this</span>.email, <span class="hljs-keyword">this</span>.password);

  <span class="hljs-meta">@override</span>
  <span class="hljs-built_in">List</span>&lt;<span class="hljs-built_in">Object</span>&gt; <span class="hljs-keyword">get</span> props =&gt; [email, password];
}


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SignOut</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">AuthenticationEvent</span> </span>{}
</code></pre>
<p>The <code>AuthenticationEvent</code> class is similar to <code>AuthenticationState</code>. Let's look at the code to see what it's doing:</p>
<p><code>AuthenticationEvent</code> abstract class:</p>
<ul>
<li>This is the base class for different events that trigger authentication state changes.</li>
</ul>
<p><code>SignUpUser</code> class:</p>
<ul>
<li>This class represents an event where a user is attempting to sign up.</li>
<li>It takes two parameters, <code>email</code> and <code>password</code>, representing the credentials the user is using to sign up.</li>
<li>This class's instances will signal the <code>Bloc</code> that a user is trying to sign up, and the <code>Bloc</code> can respond by initiating the sign-up process and transitioning the authentication state accordingly.</li>
</ul>
<p><code>SignOut</code> class:</p>
<ul>
<li>This class's instances will signal the <code>Bloc</code> that a user is trying to sign out. The <code>bloc</code> can respond by initiating the sign-out process and updating the authentication state accordingly.</li>
</ul>
<h4 id="heading-the-authenticationbloc-class">The <code>AuthenticationBloc</code> class</h4>
<p>The <code>AuthenticationBloc</code> will handle the overall authentication state, from what happens when a user clicks a button to what shows on the screen. It also interacts with the Firebase service we created directly.</p>
<p>First, create a file called <code>authentication_bloc.dart</code> in your project's <code>bloc</code> directory.</p>
<p>Add the following code to define the <code>AuthenticationBloc</code> class:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:bloc/bloc.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:meta/meta.dart'</span>;

<span class="hljs-keyword">import</span> <span class="hljs-string">'../models/user.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'../services/authentication.dart'</span>;

<span class="hljs-keyword">part</span> <span class="hljs-string">'authentication_event.dart'</span>;
<span class="hljs-keyword">part</span> <span class="hljs-string">'authentication_state.dart'</span>;



<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AuthenticationBloc</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Bloc</span>&lt;<span class="hljs-title">AuthenticationEvent</span>, <span class="hljs-title">AuthenticationState</span>&gt; </span>{
  <span class="hljs-keyword">final</span> AuthService authService = AuthService();

  AuthenticationBloc() : <span class="hljs-keyword">super</span>(AuthenticationInitialState()) {
    <span class="hljs-keyword">on</span>&lt;AuthenticationEvent&gt;((event, emit) {});

    <span class="hljs-keyword">on</span>&lt;SignUpUser&gt;((event, emit) <span class="hljs-keyword">async</span> {
      emit(AuthenticationLoadingState(isLoading: <span class="hljs-keyword">true</span>));
      <span class="hljs-keyword">try</span> {
          <span class="hljs-keyword">final</span> UserModel? user =
          <span class="hljs-keyword">await</span> authService.signUpUser(event.email, event.password);
      <span class="hljs-keyword">if</span> (user != <span class="hljs-keyword">null</span>) {
        emit(AuthenticationSuccessState(user));

      } <span class="hljs-keyword">else</span> {
        emit(<span class="hljs-keyword">const</span> AuthenticationFailureState(<span class="hljs-string">'create user failed'</span>));
      }
      } <span class="hljs-keyword">catch</span> (e) {
        <span class="hljs-built_in">print</span>(e.toString());
      }
     emit(AuthenticationLoadingState(isLoading: <span class="hljs-keyword">false</span>));
    });

     <span class="hljs-keyword">on</span>&lt;SignOut&gt;((event, emit) <span class="hljs-keyword">async</span> {
      emit(AuthenticationLoadingState(isLoading: <span class="hljs-keyword">true</span>));
      <span class="hljs-keyword">try</span> {
        authService.signOutUser();
      } <span class="hljs-keyword">catch</span> (e) {
        <span class="hljs-built_in">print</span>(<span class="hljs-string">'error'</span>);
        <span class="hljs-built_in">print</span>(e.toString());
      } 
       emit(AuthenticationLoadingState(isLoading: <span class="hljs-keyword">false</span>));
     });
}
}
</code></pre>
<p>In this code snippet, we have created an instance of the <code>AuthService</code> class, which handles user authentication operations, such as signing up and signing out.</p>
<p><code>on&lt;SignUpUser&gt;((event, emit) async { ... }</code> defines a handler for the <code>SignUpUser</code> event. When this event is triggered, the <code>bloc</code> goes through the following steps:</p>
<ul>
<li>It emits an  <code>AuthenticationLoadingState</code> to indicate that the authentication process is in progress.</li>
<li>It calls for the <code>signUpUser</code> method of the  <code>authService</code> to attempt to create a user account with the provided email and password.</li>
<li>If the user account creation is successful (that is, the user is not null), it emits an  <code>AuthenticationSuccessState</code> with the user data.</li>
<li>If the user account creation fails, it emits an <code>AuthenticationFailureState</code> with an error message and logs the error.</li>
<li>Regardless of success or failure, it emits another <code>AuthenticationLoadingState</code> to signal the end of the authentication process.</li>
</ul>
<p><code>on&lt;SignOut&gt;((event, emit) async { ... }</code> defines a handler for the <code>SignOut</code>  event. When this event is triggered, the <code>bloc</code> goes through the following steps:</p>
<ul>
<li>It emits an <code>AuthenticationLoadingState</code> to indicate that the sign-out process is in progress.</li>
<li>It calls the <code>signOutUser</code> method of the <code>authService</code> to sign the user out.</li>
<li>If any errors occur during the sign-out process, it logs the error.</li>
<li>It emits another <code>AuthenticationLoadingState</code> to signal the end of the sign-out process.</li>
</ul>
<p>The <code>AuthenticationBloc</code> manages the state of the authentication process, including loading, success, and failure states, based on the events triggered by user actions. The <code>authService</code> is responsible for carrying out the actual authentication operations.  </p>
<p>With the Bloc set up, we can implement the authentication flow using Bloc.</p>
<h2 id="heading-how-to-implement-the-authentication-flow-with-bloc"><strong>How to Implement the Authentication Flow with Bloc</strong></h2>
<p>To implement the authentication flow, you will create a dedicated Stateless widget to check if a user has logged in already so that we will know what screen to show the user. The page will display different screens based on the user's authentication state.</p>
<h3 id="heading-authenticationflowscreen"><code>AuthenticationFlowScreen</code>:</h3>
<p>Create a new file called <code>authentication_page.dart</code> in your project's <code>screens</code> directory.</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:bloc_authentication_flow/screens/home.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:bloc_authentication_flow/screens/sign_up.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:firebase_auth/firebase_auth.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AuthenticationFlowScreen</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-keyword">const</span> AuthenticationFlowScreen({<span class="hljs-keyword">super</span>.key});
  <span class="hljs-keyword">static</span> <span class="hljs-built_in">String</span> id = <span class="hljs-string">'main screen'</span>;
  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> Scaffold(
      body: StreamBuilder&lt;User?&gt;(
        stream: FirebaseAuth.instance.authStateChanges(),
        builder: (context, snapshot) {
          <span class="hljs-keyword">if</span> (snapshot.hasData) {
            <span class="hljs-keyword">return</span> <span class="hljs-keyword">const</span> HomeScreen();
          } <span class="hljs-keyword">else</span> {
            <span class="hljs-keyword">return</span> <span class="hljs-keyword">const</span> SignupScreen();
          }
        },
      ),
    );
  }
}
</code></pre>
<p>In the above code, you have a <code>StatelessWidget</code> with a <code>StreamBuilder</code> as the child. The <code>StreamBuilder</code> acts as a judge, using Firebase to check the state changes and if a user has logged in or not. If a user has logged in, it directs them to the home screen, else it goes to the sign-up screen.</p>
<p>Change the home route to <code>AuthenticationFlowScreen</code> to allow the app to check before routing to any page.</p>
<pre><code class="lang-dart">   home: <span class="hljs-keyword">const</span> AuthenticationFlowScreen()
</code></pre>
<h3 id="heading-sign-up-screen">Sign-up Screen</h3>
<p>First, create a new file called <code>sign_up.dart</code> in the <code>screens</code> directory.</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:bloc_authentication_flow/screens/home.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter_bloc/flutter_bloc.dart'</span>;

<span class="hljs-keyword">import</span> <span class="hljs-string">'../bloc/authentication_bloc.dart'</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SignupScreen</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatefulWidget</span> </span>{
  <span class="hljs-keyword">static</span> <span class="hljs-built_in">String</span> id = <span class="hljs-string">'login_screen'</span>;

  <span class="hljs-keyword">const</span> SignupScreen({
    Key? key,
  }) : <span class="hljs-keyword">super</span>(key: key);

  <span class="hljs-meta">@override</span>
  State&lt;SignupScreen&gt; createState() =&gt; _SignupScreenState();
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">_SignupScreenState</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">State</span>&lt;<span class="hljs-title">SignupScreen</span>&gt; </span>{
  <span class="hljs-comment">// Text Controllers</span>
  <span class="hljs-keyword">final</span> emailController = TextEditingController();
  <span class="hljs-keyword">final</span> passwordController = TextEditingController();

  <span class="hljs-meta">@override</span>
  <span class="hljs-keyword">void</span> dispose() {
    emailController.dispose();
    passwordController.dispose();
    <span class="hljs-keyword">super</span>.dispose();
  }

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> Scaffold(
      appBar: AppBar(
        title: <span class="hljs-keyword">const</span> Text(
          <span class="hljs-string">'Login to Your Account'</span>,
          style: TextStyle(
            color: Colors.deepPurple,
          ),
        ),
        centerTitle: <span class="hljs-keyword">true</span>,
      ),
      body: Padding(
        padding: <span class="hljs-keyword">const</span> EdgeInsets.all(<span class="hljs-number">16.0</span>),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            <span class="hljs-keyword">const</span> SizedBox(height: <span class="hljs-number">20</span>),
            <span class="hljs-keyword">const</span> Text(<span class="hljs-string">'Email address'</span>),
            <span class="hljs-keyword">const</span> SizedBox(height: <span class="hljs-number">10</span>),
            TextFormField(
              controller: emailController,
              decoration: <span class="hljs-keyword">const</span> InputDecoration(
                border: OutlineInputBorder(),
                hintText: <span class="hljs-string">'Enter your email'</span>,
              ),
            ),
            <span class="hljs-keyword">const</span> SizedBox(height: <span class="hljs-number">10</span>),
            <span class="hljs-keyword">const</span> Text(<span class="hljs-string">'Password'</span>),
            TextFormField(
              controller: passwordController,
              decoration: <span class="hljs-keyword">const</span> InputDecoration(
                border: OutlineInputBorder(),
                hintText: <span class="hljs-string">'Enter your password'</span>,
              ),
              obscureText: <span class="hljs-keyword">false</span>,
            ),
            <span class="hljs-keyword">const</span> SizedBox(height: <span class="hljs-number">10</span>),
            GestureDetector(
              onTap: () {},
              child: <span class="hljs-keyword">const</span> Text(
                <span class="hljs-string">'Forgot password?'</span>,
                style: TextStyle(
                  color: Colors.deepPurple,
                ),
              ),
            ),
            <span class="hljs-keyword">const</span> SizedBox(height: <span class="hljs-number">20</span>),
            BlocConsumer&lt;AuthenticationBloc, AuthenticationState&gt;(
              listener: (context, state) {
                <span class="hljs-keyword">if</span> (state <span class="hljs-keyword">is</span> AuthenticationSuccessState) {
                  Navigator.pushNamedAndRemoveUntil(
                    context,
                    HomeScreen.id,
                    (route) =&gt; <span class="hljs-keyword">false</span>,
                  );
                } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (state <span class="hljs-keyword">is</span> AuthenticationFailureState) {
                  showDialog(
                      context: context,
                      builder: (context) {
                        <span class="hljs-keyword">return</span> <span class="hljs-keyword">const</span> AlertDialog(
                          content: Text(<span class="hljs-string">'error'</span>),
                        );
                      });
                }
              },
              builder: (context, state) {
                <span class="hljs-keyword">return</span> SizedBox(
                  height: <span class="hljs-number">50</span>,
                  width: <span class="hljs-built_in">double</span>.infinity,
                  child: ElevatedButton(
                    onPressed: () {
                      BlocProvider.of&lt;AuthenticationBloc&gt;(context).add(
                        SignUpUser(
                          emailController.text.trim(),
                          passwordController.text.trim(),
                        ),
                      );
                    },
                    child:  Text(
                      state <span class="hljs-keyword">is</span> AuthenticationLoadingState
                            ? <span class="hljs-string">'.......'</span>,
                            : <span class="hljs-string">'Signup'</span>,
                      style: TextStyle(
                        fontSize: <span class="hljs-number">20</span>,
                      ),
                    ),
                  ),
                );
              },
            ),
            <span class="hljs-keyword">const</span> SizedBox(height: <span class="hljs-number">20</span>),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                <span class="hljs-keyword">const</span> Text(<span class="hljs-string">"Already have an account? "</span>),
                GestureDetector(
                  onTap: () {},
                  child: <span class="hljs-keyword">const</span> Text(
                    <span class="hljs-string">'Login'</span>,
                    style: TextStyle(
                      color: Colors.deepPurple,
                    ),
                  ),
                )
              ],
            ),
          ],
        ),
      ),
    );
  }
}
</code></pre>
<p>This code is just a simple Login UI with two <code>textfields</code> and an elevated button. The <code>BlocConsumer</code> widget wraps the <code>Sign up</code> button and listens for changes in the <code>AuthenticationBloc</code> state. When a user presses the button, it dispatches an event to the <code>AuthenticationBloc</code> to initiate the user sign-up process.</p>
<p>Depending on the authentication state, this button may display different feedback or navigate to another screen. It checks for <code>AuthenticationSuccessState</code>, <code>AuthenticationLoadingState</code>, and <code>AuthenticationFailureState</code> states to respond accordingly.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/11/ezgif.com-video-to-gif--1-.gif" alt="Image" width="600" height="400" loading="lazy">
<em>Img 2: A Login screen showing a login process with 2 out of the 3 states.</em></p>
<h3 id="heading-home-screen">Home Screen</h3>
<p>Create another file called <code>home_screen.dart</code> in the <code>screens</code> directory and add the code below to the file.</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter_bloc/flutter_bloc.dart'</span>;

<span class="hljs-keyword">import</span> <span class="hljs-string">'../bloc/authentication_bloc.dart'</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HomeScreen</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-keyword">static</span> <span class="hljs-built_in">String</span> id = <span class="hljs-string">'home_screen'</span>;
  <span class="hljs-keyword">const</span> HomeScreen({<span class="hljs-keyword">super</span>.key});

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            <span class="hljs-keyword">const</span> Text(
              <span class="hljs-string">'Hello User'</span>,
              style: TextStyle(
                fontSize: <span class="hljs-number">20</span>,
              ),
            ),
            <span class="hljs-keyword">const</span> SizedBox(
              height: <span class="hljs-number">20</span>,
            ),
            BlocConsumer&lt;AuthenticationBloc, AuthenticationState&gt;(
              listener: (context, state) {
                <span class="hljs-keyword">if</span> (state <span class="hljs-keyword">is</span> AuthenticationLoadingState) {
                   <span class="hljs-keyword">const</span> CircularProgressIndicator();
                } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (state <span class="hljs-keyword">is</span> AuthenticationFailureState){
                    showDialog(context: context, builder: (context){
                          <span class="hljs-keyword">return</span> <span class="hljs-keyword">const</span> AlertDialog(
                            content: Text(<span class="hljs-string">'error'</span>),
                          );
                        });
                }
              },
              builder: (context, state) {
                <span class="hljs-keyword">return</span> ElevatedButton(
                    onPressed: () {
                      BlocProvider.of&lt;AuthenticationBloc&gt;(context)
                      .add(SignOut());
                    }, child: <span class="hljs-keyword">const</span> Text(
                      <span class="hljs-string">'logOut'</span>
                      ));
              },
            ),
          ],
        ),
      ),
    );
  }
}
</code></pre>
<p>The code above represents the <code>HomeScreen</code> and it's also a simple page that consists of scaffold, a column, and a text widget but the interesting part is the <code>BlocConsumer</code> which is at the elevated button that says logOut. Let's look closely at that.</p>
<p>The <code>BlocConsumer</code> Listens to state changes from the <code>AuthenticationBloc</code>. It has two parameters - listener and builder.</p>
<ul>
<li><strong>listener</strong>: Listens to state changes and reacts based on the current state received from the <code>AuthenticationBloc</code>.</li>
<li>If the state is <code>AuthenticationLoadingState</code>, it shows a <code>CircularProgressIndicator</code>.</li>
<li>If the state is <code>AuthenticationFailureState</code>, it displays an <code>AlertDialog</code> with the message 'Error'.</li>
<li><strong>builder</strong>: Builds the UI based on the current state received from the <code>AuthenticationBloc</code>.</li>
<li>It renders an <code>ElevatedButton</code> labeled "Log Out".</li>
<li>When pressed, it triggers the <code>SignOut</code> event in the <code>AuthenticationBloc</code> via BlocProvider.</li>
</ul>
<p>With the Bloc authentication flow implemented, you can run your Flutter app and test the registration functionalities. Make sure to handle other authentication-related scenarios, such as user Login and password recovery, as required by your app's specifications. Also, you'll want to handle the errors gracefully to give the user a good experience.</p>
<p>If you want to clone the repo, you can check it out on GitHub <a target="_blank" href="https://github.com/emjaycodes/bloc_authentication_flow_article">here</a> and leave a like.</p>
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>In this article, we explored building a user authentication flow in Flutter using Firebase for authentication and the Bloc state management pattern for handling application state.</p>
<p>We learned how to set up Firebase in a Flutter project, create Blocs for authentication, and implement the authentication flow using Bloc.</p>
<p>By leveraging the power of Firebase and the predictability of Bloc, you can ensure a secure and seamless user authentication experience in your Flutter apps.  </p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Host and Deploy a React App with Firebase ]]>
                </title>
                <description>
                    <![CDATA[ By Juliet Ofoegbu As a front-end developer, you may have used a free hosting platform like Vercel, Netlify, or GitHub pages to deploy your front-end projects. Personally, I typically use Vercel and Netlify. But I also like trying out different web te... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-deploy-a-react-app-with-firebase/</link>
                <guid isPermaLink="false">66d45f6333b83c4378a517de</guid>
                
                    <category>
                        <![CDATA[ Firebase ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Tue, 24 Oct 2023 15:55:28 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/10/pexels-pixabay-207241.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Juliet Ofoegbu</p>
<p>As a front-end developer, you may have used a free hosting platform like Vercel, Netlify, or GitHub pages to deploy your front-end projects.</p>
<p>Personally, I typically use Vercel and Netlify. But I also like trying out different web technologies, and I've used Firebase's authentication and storage features on different projects before. So I decided to use Firebase to deploy a React-TypeScript project of mine, and it went really well.</p>
<h2 id="heading-what-is-firebase">What is Firebase?</h2>
<p><a target="_blank" href="https://firebase.google.com/">Firebase</a> is a Backend-as-a-Service (BaaS) platform owned by Google that you can use to perform backend operations like authentication, real-time database functionality, and so on.</p>
<p>Firebase gives frontend developers the ability to work with backend features without needing to actually go deep into backend development.</p>
<p>You can also use Firebase to host and deploy projects. It provides a hosting URL after deployment that you can share with others to view your app on their own device just like other hosting and deployment platforms.</p>
<p>Follow these step-by-step procedures to successfully deploy your React projects using Firebase.</p>
<h2 id="heading-create-your-react-project">Create your React Project</h2>
<p>Depending on the method you prefer to use in creating React projects, go ahead and create one. For example, you can do so using CRA: <code>npx create-react-app app-name</code> or using Vite: <code>npm create vite@latest</code> (recommended).</p>
<p>Use <code>cd app-name</code> to navigate to the project directory. Then <code>npm start</code> or <code>npm run dev</code> to start up your development server. Build your desired project, create a GitHub repo, and push the project to GitHub. </p>
<p>Now we're done with part one of the procedure. On to the next part.</p>
<h2 id="heading-how-to-configure-and-install-firebase">How to Configure and Install Firebase</h2>
<p>If you don't have an account on Firebase, go to this <a target="_blank" href="https://firebase.google.com/">site</a> to create an account on Firebase or log in if you already have one. If you have a Google account, it will be easy to create an account on Firebase. </p>
<p>After you've successfully logged in, you'll need to create a project on Firebase. Here's how to do that:</p>
<h3 id="heading-step-1-firebase-console-dashboard">Step 1: Firebase Console Dashboard.</h3>
<p>Go to your Firebase console dashboard, where you should see the text "Go to console" at the top right-hand side of your page after logging in. </p>
<p>The page that opens up will have a "Create a project" button. Click on that button and it will take you to the page where you'll input your project details (Step 2).</p>
<p>If you've previously used Firebase, that means you already have projects on Firebase. In that case, it will bring up a page like the one below displaying a list of your projects and a box to add a new project.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/10/Firebase-console---Google-Chrome-25_07_2023-19_24_33.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Firebase console</em></p>
<h3 id="heading-step-2-create-a-new-project">Step 2: Create a New Project</h3>
<p>Click the "Add project" card. A page prompting you to give your project a name will open up.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/10/Firebase-console---Google-Chrome-25_07_2023-19_24_55.png" alt="Image" width="600" height="400" loading="lazy">
<em>Creating a project</em></p>
<h3 id="heading-step-3-fill-in-project-details">Step 3: Fill in Project Details</h3>
<p>In this example, I named the project "My React APP". </p>
<p>If you're new to Firebase, you'll need to tick the "Accept Firebase terms" checkbox and the second checkbox.</p>
<p>Click the "continue" button. The next page that comes up has a toggle to enable or disable Google Analytics for the project. Disable this toggle as we don't need Google Analytics for this demo. </p>
<p>Click the "Create Project" button and it should start creating your Firebase project.</p>
<p>If this isn't your first time using Firebase, click on the "Continue" button on the page above, disable Google Analytics, and create a new project.</p>
<h3 id="heading-step-4-install-firebase-and-firebase-tools">Step 4: Install Firebase and Firebase Tools</h3>
<p>The next step is to go to your project terminal on VS code, your Command Line Interface, or any code editor you're using. Ensure you're in the main folder of the project you want to deploy and then install Firebase into the project using this command: <code>npm install firebase</code>. </p>
<p>Next, install the Firebase tools we'll be using for hosting and deployment using this command: <code>npm install -g firebase-tools</code>.</p>
<h3 id="heading-step-5-log-in-to-firebase-using-the-terminal">Step 5: Log in to Firebase Using the Terminal</h3>
<p>After configuring the Firebase project and installing the necessary dependencies, you'll have to log in to Firebase on the terminal using this command: <code>firebase login</code>.</p>
<p>A prompt requiring you to select Yes or No to a question on whether you should "Allow Firebase to collect CLI and Emulator Suite usage and error reporting information" will appear. Select the "Yes" option.</p>
<h3 id="heading-step-6-select-account">Step 6: Select Account</h3>
<p>A window will open up on the default browser that will require you to select your Firebase account for login.</p>
<p>After successful authentication, a success message will appear on the terminal.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/10/firebase-login-on-terminal.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Firebase CLI login</em></p>
<h3 id="heading-step-7-run-project-build">Step 7: Run Project Build</h3>
<p>Use the <code>npm run build</code> command to build the project scripts. This command automatically generates a production-ready build of your application by bundling all the necessary JavaScript, CSS, and other assets into a single folder "build" folder in the project directory.</p>
<p>This process is important as it optimizes the code and assets for performance. This reduces the overall size of the application and makes it efficient for deployment.</p>
<p>After the successful completion of part two, we've come to the next integral part of this whole deployment process.</p>
<h2 id="heading-how-to-initialize-firebase">How to Initialize Firebase</h2>
<p>Now we need to initialize Firebase, so we'll walk through the steps to do that.</p>
<h3 id="heading-step-1-initialize-firebase">Step 1: Initialize Firebase</h3>
<p>Initialize Firebase for this project by using this command on the terminal: <code>firebase init</code>. It will let you know that you're about to initialize a Firebase project in the directory.</p>
<p>Some prompts that will come up after this command are: "Are you ready to proceed?", to which you will type "Y" for "Yes".</p>
<p>The next prompt is: "Which Firebase features do you want to set up for this directory?". Use the arrow down key on your keyboard to point to the "Hosting:Configure files for Firebase Hosting and (optionally) set up GitHub Action deploys" option. Press the space bar and then hit enter.</p>
<h3 id="heading-step-2-project-setup">Step 2: Project Setup</h3>
<p>This step connects your project directory with the Firebase project. When prompted to select a project, choose the "Use an existing project" option.<br>Then when prompted to "select a default Firebase project for the directory", select the particular Firebase project you created in part 1 of this process.</p>
<p>You might see some other project options if you have more than one Firebase project on your Firebase console.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/10/firebase-init.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Connecting project to Firebase.</em></p>
<h3 id="heading-step-3-setup-hosting">Step 3: Setup Hosting</h3>
<p>This process will bring up some prompts you'll have to answer.</p>
<p>The first one is: "What do you want to use as your public directory?", to which you'll choose or type in "build".</p>
<p>Next, when asked if you want to "Configure as a single-page app (rewrite all urls to /index.html)", select the "Y" or "Yes" option.</p>
<p>When asked to "Set up automatic builds and deploys with GitHub?", choose the "Yes" option. Also, when prompted with the "File build/index.html already exists. Overwrite?" question, choose "No".</p>
<h3 id="heading-step-4-authorize-firebase-with-github">Step 4: Authorize Firebase with GitHub</h3>
<p>You will have to authorize Firebase with your GitHub account. A window will open up on your browser that will require you to authorize Firebase into your GitHub, and input your GitHub password. After a successful authentication, you'll get a success message on your terminal with your GitHub username.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/10/firebase-hosting-set-up.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Firebase Hosting setup</em></p>
<p>If you've been following along successfully and gotten to this stage, you've done well so far. Now we're halfway to deploying our project.</p>
<h2 id="heading-how-to-choose-a-github-repository-and-set-up-github-workflow">How to Choose a GitHub Repository and Set Up GitHub Workflow</h2>
<h3 id="heading-step-1-select-github-repo">Step 1: Select GitHub Repo</h3>
<p>First, you'll need to type in the GitHub repository you'd like to use to set up a GitHub workflow for Firebase deployments.</p>
<p>The format to do this is "username/repository". Remember in part 1 of this process, you built a project and pushed it to GitHub. That GitHub repo is what you'll use.</p>
<p>For example, let's say your GitHub username is "CoderDev" and the repository of the project is "Firebase-Deployment". You'll type "CoderDev/Firebase-Deployment" into the terminal. It should look like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/10/firebase-github-4.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<h3 id="heading-step-2-github-secret-token">Step 2: GitHub secret token</h3>
<p>After setting up a GitHub workflow, it will create a service account with Firebase Hosting admin permissions and will upload the service account JSON to GitHub as a secret token.</p>
<p>You can also view this secret token on GitHub. To do this, go to the repository of the project and switch to the "Settings" tab. On the left-hand panel of the settings page, click on the "Secrets and variables" dropdown and select the "Actions" option. It will display your secret token like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/10/firebase-github-secrets-1.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Secret token</em></p>
<h3 id="heading-step-3-set-up-workflow">Step 3: Set up Workflow</h3>
<p>You'll be prompted with the question "Set up the workflow to run a build script before every deploy?". Choose "Yes" for this.</p>
<p>You'd also be asked, "What script should be run before every deploy? (npm ci &amp;&amp; npm run build) npm run build". Type this into the terminal: <code>npm ci &amp;&amp; npm run build</code>. This will create a workflow file in the project directory. </p>
<p>You'll now see the "firebase-hosting-pull-request.yml" file inside a ".github/workflows" folder in your project folder structure.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/10/firebase-github-3.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>First GitHub Workflow File</em></p>
<h3 id="heading-step-4-automatic-deployment">Step 4: Automatic deployment</h3>
<p>Next, you'll be asked if you want to "Set up automatic deployment to your site's live channel when a PR is merged". Select "Yes".</p>
<p>When asked to enter the name of the GitHub branch associated with your site's live channel, type or select "main". This will create another workflow file "firebase-hosting-merge.yml" inside the ".github/workflows" folder.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/10/firebase-setup-auto.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Second GitHub Workflow File</em></p>
<h3 id="heading-step-5-generate-folders">Step 5: Generate Folders</h3>
<p>The two operations performed above will generate two folders in your project directory. One named "firebase.json" is where the configuration information will be written in, and the other named ".firebaserc" is where the project information will be written in.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/10/firebase-2-folders.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Creating Folders</em></p>
<p>That's all you have to do to initialize Firebase in your project.</p>
<p>These processes are a bit lengthy, but the good news is that all that's left to do is deploy to Firebase.</p>
<h2 id="heading-how-to-deploy-to-firebase">How to Deploy to Firebase</h2>
<h3 id="heading-run-the-deployment-command">Run the Deployment Command</h3>
<p>Run the deployment command <code>firebase deploy</code>. Wait for it to deploy. After it is done, a success message will be displayed on your terminal with a hosting URL. That is the project's live link with a "web.app" domain extension.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/10/firebase-deploy--2-.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Firebase Deployment</em></p>
<p>That's it! We're done deploying our React project with Firebase.</p>
<p>Now anytime you add, commit, and push new changes to that GitHub repo, it will automatically build the app and redeploy it to Firebase so that changes will be reflected in the live site.</p>
<p>This automatic build and redeploy is possible because earlier, in the Firebase initialization process, we selected the 'yes' option to set up automatic builds and deploy with GitHub.</p>
<p>We also selected the "Yes" option to set up the workflow to run build script before every deploy and specified the scripts that should be run before every deploy.</p>
<p>To view how this deployment is carried out after every push to the repo, go to that project's GitHub repo. Switch to the "Actions" tab to see how the app is being built and deployed.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/10/fixed-features-page---OmaJuliet_Liberty-Tours@ad63f32---Google-Chrome-27_07_2023-13_13_32.png" alt="Image" width="600" height="400" loading="lazy">
<em>Build and deployment action from GitHub.</em></p>
<p>You might encounter some errors while deploying. The good thing is that right there on that GitHub actions page, you can trace to see where the error is coming from in the app.</p>
<p>Let's say you're working with TypeScript in the project, and you declared a function and didn't use it, or you called a hook and didn't use it. Your app may function as it should on the browser.  </p>
<p>But while deploying, this might be an issue or cause a warning and you'll need to fix it, commit, and push again to the repo to fix the error. Once you've done this and the deployment is successful, the action page should look like this.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/10/fixing-deployment-issue---OmaJuliet_Liberty-Tours@fce163f---Google-Chrome-25_07_2023-22_09_27-1.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Fixing deployment issue</em></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Deploying your project using Firebase might at first seem like a long process. But if you follow the steps in this article, you should be good to go.</p>
<p>You can easily deploy your application to Firebase Hosting and take advantage of its powerful capabilities such as automatic processes, simplified deployment process, and continuous integration with GitHub by following the easy steps in this article.</p>
<p>If you wish to learn more about all the features Firebase has to offer to developers, go to the <a target="_blank" href="https://firebase.google.com/docs">Firebase Official Documentation</a> and explore.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ What is Firebase Remote Config? ]]>
                </title>
                <description>
                    <![CDATA[ Remote configurations are useful because they allow you to alter the behavior in your application without having to release a new version of the app. One prominent example is using remote configurations to decide if a feature should be turned on or o... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/firebase-remote-config/</link>
                <guid isPermaLink="false">66ba4ff0158e6c6a8cb8c79a</guid>
                
                    <category>
                        <![CDATA[ Cloud Services ]]>
                    </category>
                
                    <category>
                        <![CDATA[ configuration ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Firebase ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Tomer ]]>
                </dc:creator>
                <pubDate>Tue, 03 Oct 2023 15:44:31 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/09/rima-kruciene-gpKe3hmIawg-unsplash.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Remote configurations are useful because they allow you to alter the behavior in your application without having to release a new version of the app. One prominent example is using remote configurations to decide if a feature should be turned on or off. That way, you can gradually roll it out to production or test it to see how users react.</p>
<p>If you want your application to have this functionality, you'd usually have to build your server and its logic. But we now live in an age of technological innovation, and tools have been created to help you minimize your development time.</p>
<p>This tool is called Firebase Remote Config — a cloud service that enables you change different functionalities of your app without releasing updates or asking users to update the app.</p>
<h2 id="heading-overview">Overview</h2>
<p>You can access the Remote Config feature in your project’s Firebase console. It is usually under the Release &amp; Monitor section on the left sidebar.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/1-6.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>There are two ways in which you can define your remote configurations:</p>
<ol>
<li>Using Firebase.</li>
<li>Using a template file that is in JSON format.</li>
</ol>
<p>We will focus on the first option, as the second option is a less intuitive approach.</p>
<p>Firebase Remote Config lets you define one or more keys during configuration. Keys can be of the following type:</p>
<ul>
<li>String</li>
<li>Number</li>
<li>Boolean</li>
<li>JSON</li>
</ul>
<p>These keys are used as the configurations for your application. For example, if you have a feature in your application that you would like to control through remote configurations, you  could define a Boolean key titled enableFeatureX.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/1-7.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Each key you set has a few other settings that may be useful to you. For example, you can define a default value for a key (for example, false can be the default value of a Boolean key) or make it use a value that you have defined in your application. </p>
<p>Another cool thing you can do, by clicking on the Add new button in the image above, is to set the value of a key based on certain factors. You'll see these options when you click on the button:</p>
<ul>
<li>Conditional value.</li>
<li>Experiment.</li>
<li>Personalization.</li>
</ul>
<p>Once you are done adding a key, make sure to publish your changes so they will be deployed.</p>
<h2 id="heading-the-conditional-value-option">The Conditional Value Option</h2>
<p>You can configure how a value will be set to specific users based on various conditions.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/1-8.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Here, you can decide on what you want to test and how. You will discover several options when you click on the “Applies if” dropdown. </p>
<p>To illustrate the use of this feature, let’s say that you want to target iOS users in the US. You can do that using the “Applies if” dropdown and choosing Platform and then iOS. </p>
<p>After that, you can press the "and" button to add a condition for Country/Region and choose United States.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/1-9.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Make sure to also name your condition, otherwise, the Create condition button won’t be enabled.</p>
<p>Notice how the last field in defining a new condition window tells you how many users will be affected by this condition? That's a pretty cool feature.</p>
<h2 id="heading-the-experiment-option">The Experiment Option</h2>
<p>This option lets you change the behavior of a value in your remote configurations before taking effect on all your users.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/1-10.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>You can follow these steps to configure the experiment option:</p>
<ul>
<li>In the first step, you have to fill in the name and description of your experiment.</li>
<li>Then, you have choose which application to target and how many users will be affected (percentage-wise) in the second step.</li>
<li>The third step is to set up the metrics to measure this experiment. There are two types — the primary metrics and additional metrics.</li>
<li>Lastly, you can decide on the number of A/B test groups for this experiment.</li>
</ul>
<h2 id="heading-the-personalization-option">The Personalization Option</h2>
<p>Last but not least is the option to tailor a specific value of your remote configurations to a user based on their own behavior.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/1-11.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>You can define values the algorithm may supply to the user based on their behavior. These will be chosen by an objective you define (Step 2). This objective can range from the engagement time of the user to the amount of clicks they perform. In Step 3, you define a condition that will target users so that they will become personalized. Lastly, in Step 4, you add the name and description of this personalization.</p>
<p>Each option has much more to offer than what I have described here, so if you want to learn more, you can use one of the reference links at the bottom. Now that we understand what Remote Config is, let’s see how we can add it to our application.</p>
<h2 id="heading-how-to-setup-firebase-remote-config">How to Setup Firebase Remote Config</h2>
<p>Before you can do anything related to applying remote configurations, you need to make sure you've added Firebase to your Android project. This has been documented <a target="_blank" href="https://firebase.google.com/docs/android/setup?authuser=0">here</a>. </p>
<p>After you have done that, follow these steps:</p>
<h3 id="heading-step-1-add-the-firebase-remote-configuration-library-to-your-project-inside-your-application-buildgradle-file">Step #1 - Add the Firebase remote configuration library to your project inside your application build.gradle file</h3>
<pre><code> implementation <span class="hljs-string">'com.google.firebase:firebase-config-ktx'</span>
</code></pre><p>There is an option to also import the Firebase Analytics module, but it is not required for remote configurations. It is used in other areas of remote configurations, such as defining a condition based on a specific event happening.</p>
<h3 id="heading-step-2-use-the-remoteconfig-object">Step #2 - Use the <code>RemoteConfig</code> Object</h3>
<p>After syncing your project, you can access the <code>RemoteConfig</code> object with this command:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> remoteConfig: FirebaseRemoteConfig = Firebase.remoteConfig
</code></pre>
<h3 id="heading-step-3-define-fetch-interval">Step #3 - Define fetch interval</h3>
<p>You can define how often your remote configurations will be fetched and updated. When you are still developing your application, setting this number to be relatively low is more ideal.</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> remoteConfigSettings = remoteConfigSettings {                             minimumFetchIntervalInSeconds = <span class="hljs-number">2000</span>
}
</code></pre>
<p>If you set the <code>**minimumFetchIntervalInSeconds**</code> to be too low, Firebase will throw a <code>FirebaseRemoteConfigFetchThrottledException</code>, so use a low number only when you are testing things.</p>
<h3 id="heading-step-4-set-the-configuration-for-the-remote-configuration">Step #4 - Set the configuration for the remote configuration</h3>
<p>You can set the remote configuration using the code below:</p>
<pre><code class="lang-kotlin">remoteConfig.setConfigSettingsAsync(remoteConfigSettings)
</code></pre>
<h3 id="heading-step-5-set-default-values">Step #5 - Set default values</h3>
<p>You can have application default values for your remote configurations. These can be created as an XML file inside the XML directory inside the res folder. Here’s what the code looks like:</p>
<pre><code class="lang-kotlin">:remoteConfig.setDefaultsAsync(R.xml.remote_config_defaults)
</code></pre>
<p>This XML file must have an underlying element of a map to wrap all your default values. For example, let’s imagine we have defined a key in remote configurations called <code>my_key</code>, whose value is <code>1</code>. The XML for the default values will look like this:</p>
<pre><code class="lang-xml"><span class="hljs-meta">&lt;?xml version="1.0" encoding="utf-8"?&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">defaultsMap</span>&gt;</span>
   <span class="hljs-tag">&lt;<span class="hljs-name">entry</span>&gt;</span>      
      <span class="hljs-tag">&lt;<span class="hljs-name">key</span>&gt;</span>my_key<span class="hljs-tag">&lt;/<span class="hljs-name">key</span>&gt;</span>     
      <span class="hljs-tag">&lt;<span class="hljs-name">value</span>&gt;</span>1<span class="hljs-tag">&lt;/<span class="hljs-name">value</span>&gt;</span>   
    <span class="hljs-tag">&lt;/<span class="hljs-name">entry</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">defaultsMap</span>&gt;</span>
</code></pre>
<p>Remote configurations need to be fetched and activated. The fetch action fetches and stores your Remote Configurations inside the Remote Config object. The activation part is to make these values available to your application. That’s why there are two API methods:</p>
<ul>
<li><code>fetch</code> (and later use activate)</li>
</ul>
<pre><code class="lang-kotlin">remoteConfig.fetch().addOnCompleteListener { task -&gt;               <span class="hljs-keyword">if</span>                <span class="hljs-keyword">if</span> (task.isSuccessful) {     
              <span class="hljs-comment">//Remote Configurations fetch successfully          </span>
           }         
        }.addOnFailureListener { error -&gt;             
                <span class="hljs-comment">//Remote Configurations fetch failure            </span>
       }
-------------------------
remoteConfig.activate().addOnCompleteListener { task -&gt;  
<span class="hljs-keyword">if</span> (task.isSuccessful) {
        <span class="hljs-comment">//Remote Configurations activation success   </span>
        }  
   }.addOnFailureListener { error -&gt; 
               <span class="hljs-comment">//Remote Configurations activation failure</span>
  }
</code></pre>
<ul>
<li><code>fetchAndActivate</code></li>
</ul>
<pre><code class="lang-kotlin">remoteConfig.fetchAndActivate().addOnCompleteListener { task -&gt;                                <span class="hljs-keyword">if</span> (task.isSuccessful) {     
                <span class="hljs-comment">//Remote Configurations fetched and activated successfully                }        </span>
       }.addOnFailureListener { error -&gt;           
       <span class="hljs-comment">//Remote Configurations fetched and activated failure    </span>
     }
</code></pre>
<h3 id="heading-step-6-access-configurations">Step #6 - Access configurations</h3>
<p>Now that our remote configurations have been fetched and activated, we can access and use them in our application. We can do so by accessing the <code>remoteConfig</code> object and using one of the getter methods per the type of the value we set:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> myRemoteConfigValue: String = remoteConfig.getString(<span class="hljs-string">"my_key"</span>)
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Since your application will rely on remote configurations for its operation (or parts of it), it is crucial to decide how the application will behave if it does not arrive or takes too long to receive a response. </p>
<p>In essence, there are two ways to handle loading the remote configurations:</p>
<ol>
<li>Your application boots up and waits for the remote configurations to be activated.</li>
<li>Your application boots up and does not wait for the remote configuration to be activated. Opting instead to use the remote configurations on the second application run.</li>
</ol>
<p>It's important to understand that there is no option that is preferable over the other. It all depends on what your use case is and how you would like the user's experience to be when using your application. The first option guarantees that once your application is loaded, all the remote configurations that you have defined will be set and the user's experience will be smooth after the initial load time. If you have critical features that rely on the remote configurations, you will have to go with this option.</p>
<p>On the other hand, if your remote configurations concern a specific feature of your application that doesn't necessarily need to happen on the first initial launch, you might consider going for second option. That way, your application does not need to wait for the remote configurations to be received from Firebase and the logic inside your application can happen later.</p>
<p>There are good and bad implications for each of these methods, and it’s up to you to decide which is better suited for your application. If you choose the first option, you may add a loading screen that times out after a certain period. If you choose option two, it is recommended to create a default mechanism for features in your application and how they should work when the configuration has not yet been received.</p>
<p>There is more than we have discussed in this article, and I encourage you to investigate deeper things. I recently used Firebase Remote Configurations in an application I created that helps users schedule appointments.</p>
<p>You can check it out o<a target="_blank" href="https://play.google.com/store/apps/details?id=com.tomerpacific.scheduler">n the Google Play store</a>.</p>
<p>And you can see the source code here:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/TomerPacific/scheduler">https://github.com/TomerPacific/scheduler</a></div>
<p>If you want to read other articles I have written, you can find them below:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/TomerPacific/MediumArticles">https://github.com/TomerPacific/MediumArticles</a></div>
<h2 id="heading-references">References</h2>
<ul>
<li><a target="_blank" href="https://firebase.google.com/docs/remote-config/get-started?platform=android">Getting Started With Firebase For Android</a></li>
<li><a target="_blank" href="https://firebase.google.com/docs/remote-config/use-cases">Remote Config Use Cases</a></li>
<li><a target="_blank" href="https://firebase.google.com/docs/remote-config/loading">Remote Config Loading Strategies</a></li>
<li><a target="_blank" href="https://firebase.google.com/docs/remote-config/personalization">Remote Config Personalization</a></li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Full Stack Web Dev with Next.js & Firebase – Google Drive Clone ]]>
                </title>
                <description>
                    <![CDATA[ Are you ready to take your web development skills to the next level and learn a bit about how platforms like Google Drive work under the hood?  We just published a course on the freeCodeCamp.org YouTube channel that will teach you how to build a Goog... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/full-stack-web-development-by-building-a-google-drive-clone-nextjs-firebase/</link>
                <guid isPermaLink="false">66b2028125ef0bb2c5a51732</guid>
                
                    <category>
                        <![CDATA[ Firebase ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Next.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ youtube ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Beau Carnes ]]>
                </dc:creator>
                <pubDate>Wed, 06 Sep 2023 12:38:00 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/08/drivenext.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Are you ready to take your web development skills to the next level and learn a bit about how platforms like Google Drive work under the hood? </p>
<p>We just published a course on the freeCodeCamp.org YouTube channel that will teach you how to build a Google Drive clone with Next.js, TypeScript, Tailwind CSS, and Firebase 9. The course will give you the hands-on experience to create a powerful full stack application from scratch.</p>
<p>In this comprehensive course developed by the talented Nishant Singh, you will learn how to build a Google Drive clone that covers both the front-end and back-end aspects of web development. The course will guide you through the implementation of essential features such as file and folder uploading, authentication, and much more. By the end of the course, you will have a fully functional Google Drive clone that you can showcase in your portfolio.</p>
<p>The course focuses on several core technologies that are widely used in modern web development. Each of these technologies plays a crucial role in building the Google Drive clone.</p>
<p><strong>Next.js:</strong> Next.js is a popular React framework that enables server-side rendering, automatic code splitting, and easy routing. It's the foundation of our project, allowing us to create dynamic and performant web applications.</p>
<p><strong>TypeScript:</strong> TypeScript adds static typing to JavaScript, making your code more predictable and reducing the chances of runtime errors. By incorporating TypeScript into our project, we ensure a higher level of code quality and maintainability.</p>
<p><strong>Tailwind CSS:</strong> Tailwind CSS is a utility-first CSS framework that simplifies styling by providing a set of pre-designed classes. It allows us to create a responsive and visually appealing user interface with minimal effort.</p>
<p><strong>Firebase 9:</strong> Firebase is a comprehensive platform for building web and mobile applications. In this course, we'll leverage Firebase 9, which offers real-time database capabilities, authentication services, and cloud storage. Firebase simplifies backend development and allows us to focus on creating user-friendly features.</p>
<p>The course is broken up into the following sections:</p>
<ul>
<li>Base Setup</li>
<li>Authentication</li>
<li>Building the Topbar</li>
<li>Upload Files Component I</li>
<li>Initialising Firebase</li>
<li>Upload Files Component ||</li>
<li>Uploading Files to Storage</li>
<li>Displaying Files</li>
<li>Creating Folders</li>
<li>Nested Folders and Files</li>
<li>Adding Google Auth</li>
<li>Sharing Files and Folders using Email</li>
</ul>
<p>So get ready to build your own Google Drive clone and enhance your web development skills! You can watch <a target="_blank" href="https://youtu.be/gwOVynGnDZA">the full course on the freeCodeCamp.org YouTube channel</a> (3-hour watch).</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/gwOVynGnDZA" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Set Up Social Media Web Authentication using Firebase ]]>
                </title>
                <description>
                    <![CDATA[ User authentication is extremely important in the context of web development. The way users log in affects their overall experience and engagement with an application. It also affects how they initially perceive it. Authentication techniques are cont... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/social-media-based-web-authentication-with-firebase/</link>
                <guid isPermaLink="false">66bb891dc32849d18c5cdca9</guid>
                
                    <category>
                        <![CDATA[ authentication ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Firebase ]]>
                    </category>
                
                    <category>
                        <![CDATA[ social media ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web App Security ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ David Jaja ]]>
                </dc:creator>
                <pubDate>Thu, 31 Aug 2023 00:12:32 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/08/Article-Cover--3.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>User authentication is extremely important in the context of web development. The way users log in affects their overall experience and engagement with an application. It also affects how they initially perceive it.</p>
<p>Authentication techniques are continually evolving as social media sites continue to grow in popularity. The ability to log into web apps using social network accounts is a helpful advance in this area.</p>
<p>This article discusses how you can enhance the user login process for web applications by employing social media authentication through Firebase. It goes into the benefits, setup methods, and integration approaches while offering helpful guidelines.</p>
<h2 id="heading-heres-what-well-cover">Here's what we'll cover:</h2>
<ol>
<li><a class="post-section-overview" href="#heading-why-use-social-media-authentication">Why Use Social Media Authentication?</a></li>
<li><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></li>
<li><a class="post-section-overview" href="#heading-what-is-firebase-and-why-use-it-for-authentication">What is Firebase and Why Use it for Authentication?</a></li>
<li><a class="post-section-overview" href="#heading-how-to-set-up-firebase-for-social-media-authentication">How to Set Up Firebase for Social Media Authentication</a></li>
<li><a class="post-section-overview" href="#heading-how-to-set-up-your-react-app">How to Set Up Your React App</a></li>
<li><a class="post-section-overview" href="#heading-how-to-integrate-social-media-authentication-in-your-app">How to Integrate Social Media Authentication in Your App</a></li>
<li><a class="post-section-overview" href="#heading-striking-the-right-balance-offering-both-social-media-and-emailpassword-authentication">Offering Both Social Media and Email/Password Authentication</a></li>
<li><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></li>
</ol>
<h2 id="heading-why-use-social-media-authentication">Why Use Social Media Authentication?</h2>
<p>I'm sure you've grown weary of the usual username-password routine when logging into a new platform. It often entails creating a new password on the spot or resorting to insecure password conventions that could grant unauthorized access to your many accounts.</p>
<p>Fortunately, social media authentication offers some advantages:</p>
<ol>
<li>Effortless User Experience: Social media login choices simplify the registration process, making it convenient for users to initiate their app usage.</li>
<li>Heightened Security: Social media platforms implement strong security measures that can bolster the safety of your app's users.</li>
<li>Elimination of Password Hassles: Through social media authentication, users are relieved from the burden of remembering numerous passwords, reducing the inconvenience of managing credentials.</li>
<li>Reduced Account Abandonment: Social media login prompts users to join and interact with your app, minimizing the chances of them leaving the registration process unfinished.</li>
<li>Access to Trustworthy User Information: Social media platforms provide substantial user information, which can be harnessed to personalize the experience offered by your app.</li>
<li>Streamlined Account Recovery: In instances of forgotten passwords, social media authentication presents a straightforward approach for users to regain entry to their accounts.</li>
</ol>
<p>In summary, social media authentication offers a convenient and secure method for users to join and use your app. It leads to an improved user experience, and decreased account abandonment, and grants you access to valuable user insights.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>This article is intended for those with a solid grasp of the following concepts:</p>
<ul>
<li>HTML, CSS, and JavaScript</li>
<li>React and React Routing</li>
<li>Fundamental familiarity with using Firebase</li>
</ul>
<h2 id="heading-what-is-firebase-and-why-use-it-for-authentication">What is Firebase and Why Use it for Authentication?</h2>
<p>Firebase serves as a comprehensive platform, providing developers with backend services and tools to create web and mobile applications. </p>
<p>One of its key offerings is an authentication service that streamlines the process of integrating authentication features into apps. </p>
<p>With <a target="_blank" href="https://firebase.google.com/">Firebase</a>, implementing authentication becomes more straightforward, thanks to its provision of pre-built user interface components, developer-friendly APIs, and support for various authentication methods.</p>
<h2 id="heading-how-to-set-up-firebase-for-social-media-authentication">How to Set Up Firebase for Social Media Authentication</h2>
<h3 id="heading-step-1-create-a-firebase-project">Step 1: Create a Firebase Project</h3>
<ol>
<li>Go to <a target="_blank" href="https://console.firebase.google.com/">the Firebase Console</a> and sign in with your Google account.</li>
<li>Click the "Add Project" button.</li>
<li>Enter a name for your project and select a location for your data storage.</li>
<li>Click the "Create" button.</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/Screenshot-2023-08-22-082447.png" alt="Image" width="600" height="400" loading="lazy">
<em>Firebase console homepage</em></p>
<h3 id="heading-step-2-register-a-web-app">Step 2: Register a Web app</h3>
<p>This feature enables you to register your web applications to access the features of Firebase via web apps.</p>
<ol>
<li>In the Firebase Console, click the "Web" (&lt;/&gt;) icon.</li>
<li>Click the "Add App" button.</li>
<li>Enter a name for your app and select the "Web" app type.</li>
<li>Click the "Register" button.</li>
</ol>
<p>After you have created a Firebase project and registered a web app, you can start using Firebase for social media authentication.</p>
<h3 id="heading-step-3-discover-social-media-sign-in-methods">Step 3: Discover Social Media Sign-In Methods</h3>
<p>To do this, once your project is created, you'll need to navigate to the "Authentication" section on the left-hand menu.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/2-Auth-side-bar-shown.png" alt="Image" width="600" height="400" loading="lazy">
<em>Showing the Authentication sidebar</em></p>
<p>Under the "Sign-in method" tab, you'll find a list of authentication providers from which you can choose one:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/Screenshot-2023-08-22-082758.png" alt="Image" width="600" height="400" loading="lazy">
<em>Displaying various Authentication methods</em></p>
<h3 id="heading-step-4-configure-social-media-providers">Step 4: Configure Social Media Providers</h3>
<h4 id="heading-how-to-configure-google-auth">How to configure Google auth:</h4>
<p>To configure Google auth, simply add a support email, and you’re all set.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/google-enable.png" alt="Image" width="600" height="400" loading="lazy">
<em>Adding a support mail for google auth</em></p>
<h4 id="heading-how-to-configure-github-auth">How to configure GitHub auth:</h4>
<p>To configure GitHub auth, you'll need a Client ID and Client Secret. To get these, sign in to your <a target="_blank" href="https://github.com/">GitHub account</a> and go to Settings &gt; Developer settings.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/github--settings.png" alt="Image" width="600" height="400" loading="lazy">
<em>Github settings panel</em></p>
<p>Then, navigate to OAuth and create a new OAuth application.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/Setting-up-Github-OAuth.png" alt="Image" width="600" height="400" loading="lazy">
<em>Creating a github OAuth application</em></p>
<p>In order to get the Authorization callback, go back to your Firebase console and copy the URL in the GitHub setup.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/github-callback-url.png" alt="Image" width="600" height="400" loading="lazy">
<em>GitHub callback URL</em></p>
<p>Note: To complete this process, you’d need to have your app already hosted or at least a URL to where your app is going to be hosted.</p>
<p>Next, you’ll be routed to a page where your app has been registered and you have your Client ID and Secret.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/3-Github-Client-ID-and-Secret-generated.png" alt="Image" width="600" height="400" loading="lazy">
<em>GitHub Client ID and secret Generated</em></p>
<p>Copy those details and use them to register GitHub as an auth service on Firebase.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/4-Filling-in-github-details-in-fb.png" alt="Image" width="600" height="400" loading="lazy">
<em>Filling GitHub details on Firebase</em></p>
<h4 id="heading-how-to-configure-twitter-auth">How to configure Twitter auth:</h4>
<p>Similar to Github, start by logging into your Twitter developer account. If you don’t have one, sign up with the <a target="_blank" href="https://developer.twitter.com/en/portal/petition/essential/basic-info">Twitter Developer Portal</a>. It looks something like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/Twitter-Developers-signup.png" alt="Image" width="600" height="400" loading="lazy">
<em>Twitter Developer Signup</em></p>
<p>After filling in the details, you’ll be routed to the homepage.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/5-twitter-dev-homepage.png" alt="Image" width="600" height="400" loading="lazy">
<em>Twitter Dev homepage</em></p>
<p>Click on your default app, and set up user authentication.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/Twitter-user-auth-setup.png" alt="Image" width="600" height="400" loading="lazy">
<em>Twitter OAuth app setup</em></p>
<p>Don’t forget to get the callback URL from Firebase and set the Website URL to the URL where your app is hosted.</p>
<p>After setting it up, navigate to your project’s keys and tokens and generate new ones.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/Screenshot-2023-08-25-164558.png" alt="Image" width="600" height="400" loading="lazy">
<em>Generating new App Key and Secret</em></p>
<p>Paste those details back in Firebase to set up Twitter auth. </p>
<p>And with that, your three social media platforms have be set up for authentication.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/All-auths-setup.png" alt="Image" width="600" height="400" loading="lazy">
<em>All Auth's set up</em></p>
<h2 id="heading-ia"> </h2>
<p>How to Set Up Your React App</p>
<p>Now we need to get your React app set up. You'll start by creating a new React app using <a target="_blank" href="https://vitejs.dev/guide/">Vite</a>.</p>
<p>Create a folder on your computer and open that folder with your preferred IDE. Open that IDE’s terminal and run this command:</p>
<pre><code class="lang-bash">npm create vite@latest
</code></pre>
<p>When the details load, select React and wait for the installation to complete.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/Vite-React.png" alt="Image" width="600" height="400" loading="lazy">
<em>Creating a React App with Vite</em></p>
<p>You’ll be left with a handful of files and some boilerplate code that you can get rid of.</p>
<p>Next, run <code>npm run dev</code> in the terminal to start a development server on port <code>http://localhost:5173/</code>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/Vit-setup.png" alt="Image" width="600" height="400" loading="lazy">
<em>React app running in browser</em></p>
<p>To use Firebase in your app, you must first define a Firebase config file. This file contains all the necessary data used to identify your Firebase app.</p>
<p>So create a folder in your <code>src</code> directory called <code>firebase</code>. Then nest a <code>config.js</code> file in that folder and paste the config file details from your Firebase console you saved earlier.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/6-firebase-config-in-vscode.png" alt="Image" width="600" height="400" loading="lazy">
<em>Firebase config details</em></p>
<p>Finally, install Firebase via your terminal to use its services in your app.</p>
<pre><code class="lang-bash">npm i firebase
</code></pre>
<h2 id="heading-how-to-integrate-social-media-authentication-in-your-app">How to Integrate Social Media Authentication in Your App</h2>
<p>Considering how large this section would be, it’ll be divided into several sub-sections.</p>
<ol>
<li>Setting up the UI logic for authentication</li>
<li>Setting up the Authentication logic</li>
<li>Implementing Global Authentication State</li>
<li>Creating a custom hook for Social Media Authentication</li>
<li>Creating Routes and Implementing Routing</li>
<li>Social Media Authentication</li>
<li>Route Guarding via the User State</li>
<li>Creating a useLogout hook</li>
<li>Testing the logout functionality</li>
</ol>
<h3 id="heading-how-to-set-up-the-ui-logic-for-authentication">How to set up the UI logic for authentication</h3>
<p>Create a folder (pages) in the <code>src</code> directory that houses the pages you want in your application.</p>
<p>For this implementation, there will be 2 files in the pages folder, <code>Auth.jsx</code> and <code>Home.jsx</code>. These files will act as the pages the user can see either when authenticated or not.</p>
<h3 id="heading-how-to-set-up-the-authentication-logic">How to set up the authentication logic</h3>
<p>Start by importing and initializing Firebase auth, as well as the social media platforms enabled on Firebase in your config.</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> {
  getAuth,
  GoogleAuthProvider,
  GithubAuthProvider,
  TwitterAuthProvider,
} <span class="hljs-keyword">from</span> <span class="hljs-string">"firebase/auth"</span>;

<span class="hljs-comment">// Initialize Firebase</span>
<span class="hljs-keyword">const</span> app = initializeApp(firebaseConfig);
<span class="hljs-keyword">const</span> auth = getAuth(app);

<span class="hljs-keyword">const</span> googleProvider = <span class="hljs-keyword">new</span> GoogleAuthProvider();
<span class="hljs-keyword">const</span> githubProvider = <span class="hljs-keyword">new</span> GithubAuthProvider();
<span class="hljs-keyword">const</span> twitterProvider = <span class="hljs-keyword">new</span> TwitterAuthProvider();
</code></pre>
<p>Then export these initialized functions to use them in other parts of your application.</p>
<pre><code class="lang-js"><span class="hljs-keyword">export</span> { auth, googleProvider, githubProvider, twitterProvider };
</code></pre>
<h3 id="heading-how-to-implement-global-authentication-state">How to implement Global Authentication State</h3>
<p>To ensure a consistent authentication state throughout your application, consider using the React Context approach.</p>
<h4 id="heading-step-1-create-an-authcontext">Step 1: Create an AuthContext</h4>
<p>Start by generating a context folder within your src directory and then create an <code>AuthContext.jsx</code> file within it. In the <code>AuthContext</code> file, import essential hooks from React and Firebase.</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { createContext, useReducer, useEffect, useContext } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { auth } <span class="hljs-keyword">from</span> <span class="hljs-string">"../firebase/config"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> AuthContext = createContext();
</code></pre>
<h4 id="heading-step-2-define-a-reducer-function">Step 2: Define a reducer function</h4>
<p>Construct a reducer function to manage state changes for authentication-related actions using the following code:</p>
<pre><code class="lang-js"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> authReducer = <span class="hljs-function">(<span class="hljs-params">state, action</span>) =&gt;</span> {
  <span class="hljs-keyword">switch</span> (action.type) {
    <span class="hljs-comment">// When the action type is "LOGIN", update the state with the new user information</span>
    <span class="hljs-keyword">case</span> <span class="hljs-string">"LOGIN"</span>:
      <span class="hljs-keyword">return</span> { ...state, <span class="hljs-attr">user</span>: action.payload };

    <span class="hljs-comment">// When the action type is "LOGOUT", update the state to remove the user information</span>
    <span class="hljs-keyword">case</span> <span class="hljs-string">"LOGOUT"</span>:
      <span class="hljs-keyword">return</span> { ...state, <span class="hljs-attr">user</span>: <span class="hljs-literal">null</span> };

    <span class="hljs-comment">// When the action type is "AUTH_IS_READY", update the state with user information and</span>
    <span class="hljs-comment">// set a state to indicate that the authentication process is complete</span>
    <span class="hljs-keyword">case</span> <span class="hljs-string">"AUTH_IS_READY"</span>:
      <span class="hljs-keyword">return</span> { <span class="hljs-attr">user</span>: action.payload, <span class="hljs-attr">authIsReady</span>: <span class="hljs-literal">true</span> };

    <span class="hljs-comment">// For any other action type, return the current state without any changes</span>
    <span class="hljs-keyword">default</span>:
      <span class="hljs-keyword">return</span> state;
  }
};
</code></pre>
<h4 id="heading-step-3-create-authcontextprovider-component">Step 3: Create AuthContextProvider Component</h4>
<p>Create a provider component that wraps your entire App component, using the reducer for authentication state management.</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { useEffect, useReducer } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { onAuthStateChanged } <span class="hljs-keyword">from</span> <span class="hljs-string">"firebase/auth"</span>; 


<span class="hljs-comment">// Authentication context provider component</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> AuthContextProvider = <span class="hljs-function">(<span class="hljs-params">{ children }</span>) =&gt;</span> {
  <span class="hljs-comment">// Initialize authentication state using a reducer</span>
  <span class="hljs-keyword">const</span> [state, dispatch] = useReducer(authReducer, {
    <span class="hljs-attr">user</span>: <span class="hljs-literal">null</span>,
    <span class="hljs-attr">authIsReady</span>: <span class="hljs-literal">false</span>,
  });

  <span class="hljs-comment">// Effect to determine initial authentication state and update context</span>
  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-comment">// Subscribe to authentication state changes</span>
    <span class="hljs-keyword">const</span> unsub = onAuthStateChanged(auth, <span class="hljs-function">(<span class="hljs-params">user</span>) =&gt;</span> {
      <span class="hljs-comment">// Dispatch an action to update the state with the user information</span>
      dispatch({ <span class="hljs-attr">type</span>: <span class="hljs-string">"AUTH_IS_READY"</span>, <span class="hljs-attr">payload</span>: user });

      <span class="hljs-comment">// Unsubscribe to avoid further unnecessary updates</span>
      unsub(); <span class="hljs-comment">// Unsubscribe once the initial auth state is determined</span>
    });
  }, []);

  <span class="hljs-comment">// Provide authentication state and dispatch function to children components</span>
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AuthContext.Provider</span> <span class="hljs-attr">value</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">...state</span>, <span class="hljs-attr">dispatch</span> }}&gt;</span>
      {children}
    <span class="hljs-tag">&lt;/<span class="hljs-name">AuthContext.Provider</span>&gt;</span></span>
  );
};
</code></pre>
<h4 id="heading-step-4-implement-useauthcontext-custom-hook">Step 4: Implement useAuthContext custom hook</h4>
<p>You can simplify access to the authentication context with a custom hook, like this:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { useContext } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-comment">// Custom hook to access the authentication context</span>
<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">useAuthContext</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">// Get the authentication context from the nearest AuthContextProvider</span>
  <span class="hljs-keyword">const</span> context = useContext(AuthContext);

  <span class="hljs-comment">// Check if the context was successfully obtained</span>
  <span class="hljs-keyword">if</span> (!context) {
    <span class="hljs-keyword">throw</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"useAuthContext must be used inside an AuthContextProvider"</span>);
  }

  <span class="hljs-comment">// Return the authentication context object for use in components</span>
  <span class="hljs-keyword">return</span> context;
}
</code></pre>
<h4 id="heading-how-to-integrating-the-authcontextprovider">How to integrating the AuthContextProvider</h4>
<p>Finally, integrate the <code>AuthContextProvider</code> into your main application setup</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> ReactDOM <span class="hljs-keyword">from</span> <span class="hljs-string">"react-dom/client"</span>;
<span class="hljs-keyword">import</span> App <span class="hljs-keyword">from</span> <span class="hljs-string">"./App.jsx"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"./index.css"</span>;
<span class="hljs-keyword">import</span> { AuthContextProvider } <span class="hljs-keyword">from</span> <span class="hljs-string">"./context/AuthContext.jsx"</span>;

ReactDOM.createRoot(<span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"root"</span>)).render(
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">React.StrictMode</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">AuthContextProvider</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">App</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">AuthContextProvider</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">React.StrictMode</span>&gt;</span></span>
);
</code></pre>
<p>With that, all parts of your app can access the context values from the <code>AuthContext</code>.</p>
<h3 id="heading-how-to-create-a-custom-hook-for-social-media-authentication">How to create a custom hook for Social Media Authentication</h3>
<p>Firebase authentication processes are similar in pattern and code structure. So it's a good idea to follow the DRY principle and create a utility hook that performs authentication for all social media platforms. This allows you to reuse the same code for each platform, making your code more efficient and easier to maintain.</p>
<p>Here is the step-by-step process to follow to create a custom hook for social media authentication.</p>
<h4 id="heading-step-1-create-the-custom-hook">Step 1: Create the custom hook</h4>
<p>In your source directory, establish a hooks folder and within it, create a file named <code>useSocialSignup.jsx</code>.</p>
<h4 id="heading-step-2-import-dependencies">Step 2: Import dependencies</h4>
<p>Import the necessary functions from React and Firebase into your <code>useSocialSignup</code> file.</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { signInWithPopup } <span class="hljs-keyword">from</span> <span class="hljs-string">"firebase/auth"</span>;
<span class="hljs-keyword">import</span> { auth } <span class="hljs-keyword">from</span> <span class="hljs-string">"../firebase/config"</span>;
<span class="hljs-keyword">import</span> { useAuthContext } <span class="hljs-keyword">from</span> <span class="hljs-string">"../context/AuthContext"</span>;
</code></pre>
<h4 id="heading-step-3-define-the-hook-function">Step 3: Define the hook function</h4>
<p>Develop the <code>useSocialSignup</code> function, which takes a provider as a parameter and returns an object containing an error state, pending state, and the sign-in function for the social provider.</p>
<pre><code class="lang-js"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> useSocialSignup = <span class="hljs-function">(<span class="hljs-params">provider</span>) =&gt;</span> {
  <span class="hljs-comment">// State variables to manage sign-up process</span>
  <span class="hljs-keyword">const</span> [error, setError] = useState(<span class="hljs-literal">null</span>);
  <span class="hljs-keyword">const</span> [isPending, setIsPending] = useState(<span class="hljs-literal">false</span>);
  <span class="hljs-keyword">const</span> [isCancelled, setIsCancelled] = useState(<span class="hljs-literal">false</span>);

  <span class="hljs-comment">// Accessing the authentication context's dispatch function</span>
  <span class="hljs-keyword">const</span> { dispatch } = useAuthContext();

  <span class="hljs-comment">// Function to initiate the social sign-up process</span>
  <span class="hljs-keyword">const</span> signInWithSocial = <span class="hljs-keyword">async</span> () =&gt; {
    setError(<span class="hljs-literal">null</span>);
    setIsPending(<span class="hljs-literal">true</span>);

    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> signInWithPopup(auth, provider);

      dispatch({ <span class="hljs-attr">type</span>: <span class="hljs-string">"LOGIN"</span>, <span class="hljs-attr">payload</span>: res.user });

      <span class="hljs-keyword">if</span> (!isCancelled) {
        setIsPending(<span class="hljs-literal">false</span>);
        setError(<span class="hljs-literal">null</span>);
      }
    } <span class="hljs-keyword">catch</span> (err) {
      setError(err.message);
      setIsPending(<span class="hljs-literal">false</span>);
    }
  };

  <span class="hljs-comment">// Effect hook to set isCancelled to true when component unmounts</span>
  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> setIsCancelled(<span class="hljs-literal">true</span>);
  }, []);

  <span class="hljs-comment">// Return values and functions for component usage</span>
  <span class="hljs-keyword">return</span> { error, isPending, signInWithSocial };
};
</code></pre>
<p>This hook encapsulates the process of signing in with social providers. It manages error, pending, and cancellation states, interacts with Firebase authentication, and utilizes the authentication context to dispatch actions.</p>
<h3 id="heading-how-to-create-routes-and-implementing-routing">How to create routes and implementing routing</h3>
<p>To ensure smooth navigation and user experience, setting up routes becomes crucial after implementing authentication logic. These well-organized steps guide you through the process.</p>
<h4 id="heading-step-1-install-react-router-dom">Step 1: Install react-router-dom</h4>
<p>Install <a target="_blank" href="https://www.npmjs.com/package/react-router-dom">the react-router-dom package</a>, a popular choice for managing routing in React applications.</p>
<pre><code class="lang-bash">npm i react-router-dom
</code></pre>
<h4 id="heading-step-2-import-dependencies-1">Step 2: Import dependencies</h4>
<p>In your <code>App.jsx</code> file, import necessary components and functions for routing.</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { BrowserRouter, Navigate, Route, Routes } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-router-dom"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"./App.css"</span>;
<span class="hljs-keyword">import</span> Home <span class="hljs-keyword">from</span> <span class="hljs-string">"./pages/Home"</span>;
<span class="hljs-keyword">import</span> Auth <span class="hljs-keyword">from</span> <span class="hljs-string">"./pages/Auth"</span>;
</code></pre>
<h4 id="heading-step-3-define-routes">Step 3: Define routes</h4>
<p>Wrap your application content in a BrowserRouter component and use the Routes component to define your routes. Utilize the Route component to map each route path to its corresponding component.</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">BrowserRouter</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Routes</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Route</span> <span class="hljs-attr">path</span>=<span class="hljs-string">"/"</span> <span class="hljs-attr">element</span>=<span class="hljs-string">{</span>&lt;<span class="hljs-attr">Home</span> /&gt;</span>} /&gt;
        <span class="hljs-tag">&lt;<span class="hljs-name">Route</span> <span class="hljs-attr">path</span>=<span class="hljs-string">"/auth"</span> <span class="hljs-attr">element</span>=<span class="hljs-string">{</span>&lt;<span class="hljs-attr">Auth</span> /&gt;</span>} /&gt;
      <span class="hljs-tag">&lt;/<span class="hljs-name">Routes</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">BrowserRouter</span>&gt;</span></span>
  );
}
</code></pre>
<p>At the moment, you can freely navigate between routes, like so:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/Onauth-routing.gif" alt="Image" width="600" height="400" loading="lazy">
<em>Moving between Routes without Auth</em></p>
<h3 id="heading-social-media-authentication">Social Media Authentication</h3>
<p>To ensure your efforts haven't been in vain, head over to the <code>Auth.jsx</code> to implement authentication.</p>
<h4 id="heading-step-1-import-dependencies">Step 1: Import dependencies</h4>
<p>In your <code>Auth.jsx</code> file, start by importing necessary providers, context, and the custom signup hook.</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> {
  googleProvider,
  twitterProvider,
  githubProvider,
} <span class="hljs-keyword">from</span> <span class="hljs-string">"../firebase/config"</span>;
<span class="hljs-keyword">import</span> { useSocialSignup } <span class="hljs-keyword">from</span> <span class="hljs-string">"../hooks/useSocialSignup"</span>;
<span class="hljs-keyword">import</span> {useEffect} <span class="hljs-keyword">from</span> ‘react’

<span class="hljs-keyword">import</span> {useAuthContext} <span class="hljs-keyword">from</span> “../context/AuthContext”
</code></pre>
<h4 id="heading-step-2-create-instances-of-the-hook">Step 2: Create instances of the hook</h4>
<p>Create instances of the <code>useSocialSignup</code> custom hook for each authentication provider.</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> google = useSocialSignup(googleProvider);
<span class="hljs-keyword">const</span> twitter = useSocialSignup(twitterProvider);
<span class="hljs-keyword">const</span> github = useSocialSignup(githubProvider);
</code></pre>
<h4 id="heading-step-3-add-buttons-for-social-sign-up">Step 3: Add buttons for social sign-up</h4>
<p>Create buttons for each social sign-up option (Google, Twitter, GitHub) and attach onClick event handlers to call the <code>signInWithSocial</code> function from the respective hook.</p>
<pre><code class="lang-js"><span class="hljs-keyword">return</span> (
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"utility__page"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Welcome to my Auth Page<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{google.signInWithSocial}</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">src</span>=<span class="hljs-string">{GoogleIcon}</span> <span class="hljs-attr">alt</span>=<span class="hljs-string">""</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">span</span>&gt;</span>Google<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{twitter.signInWithSocial}</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">src</span>=<span class="hljs-string">{TwitterIcon}</span> <span class="hljs-attr">alt</span>=<span class="hljs-string">""</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">span</span>&gt;</span>Twitter<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{github.signInWithSocial}</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">src</span>=<span class="hljs-string">{GithubIcon}</span> <span class="hljs-attr">alt</span>=<span class="hljs-string">""</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">span</span>&gt;</span>GitHub<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
);
</code></pre>
<h4 id="heading-step-4-apply-styling">Step 4: Apply styling</h4>
<p>You can use the provided CSS below to style your components for a clean and organized appearance.</p>
<pre><code class="lang-css">* {
  <span class="hljs-attribute">box-sizing</span>: border-box;
  <span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span>;
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">0</span>;
}

<span class="hljs-selector-tag">html</span> {
  <span class="hljs-attribute">font-size</span>: <span class="hljs-number">62.5%</span>;
  <span class="hljs-attribute">color</span>: <span class="hljs-number">#121212</span>;
}

<span class="hljs-selector-class">.utility__page</span> {
  <span class="hljs-attribute">display</span>: flex;
  <span class="hljs-attribute">width</span>: <span class="hljs-number">100%</span>;
  <span class="hljs-attribute">height</span>: <span class="hljs-number">100vh</span>;
  <span class="hljs-attribute">justify-content</span>: center;
  <span class="hljs-attribute">align-items</span>: center;
  <span class="hljs-attribute">font-family</span>: <span class="hljs-string">"Segoe UI"</span>, Tahoma, Geneva, Verdana, sans-serif;
  <span class="hljs-attribute">row-gap</span>: <span class="hljs-number">2rem</span>;
  <span class="hljs-attribute">flex-direction</span>: column;
  <span class="hljs-attribute">background</span>: <span class="hljs-number">#e2dbd9</span>;
}

<span class="hljs-selector-tag">h1</span> {
  <span class="hljs-attribute">font-size</span>: <span class="hljs-number">5rem</span>;
}

<span class="hljs-selector-tag">button</span> {
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">1rem</span> <span class="hljs-number">4rem</span>;
  <span class="hljs-attribute">font-size</span>: <span class="hljs-number">2rem</span>;
  <span class="hljs-attribute">border</span>: none;
  <span class="hljs-attribute">cursor</span>: pointer;
  <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">5px</span>;
  <span class="hljs-attribute">display</span>: flex;
  <span class="hljs-attribute">justify-content</span>: center;
  <span class="hljs-attribute">align-items</span>: center;
  <span class="hljs-attribute">gap</span>: <span class="hljs-number">1rem</span>;
}

<span class="hljs-selector-tag">button</span> <span class="hljs-selector-tag">img</span> {
  <span class="hljs-attribute">width</span>: <span class="hljs-number">20px</span>;
  <span class="hljs-attribute">height</span>: <span class="hljs-number">20px</span>;
}

<span class="hljs-selector-class">.user</span> {
  <span class="hljs-attribute">font-size</span>: <span class="hljs-number">3rem</span>;
  <span class="hljs-attribute">display</span>: flex;
  <span class="hljs-attribute">align-items</span>: center;
  <span class="hljs-attribute">column-gap</span>: <span class="hljs-number">1rem</span>;
}

<span class="hljs-selector-class">.logout</span> {
  <span class="hljs-attribute">background</span>: <span class="hljs-built_in">rgb</span>(<span class="hljs-number">208</span>, <span class="hljs-number">84</span>, <span class="hljs-number">84</span>);
  <span class="hljs-attribute">color</span>: <span class="hljs-number">#fff</span>;
}

<span class="hljs-selector-class">.profile_img</span> {
  <span class="hljs-attribute">width</span>: <span class="hljs-number">5rem</span>;
  <span class="hljs-attribute">height</span>: <span class="hljs-number">5rem</span>;
  <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">50%</span>;
}
</code></pre>
<p>At the moment, your auth page looks something like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/Auth-page.png" alt="Image" width="600" height="400" loading="lazy">
<em>Auth page after applying styling</em></p>
<h4 id="heading-step-5-test-the-authentication">Step 5: Test the authentication</h4>
<p>To test authentication, import the user from your <code>AuthContext</code> and log it to the console using a <code>useEffect</code>.</p>
<pre><code class="lang-js">  <span class="hljs-keyword">const</span> { user } = useAuthContext();
  useEffect(<span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">console</span>.log(user), [user]);
</code></pre>
<p>Testing the auth now gives the following:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/first-login-giffy.gif" alt="Image" width="600" height="400" loading="lazy">
<em>Authentication confirmed in the console via the user object</em></p>
<p>As you can see, you’ve successfully logged a user in using Social Media Authentication. Kudos!</p>
<p>To confirm, head over to your Firebase auth page and check for valid users.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/Valid-users-check.png" alt="Image" width="600" height="400" loading="lazy">
<em>Confirming signed up user on Firebase</em></p>
<p>Feel free to try other login methods as they all work the same.</p>
<h3 id="heading-route-gaurding-via-the-user-state">Route Gaurding via the User State</h3>
<p>To prevent unauthorized access, set up route guards that check the user's authentication state in your <code>App.jsx</code> file.</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> { user, authIsReady } = useAuthContext();

<span class="hljs-keyword">if</span> (!authIsReady) {
  <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>; <span class="hljs-comment">// Return null while waiting for authIsReady</span>
}

<span class="hljs-keyword">return</span> (
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">BrowserRouter</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Routes</span>&gt;</span>
      {user ? (
        <span class="hljs-tag">&lt;&gt;</span>
          {/* Authenticated routes */}
          <span class="hljs-tag">&lt;<span class="hljs-name">Route</span> <span class="hljs-attr">path</span>=<span class="hljs-string">"/"</span> <span class="hljs-attr">element</span>=<span class="hljs-string">{</span>&lt;<span class="hljs-attr">Home</span> /&gt;</span>} /&gt;
          {/* Route guards */}
          <span class="hljs-tag">&lt;<span class="hljs-name">Route</span> <span class="hljs-attr">path</span>=<span class="hljs-string">"*"</span> <span class="hljs-attr">element</span>=<span class="hljs-string">{</span>&lt;<span class="hljs-attr">Navigate</span> <span class="hljs-attr">to</span>=<span class="hljs-string">"/"</span> /&gt;</span>} /&gt;
        <span class="hljs-tag">&lt;/&gt;</span>
      ) : (
        <span class="hljs-tag">&lt;&gt;</span>
          {/* Authentication routes */}
          <span class="hljs-tag">&lt;<span class="hljs-name">Route</span> <span class="hljs-attr">path</span>=<span class="hljs-string">"/auth"</span> <span class="hljs-attr">element</span>=<span class="hljs-string">{</span>&lt;<span class="hljs-attr">Auth</span> /&gt;</span>} /&gt;
          {/* Route guards */}
          <span class="hljs-tag">&lt;<span class="hljs-name">Route</span> <span class="hljs-attr">path</span>=<span class="hljs-string">"*"</span> <span class="hljs-attr">element</span>=<span class="hljs-string">{</span>&lt;<span class="hljs-attr">Navigate</span> <span class="hljs-attr">to</span>=<span class="hljs-string">"/auth"</span> /&gt;</span>} /&gt;
        <span class="hljs-tag">&lt;/&gt;</span></span>
      )}
    &lt;/Routes&gt;
  &lt;/BrowserRouter&gt;
);
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/plain-home-page-after-auth.png" alt="Image" width="600" height="400" loading="lazy">
<em>Routed to home page after adding route guards</em></p>
<p>As you can see, you’ve been routed to the home page, and even if you attempt to go the auth page, you’d be routed back here.</p>
<h4 id="heading-how-to-customize-the-home-page">How to customize the home page</h4>
<p>For the home page, fetch the user's details and display them if a user is authenticated.</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { useAuthContext } <span class="hljs-keyword">from</span> <span class="hljs-string">"../context/AuthContext"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Home</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> { user } = useAuthContext();
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"utility__page "</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span> Home Page<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
      {user &amp;&amp; (
        <span class="hljs-tag">&lt;&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"user"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span> You<span class="hljs-symbol">&amp;apos;</span>re logged in as: <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">span</span>&gt;</span>{user.displayName} <span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"profile_img"</span> <span class="hljs-attr">src</span>=<span class="hljs-string">{user.photoURL}</span> <span class="hljs-attr">alt</span>=<span class="hljs-string">""</span>/&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
         <span class="hljs-tag">&lt;/&gt;</span></span>
      )}
    &lt;/div&gt;
  );
}
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/Auth-showing-details.png" alt="Image" width="600" height="400" loading="lazy">
<em>Home page showing custom user details</em></p>
<p>And voilà! You’ve been able to fetch some details about that user based on the info on the social media they used to log in.</p>
<h3 id="heading-how-to-create-a-uselogout-hook">How to create a useLogout hook</h3>
<p>The final step to complete your authentication process is to provide users with the ability to log out of your application. Here's how to create a useLogout hook:</p>
<h4 id="heading-step-1-create-the-hook">Step 1: Create the hook</h4>
<p>Create a new file called useLogout.jsx in your hooks folder. Import the necessary hooks and functions.</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { auth } <span class="hljs-keyword">from</span> <span class="hljs-string">"../firebase/config"</span>;
<span class="hljs-keyword">import</span> { signOut } <span class="hljs-keyword">from</span> <span class="hljs-string">"firebase/auth"</span>;
<span class="hljs-keyword">import</span> { useAuthContext } <span class="hljs-keyword">from</span> <span class="hljs-string">"../context/AuthContext"</span>;
</code></pre>
<h4 id="heading-step-2-create-the-hook-states">Step 2: Create the hook states</h4>
<p>Create states to manage the logout process, including error, pending, and cancellation states.</p>
<pre><code class="lang-js"><span class="hljs-comment">// Error state for potential errors during logout </span>
<span class="hljs-keyword">const</span> [error, setError] = useState(<span class="hljs-literal">null</span>); 
<span class="hljs-comment">// State to indicate if logout is in progress </span>
<span class="hljs-keyword">const</span> [isPending, setIsPending] = useState(<span class="hljs-literal">false</span>); 
<span class="hljs-comment">// State to track if the operation is cancelled</span>
<span class="hljs-keyword">const</span> [isCancelled, setIsCancelled] = useState(<span class="hljs-literal">false</span>);
</code></pre>
<h4 id="heading-step-3-extract-the-dispatch-function-from-the-authentication-context">Step 3: Extract the dispatch function from the authentication context</h4>
<p>This function will be used to indicate a logout action has been called:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> { dispatch } = useAuthContext();
</code></pre>
<h4 id="heading-step-4-create-the-hook-logic">Step 4: Create the hook logic</h4>
<p>Use a try-catch block create the logic of logging a user out:</p>
<pre><code class="lang-js"><span class="hljs-keyword">try</span> { 
<span class="hljs-comment">// Initiating the logout using Firebase's signOut function </span>
    <span class="hljs-keyword">await</span> signOut(auth); 
    dispatch({ <span class="hljs-attr">type</span>: <span class="hljs-string">"LOGOUT"</span> }); <span class="hljs-comment">// Dispatching a LOGOUT action </span>
<span class="hljs-comment">// If the operation wasn't cancelled, reset pending state and error </span>
   <span class="hljs-keyword">if</span> (!isCancelled) { 
       setIsPending(<span class="hljs-literal">false</span>); <span class="hljs-comment">// Resetting isPending after the asynchronous call completes </span>
       setError(<span class="hljs-literal">null</span>); <span class="hljs-comment">// Clearing any error that might have occurred </span>
     } 
   } <span class="hljs-keyword">catch</span> (err) { 
      <span class="hljs-comment">// Handling logout error </span>
     <span class="hljs-keyword">if</span> (!isCancelled) { 
        <span class="hljs-built_in">console</span>.log(err.message); <span class="hljs-comment">// Logging the error message</span>
        setError(err.message); <span class="hljs-comment">// Setting the error state in case of an error </span>
        setIsPending(<span class="hljs-literal">false</span>); <span class="hljs-comment">// Resetting pending state if an error occurs </span>
   } 
}
</code></pre>
<h4 id="heading-step-5-how-to-handle-unmounting">Step 5: How to handle unmounting</h4>
<p>In the case where the component is unmounted (the page closes or there’s a route change), you’ll want to handle that occurrence to prevent errors.</p>
<pre><code class="lang-js"><span class="hljs-comment">// Effect hook to set isCancelled to true when component unmounts</span>
   useEffect(<span class="hljs-function">() =&gt;</span> { 
       <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> setIsCancelled(<span class="hljs-literal">true</span>); <span class="hljs-comment">// The cleanup function runs when the component unmounts }, []);</span>
</code></pre>
<h4 id="heading-step-5-exporting-values">Step 5: Exporting values</h4>
<p>Return the relevant values and functions for other components to use.</p>
<pre><code class="lang-js"> <span class="hljs-keyword">return</span> { logout, error, isPending };
</code></pre>
<p>For ease of accesibility, here’s the full useLogout hook.</p>
<pre><code class="lang-js"><span class="hljs-comment">// Importing necessary hooks and functions</span>
<span class="hljs-keyword">import</span> { useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { auth } <span class="hljs-keyword">from</span> <span class="hljs-string">"../firebase/config"</span>; <span class="hljs-comment">// Importing Firebase auth instance</span>
<span class="hljs-keyword">import</span> { signOut } <span class="hljs-keyword">from</span> <span class="hljs-string">"firebase/auth"</span>; <span class="hljs-comment">// Importing signOut function from Firebase</span>
<span class="hljs-keyword">import</span> { useAuthContext } <span class="hljs-keyword">from</span> <span class="hljs-string">"../context/AuthContext"</span>; <span class="hljs-comment">// Importing the custom hook to access the authentication context</span>

<span class="hljs-comment">// Custom hook for handling user logout</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> useLogout = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-comment">// State variables to manage logout process</span>
  <span class="hljs-keyword">const</span> [error, setError] = useState(<span class="hljs-literal">null</span>); <span class="hljs-comment">// Error state for potential errors during logout</span>
  <span class="hljs-keyword">const</span> [isPending, setIsPending] = useState(<span class="hljs-literal">false</span>); <span class="hljs-comment">// State to indicate if logout is in progress</span>
  <span class="hljs-keyword">const</span> [isCancelled, setIsCancelled] = useState(<span class="hljs-literal">false</span>); <span class="hljs-comment">// State to track if the operation is cancelled</span>
  <span class="hljs-keyword">const</span> { dispatch } = useAuthContext(); <span class="hljs-comment">// Accessing the authentication context's dispatch function</span>

  <span class="hljs-comment">// Function to initiate the logout process</span>
  <span class="hljs-keyword">const</span> logout = <span class="hljs-keyword">async</span> () =&gt; {
    setError(<span class="hljs-literal">null</span>); <span class="hljs-comment">// Clearing any previous errors</span>
    setIsPending(<span class="hljs-literal">true</span>); <span class="hljs-comment">// Indicating that the logout process is in progress</span>

    <span class="hljs-keyword">try</span> {
      <span class="hljs-comment">// Initiating the logout using Firebase's signOut function</span>
      <span class="hljs-keyword">await</span> signOut(auth);
      dispatch({ <span class="hljs-attr">type</span>: <span class="hljs-string">"LOGOUT"</span> }); <span class="hljs-comment">// Dispatching a LOGOUT action</span>

      <span class="hljs-comment">// If the operation wasn't cancelled, reset pending state and error</span>
      <span class="hljs-keyword">if</span> (!isCancelled) {
        setIsPending(<span class="hljs-literal">false</span>); <span class="hljs-comment">// Resetting isPending after the asynchronous call completes</span>
        setError(<span class="hljs-literal">null</span>); <span class="hljs-comment">// Clearing any error that might have occurred</span>
      }
    } <span class="hljs-keyword">catch</span> (err) {
      <span class="hljs-comment">// Handling logout error</span>
      <span class="hljs-keyword">if</span> (!isCancelled) {
        <span class="hljs-built_in">console</span>.log(err.message); <span class="hljs-comment">// Logging the error message</span>
        setError(err.message); <span class="hljs-comment">// Setting the error state in case of an error</span>
        setIsPending(<span class="hljs-literal">false</span>); <span class="hljs-comment">// Resetting pending state if an error occurs</span>
      }
    }
  };

  <span class="hljs-comment">// Effect hook to set isCancelled to true when component unmounts</span>
  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> setIsCancelled(<span class="hljs-literal">true</span>); <span class="hljs-comment">// The cleanup function runs when the component unmounts</span>
  }, []);

  <span class="hljs-comment">// Returning the relevant values and functions for component usage</span>
  <span class="hljs-keyword">return</span> { logout, error, isPending };
};
</code></pre>
<h3 id="heading-how-to-test-the-logout-functionality">How to test the logout functionality</h3>
<p>In your Home.jsx component, import the useLogout hook and extract the logout function. Attach the logout function to a button's <code>onClick</code> event to enable users to log out.</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { useLogout } <span class="hljs-keyword">from</span> <span class="hljs-string">"../hooks/useLogout"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Home</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> { user } = useAuthContext();
  <span class="hljs-keyword">const</span> { logout } = useLogout(); <span class="hljs-comment">//logout function extracted</span>

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"utility__page "</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span> Home Page<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
      {user &amp;&amp; (
        <span class="hljs-tag">&lt;&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"user"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span> You<span class="hljs-symbol">&amp;apos;</span>re logged in as: <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">span</span>&gt;</span>{user.displayName} <span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"profile_img"</span> <span class="hljs-attr">src</span>=<span class="hljs-string">{user.photoURL}</span> <span class="hljs-attr">alt</span>=<span class="hljs-string">""</span> /&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
           //logout function used
          <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"logout"</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{logout}</span>&gt;</span>
             Log out
          <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
        <span class="hljs-tag">&lt;/&gt;</span></span>
      )}
    &lt;/div&gt;
  );
}
</code></pre>
<p>At the moment, your home page looks like this;</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/before-logout.png" alt="Image" width="600" height="400" loading="lazy">
<em>Home page before logging the user out</em></p>
<p>Click on the button and log out the user.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/login-out-and-in.gif" alt="Image" width="600" height="400" loading="lazy">
<em>Testing the log in and log out functionality</em></p>
<p>With that, your authentication process is completely set up, congrats!</p>
<h2 id="heading-striking-the-right-balance-offering-both-social-media-and-emailpassword-authentication">Striking the Right Balance: Offering Both Social Media and Email/Password Authentication</h2>
<p>User authentication is a key part of the user experience on any web app. Social media authentication can offer a streamlined experience and enhanced security, but it's important to strike a balance by also offering the option for email/password authentication. This ensures inclusivity, caters to various user preferences, and addresses privacy concerns. </p>
<p>By offering both options, you create a versatile and user-centric authentication process that contributes to a positive user experience.</p>
<p>An example of an ideal authentication page can be seen below.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/Final-signup-page.png" alt="Image" width="600" height="400" loading="lazy">
<em>Standard Auth Page</em></p>
<h2 id="heading-guidelines-for-building-auth-pages">Guidelines for Building Auth Pages</h2>
<p>It is important to apply some basics best practices when building authentication pages, such as:</p>
<ol>
<li>Showing all the possible ways a user can get authenticated in a clear and concise manner.</li>
<li>Using authentic company icons to build trust. You can find free company SVGs on sites like <a target="_blank" href="https://fontawesome.com/">Font Awesome</a>, <a target="_blank" href="https://fonts.google.com/icons">Google icons</a>, and so on.</li>
<li>Use intuitive icons to label inputs such as envelope for mail and padlock for password.</li>
<li>Address privacy concerns by clearly communicating how user data will be used and protected during the authentication process.</li>
</ol>
<p>For ease of accessibility, here’s a link to the <a target="_blank" href="https://github.com/Daiveedjay/OAuth-Article">repo</a>.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In conclusion, using social media login with Firebase is a smart strategy. It brings together user-friendliness, safety, and privacy. </p>
<p>By offering various ways to log in, websites can accommodate different user choices, be more inclusive, and adapt to new trends. </p>
<p>Balancing authentication options like this makes users happy and builds trust. This is important for creating modern websites and ensuring smooth, user-focused logins.</p>
<h3 id="heading-contact-information">Contact Information</h3>
<p>Want to connect or contact me? Feel free to hit me up on the following:</p>
<ul>
<li>Twitter / X : <a target="_blank" href="https://twitter.com/JajaDavid8">@jajadavid8</a></li>
<li>LinkedIn: <a target="_blank" href="https://www.linkedin.com/in/david-jaja-8084251b4/">David Jaja</a></li>
<li>Email: Jajadavidjid@gmail.com</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Add Real-time Post Notifications to Your React Applications ]]>
                </title>
                <description>
                    <![CDATA[ By Nishant Kumar Recently, I was working on an application.  That application was a Thread Clone, the newly launched social media platform. The tech stack I was using was React for the front end and Firebase for authentication, real-time database, an... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-add-post-notifications-to-your-react-applications/</link>
                <guid isPermaLink="false">66d4605c3a8352b6c5a2aab8</guid>
                
                    <category>
                        <![CDATA[ Firebase ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Wed, 26 Jul 2023 14:22:14 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/07/Depth-First-Search--1-.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Nishant Kumar</p>
<p>Recently, I was working on an application.  That application was a Thread Clone, the newly launched social media platform.</p>
<p>The tech stack I was using was React for the front end and Firebase for authentication, real-time database, and for file uploads.</p>
<p>As I was building the app, I thought wouldn’t it be cool if I add a Real-time notifications feature in the app, that updates the user when someone likes or comments on your thread? </p>
<p>And so I started writing the code.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>You must know React and Firebase in order to proceed with the things explained below. </p>
<p>However, this can also be implemented in different databases like SQL or NoSQL.</p>
<h2 id="heading-how-to-set-up-the-project">How to Set Up the Project</h2>
<p>Before implementing this notification feature, we need a few things ready. </p>
<p>Since this is a social media application like Facebook, Twitter, or Linkedin, we need a few parameters ready.</p>
<p>Let’s talk about those parameters now.</p>
<p>Take this payload as an example:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> notificationData = {
   <span class="hljs-attr">userName</span>: auth.currentUser.displayName,
   <span class="hljs-attr">recipientUserId</span>: recipientUserId,
   <span class="hljs-attr">senderUserEmail</span>: auth.currentUser.email,
   <span class="hljs-attr">senderUserId</span>: auth.currentUser.uid,
   <span class="hljs-attr">type</span>: <span class="hljs-string">"like"</span>,
   <span class="hljs-attr">threadID</span>: threadID,
   <span class="hljs-attr">threadData</span>: threadData,
   <span class="hljs-attr">timestamp</span>: moment().format(),
   <span class="hljs-attr">isRead</span>: <span class="hljs-literal">false</span>,
};
</code></pre>
<p>We have an object called <code>notificationData</code>. </p>
<p>In that object, we have parameters like <code>username</code>, <code>recipientUserId</code>, <code>senderUserEmail</code>, <code>senderUserId</code>, and more. </p>
<p>Let me explain these:</p>
<ul>
<li><code>userName</code>: The Current Username of the person who has logged in.</li>
<li><code>recipientUserId</code>: The ID of the person who will receive notifications.</li>
<li><code>senderUserEmail</code>: The Current Username of the person who has logged in.</li>
<li><code>senderUserId</code>: The Current UserID of the person who has logged in.</li>
<li><code>type: "like"</code> : The type of Notification. It can be a like or a comment.</li>
<li><code>threadID</code>: The ID of the Thread, or a Post.</li>
<li><code>threadData</code>: The contents of the Thread, or a Post.</li>
<li><code>timestamp</code>: The current timestamp.</li>
<li><code>isRead: false</code>: The status of the notification, if it has been read or not.</li>
</ul>
<p>The current <code>userName</code>, <code>senderUserEmail</code>, and <code>senderUserId</code> belong to the current user who has logged in. </p>
<p>We need these inputs in order the show who interacted with your thread. </p>
<p>If I have logged in, this data should be mine. We are getting these params from the auth from Firebase Auth.</p>
<p>You need the <code>recipientUserId</code> to notify the user that someone has liked or commented on your post. </p>
<p>If I am liking a thread or adding a comment on one, we need the ID of the user who posted the thread so we can filter through the notifications when we have to display the data.</p>
<p>We also have <code>isRead</code>, which is a boolean value to check if our notification has been read or not. </p>
<p>If we click a notification, we can mark it as read, just by changing the <code>isRead</code> to true.</p>
<p>The rest of the parameters are <code>threadID</code>, which is the ID of the thread and <code>threadData</code> is the contents of the thread.</p>
<p>Now, how to get these inputs is up to you. If you want to build a social media application, simply refer to the videos below.</p>
<h2 id="heading-how-to-add-a-notification-collection-in-firebase">How to Add a Notification Collection in Firebase</h2>
<p>First of all, we need to create a reference to Firebase. </p>
<p>Let’s create it:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">let</span> notificationCollection = collection(database, <span class="hljs-string">"notification"</span>);
</code></pre>
<p>Now, we must have a function for handling likes. If that function runs, we send a like to a respective thread. </p>
<p>Take the below function as an example:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> likeThread = <span class="hljs-function">(<span class="hljs-params">
  userId,
  recipientUserId,
  threadData,
  threadID,
  liked
</span>) =&gt;</span> {
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">let</span> docToLike = doc(likeRef, <span class="hljs-string">`<span class="hljs-subst">${userId}</span>_<span class="hljs-subst">${threadID}</span>`</span>);
    <span class="hljs-keyword">let</span> docToNotify = doc(
      notificationCollection,
      <span class="hljs-string">`<span class="hljs-subst">${recipientUserId}</span>_<span class="hljs-subst">${threadID}</span>`</span>
    );

    <span class="hljs-keyword">if</span> (liked) {
      deleteDoc(docToLike);
      deleteDoc(docToNotify);
    } <span class="hljs-keyword">else</span> {
      setDoc(docToLike, { userId, threadID });

      <span class="hljs-keyword">if</span> (userId !== recipientUserId) {
        <span class="hljs-keyword">const</span> notificationData = {
          <span class="hljs-attr">userName</span>: auth.currentUser.displayName,
          <span class="hljs-attr">recipientUserId</span>: recipientUserId,
          <span class="hljs-attr">senderUserEmail</span>: auth.currentUser.email,
          <span class="hljs-attr">senderUserId</span>: auth.currentUser.uid,
          <span class="hljs-attr">type</span>: <span class="hljs-string">"like"</span>,
          <span class="hljs-attr">threadID</span>: threadID,
          <span class="hljs-attr">threadData</span>: threadData,
          <span class="hljs-attr">timestamp</span>: moment().format(),
          <span class="hljs-attr">isRead</span>: <span class="hljs-literal">false</span>,
        };
        setDoc(docToNotify, notificationData);
      }
    }
  } <span class="hljs-keyword">catch</span> (err) {
    <span class="hljs-built_in">console</span>.log(err, <span class="hljs-string">"error"</span>);
  }
};
</code></pre>
<p>We have a <code>likeThread</code> function that takes some of the parameters I previously mentioned. We have the <code>userId</code> here as well, which denotes the ID of the current user.</p>
<p>We also have a <code>liked</code> property, which is a way to check for likes on a thread. When we like it, it will become true, otherwise it will be false.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">let</span> docToNotify = doc(
  notificationCollection, <span class="hljs-string">`<span class="hljs-subst">${recipientUserId}</span>_<span class="hljs-subst">${threadID}</span>`</span>
);
</code></pre>
<p>We have this <code>doc</code> function from Firebase Firestore, that combines the <code>recipientUserId</code> with the <code>threadID</code> as a unique string and that will be the ID of the notification for a Thread in the Firestore Database.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">if</span> (liked) {
      deleteDoc(docToLike);
      deleteDoc(docToNotify);
    } <span class="hljs-keyword">else</span> {
      setDoc(docToLike, { userId, threadID });

      <span class="hljs-keyword">if</span> (userId !== recipientUserId) {
        <span class="hljs-keyword">const</span> notificationData = {
          <span class="hljs-attr">userName</span>: auth.currentUser.displayName,
          <span class="hljs-attr">recipientUserId</span>: recipientUserId,
          <span class="hljs-attr">senderUserEmail</span>: auth.currentUser.email,
          <span class="hljs-attr">senderUserId</span>: auth.currentUser.uid,
          <span class="hljs-attr">type</span>: <span class="hljs-string">"like"</span>,
          <span class="hljs-attr">threadID</span>: threadID,
          <span class="hljs-attr">threadData</span>: threadData,
          <span class="hljs-attr">timestamp</span>: moment().format(),
          <span class="hljs-attr">isRead</span>: <span class="hljs-literal">false</span>,
        };
        setDoc(docToNotify, notificationData);
      }
    }
</code></pre>
<p>We have two if statements in the code block. </p>
<p>The first one is that if the thread is already liked and we unlike it, we will delete the notification document for that thread from the database using <code>deleteDoc</code> while passing the collection reference, which is <code>docToNotify</code><strong>.</strong></p>
<p>The second one checks if the <code>userId</code><strong>,</strong> which is our own ID, is not equal to the <code>recipientUserId</code> from the thread. </p>
<p>It checks if we like or add a comment on our own post. In this scenario, we cannot send a notification to ourselves. </p>
<p>But keep in mind that the <code>addDoc</code> function that sends likes will be outside the second <code>if statement</code>. This is because we can like our own threads, but cannot get notifications.</p>
<pre><code class="lang-javascript">setDoc(docToNotify, notificationData);
</code></pre>
<p>Then we add these data to Firebase Firestore using the <code>setDoc</code> function with the parameters <code>docToNotify</code>, which notifies users, and the payload which is <code>notificationData</code><strong>.</strong></p>
<p>As for the comments, we can do the same thing we did for likes. </p>
<p>The only difference will be the type key is a comment if we are sending a notification for comments.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> postReplies = <span class="hljs-keyword">async</span> (
  recipientUserId,
  threadData,
  userId,
  threadID,
  reply,
  timeStamp,
  currentUserName
) =&gt; {
  <span class="hljs-keyword">try</span> {
    addDoc(repliesRef, {
      threadID,
      reply,
      timeStamp,
      <span class="hljs-attr">name</span>: currentUserName,
    });

    <span class="hljs-keyword">if</span> (userId != recipientUserId) {
      <span class="hljs-keyword">const</span> notificationData = {
        <span class="hljs-attr">userName</span>: auth.currentUser.displayName,
        <span class="hljs-attr">recipientUserId</span>: recipientUserId,
        <span class="hljs-attr">senderUserEmail</span>: auth.currentUser.email,
        <span class="hljs-attr">senderUserId</span>: auth.currentUser.uid,
        <span class="hljs-attr">type</span>: <span class="hljs-string">"comment"</span>,
        <span class="hljs-attr">threadID</span>: threadID,
        <span class="hljs-attr">threadData</span>: threadData,
        <span class="hljs-attr">timestamp</span>: moment().format(),
        <span class="hljs-attr">isRead</span>: <span class="hljs-literal">false</span>,
      };

      addDoc(notificationCollection, notificationData);
    }
  } <span class="hljs-keyword">catch</span> (err) {
    <span class="hljs-built_in">console</span>.log(err);
  }
};
</code></pre>
<h2 id="heading-how-to-get-notifications-for-a-particular-user">How to Get Notifications for a Particular User</h2>
<p>To get notifications for a particular user, we need the <code>userId</code>, which is the current ID of the user who is logged in.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getNotifications = <span class="hljs-keyword">async</span> (userId) =&gt; {
  <span class="hljs-keyword">const</span> getNotifQuery = query(
    notificationCollection,
    where(<span class="hljs-string">"recipientUserId"</span>, <span class="hljs-string">"=="</span>, userId),
    orderBy(<span class="hljs-string">"timestamp"</span>, <span class="hljs-string">"desc"</span>)
  );
  onSnapshot(getNotifQuery, <span class="hljs-function">(<span class="hljs-params">response</span>) =&gt;</span> {
    <span class="hljs-built_in">console</span>.log(
      response.docs.map(<span class="hljs-function">(<span class="hljs-params">doc</span>) =&gt;</span> {
        <span class="hljs-keyword">return</span> { ...doc.data(), <span class="hljs-attr">id</span>: doc.id };
      })
    );
  });
};
</code></pre>
<p>We need to create a query to check if the <code>recipientUserId</code> is equal to the <code>userId</code>.</p>
<p>This means the thread is our own, and we should receive a notification for that thread if anyone interacts with it by liking or commenting on it. </p>
<p>We also have <code>orderBy</code> to order the notifications in descending order. This will give us all the notifications for a current user who has logged in.</p>
<h2 id="heading-how-to-display-notifications-in-the-ui">How to Display Notifications in the UI</h2>
<p>Displaying notifications in the interface is pretty simple:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> useFetchNotifications <span class="hljs-keyword">from</span> <span class="hljs-string">"../Hooks/useNotifications"</span>;
<span class="hljs-keyword">import</span> { useLocation } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-router-dom"</span>;
<span class="hljs-keyword">import</span> Notifications <span class="hljs-keyword">from</span> <span class="hljs-string">"../Components/Notifications"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">NotificationsPage</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">let</span> { notifications } = useFetchNotifications();
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">ul</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"notification-ul"</span>&gt;</span>
        {notifications.map((notification) =&gt; (
          <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{notification.id}</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">Notifications</span> <span class="hljs-attr">notification</span>=<span class="hljs-string">{notification}</span> /&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        ))}
      <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}
</code></pre>
<p>We can get notification data. </p>
<p>Here, I have a custom React hook called <code>useFetchNotifications()</code><strong>,</strong> from which I am destructuring the notifications array.</p>
<p>Then we map the notifications using the map function.</p>
<p>Our notification page will be like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/07/Screenshot-2023-07-23-at-9.29.07-PM.png" alt="Image" width="600" height="400" loading="lazy">
<em>Notification Page</em></p>
<p>You can also design the way you want to add user profile images for the user who liked or commented on your thread.</p>
<p>When we click a notification, we have to make it invisible or inactive. This will mean that we have read it.</p>
<p>Let’s use a function for this operation:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> readNotifications = <span class="hljs-keyword">async</span> (id) =&gt; {
  <span class="hljs-keyword">let</span> docToUpdate = doc(notificationCollection, id);

  updateDoc(docToUpdate, { <span class="hljs-attr">isRead</span>: <span class="hljs-literal">true</span> });
};
</code></pre>
<p>This function will take the id of the notification and update that particular notification’s <code>isRead</code> property to true.</p>
<p>When we click the notification, it will disappear.</p>
<h2 id="heading-how-to-show-the-number-of-notifications">How to Show the Number of Notifications</h2>
<p>We can also show the number of notifications in the bottom menu bar:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/07/Screenshot-2023-07-23-at-9.06.55-PM.png" alt="Image" width="600" height="400" loading="lazy">
<em>The Footer bar</em></p>
<p>To implement this notification number functionality, we need to filter through the notifications array and find out if the notification <code>isRead</code> property is false.</p>
<p> If it is false, it means it has not been read yet. If it has not been read yet, it means we can show its count:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">let</span> isRead = notifications
    .filter(<span class="hljs-function">(<span class="hljs-params">item</span>) =&gt;</span> item.isRead === <span class="hljs-literal">false</span>)
    .map(<span class="hljs-function">(<span class="hljs-params">notif</span>) =&gt;</span> notif.isRead);
</code></pre>
<p>Let’s have a count badge adjacent to the notification icon. </p>
<p>Here, we should find the length of the <code>isRead</code> array to get the total count of  notifications:</p>
<pre><code class="lang-jsx">&lt;div className=<span class="hljs-string">"active-notifications"</span>&gt;{isRead.length}&lt;/div&gt;
</code></pre>
<p>We will only show the count if the length is more than zero. </p>
<p>In that case, we can have a condition that checks for the length of the <code>isRead</code> array length:</p>
<pre><code class="lang-jsx">{isRead.length ? (
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"active-notifications"</span>&gt;</span>{isRead.length}<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
) : (
  <span class="xml"><span class="hljs-tag">&lt;&gt;</span><span class="hljs-tag">&lt;/&gt;</span></span>
)}
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p>This is how we handle a notification system for a Thread application or any social media application.</p>
<p><a target="_blank" href="https://youtu.be/03OvR8I3EXg">Here is a video version of the article</a> if you prefer video format.</p>
<p>You can also learn how to build a <a target="_blank" href="https://youtu.be/_itNFs2cUnY">Threads clone</a> and a <a target="_blank" href="https://youtube.com/playlist?list=PLWgH1O_994O-vRmOAKtq8VIM6XIC6xwkb">LinkedIn clone</a> using React and Firebase.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Set Up Firebase Cloud Messaging in Flutter Using Firebase ]]>
                </title>
                <description>
                    <![CDATA[ In today's highly competitive mobile app landscape, effectively engaging your app's users and delivering timely information is key.  Firebase Cloud Messaging (FCM) is a powerful push notification service provided by Firebase. It offers a seamless way... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/set-up-firebase-cloud-messaging-in-flutter/</link>
                <guid isPermaLink="false">66ba10f07282cc17abcf0c68</guid>
                
                    <category>
                        <![CDATA[ Android ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Firebase ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Flutter ]]>
                    </category>
                
                    <category>
                        <![CDATA[ mobile app development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Arunachalam B ]]>
                </dc:creator>
                <pubDate>Wed, 05 Jul 2023 16:38:21 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/07/A-Complete-Guide-to-FCM-Integration-in-Flutter-Using-Firebase.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In today's highly competitive mobile app landscape, effectively engaging your app's users and delivering timely information is key. </p>
<p>Firebase Cloud Messaging (FCM) is a powerful push notification service provided by Firebase. It offers a seamless way to connect with your app's users and keep them engaged. </p>
<p>In this tutorial, we will delve into the integration of FCM in Flutter. We'll explore its benefits and showcase real-world examples of how it can enhance user engagement and improve app performance. </p>
<h2 id="heading-what-is-firebase-cloud-messaging">What is Firebase Cloud Messaging?</h2>
<p>Firebase Cloud Messaging (FCM) provides a reliable and battery-efficient connection between your server and devices. It allows you to deliver and receive messages and notifications on iOS, Android, and the web at no cost. </p>
<p>In this tutorial, we will explore the process of setting up and using Firebase Cloud Messaging (FCM) in Flutter using Firebase as the backend service. While the main focus will be on Android implementation, it's worth noting that the process is similar for iOS and Android (with a few configuration differences).</p>
<p>Here is what we'll cover:</p>
<ol>
<li>How to create an app in Firebase</li>
<li>How to set up Firebase in Flutter</li>
<li>How to implement push notifications using FCM tokens</li>
</ol>
<p>In this tutorial, you'll learn how to send a simple notification using Firebase to the app running in Flutter. Let's get started.</p>
<h2 id="heading-how-to-create-an-app-in-firebase">How to Create an App in Firebase</h2>
<p>I'll create a new project in the Firebase console to get started. I'll walk through the necessary steps, including project setup, how to configure Firebase Cloud Messaging, and how to get the required credentials and configuration files for our Flutter app.</p>
<p>Before creating the app you need to signup for the Firebase <a target="_blank" href="https://console.firebase.google.com/">console</a> if you don't have an account. After sign up, try to create a project.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-292.png" alt="Image" width="600" height="400" loading="lazy">
<em>Create a Project in Firebase</em></p>
<p>It will take a little time to create a project.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-293.png" alt="Image" width="600" height="400" loading="lazy">
<em>Creating project in Firebase</em></p>
<p>After creating the project, it will redirect you to the project dashboard.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/07/image-2.png" alt="Image" width="600" height="400" loading="lazy">
<em>Project Overview in Firebase Console</em></p>
<p>Once you've created the project in Firebase console, it's time to get started with our Flutter app.</p>
<h2 id="heading-how-to-set-up-firebase-in-flutter">How to Set Up Firebase in Flutter</h2>
<p>I have created a simple Flutter project using Visual Studio Code. If you are unfamiliar with building a Flutter project, you can refer to my <a target="_blank" href="https://www.freecodecamp.org/news/how-to-build-a-simple-login-app-with-flutter/">previous tutorial</a>. (If you are already familiar, you can skip this step.)</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-296.png" alt="Image" width="600" height="400" loading="lazy">
<em>Simple Flutter Application running on Android Device</em></p>
<p>Let's integrate Firebase into our Flutter project. To do this, we need a Firebase CLI command line tool. I have already installed the Firebase CLI. If you haven't done this, you can refer to the official <a target="_blank" href="https://firebase.google.com/docs/cli#setup_update_cli">documentation</a>.</p>
<p>Then we need to log in to Firebase using Firebase CLI.</p>
<pre><code>firebase login
</code></pre><p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-305.png" alt="Image" width="600" height="400" loading="lazy">
<em>Login to Firebase using FirebaseCLI</em></p>
<p>This will navigate you to the browser to log in to Firebase. You'll be navigated back once the authentication is successfully completed.</p>
<p>After successful login, we need to install FlutterFire CLI. We can use the FlutterFire CLI to configure our Flutter apps to connect to Firebase. Run the following command to activate the FlutterFire CLI:</p>
<pre><code>dart pub <span class="hljs-built_in">global</span> activate flutterfire_cli
</code></pre><p>The FlutterFire CLI is a command-line interface tool that simplifies the integration of Firebase services into Flutter applications. It provides a convenient way to add, configure, and manage Firebase plugins in our Flutter project.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-306.png" alt="Image" width="600" height="400" loading="lazy">
<em>Installing FlutterFireCLI</em></p>
<p>The next step is to add <code>firebase_core</code> library to our Flutter project.</p>
<p>The following command will automatically add the <code>firebase_core</code> package as a dependency in your project's <code>pubspec.yaml</code> file and fetch the latest version of the package from <code>pub.dev</code>. After running this command, you can import the <code>firebase_core</code> package into the Dart files and use Firebase services in our Flutter app.</p>
<pre><code>flutter pub add firebase_core
</code></pre><p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-307.png" alt="Image" width="600" height="400" loading="lazy">
<em>Installing Firebase Core package</em></p>
<p>The <code>flutterfire configure</code> command is used to configure Firebase services in our Flutter project using the FlutterFire CLI. This command helps us set up Firebase authentication, Firestore, Cloud Messaging, and other Firebase services easily and efficiently.</p>
<pre><code>flutterfire configure
</code></pre><p>The first step is to choose the project,</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-308.png" alt="Image" width="600" height="400" loading="lazy">
<em>Connect Flutter App with Firebase app</em></p>
<p>The next is to choose the platform. I am using it for Android here, so I choose Android.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-309.png" alt="Image" width="600" height="400" loading="lazy">
<em>Choosing platform</em></p>
<p>After the successful configuration, the Firebase App Id will be displayed.</p>
<p>Finally, we need to add some code changes to our <code>main.dart</code> file.</p>
<p>Import the following packages:</p>
<pre><code><span class="hljs-keyword">import</span> <span class="hljs-string">'package:firebase_core/firebase_core.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'firebase_options.dart'</span>;
</code></pre><p>Add the following configuration to initialize the Firebase config inside the main function of the <code>main.dart</code> file.</p>
<pre><code><span class="hljs-keyword">await</span> Firebase.initializeApp(
  options: DefaultFirebaseOptions.currentPlatform,
);
</code></pre><p>Alright, we have successfully completed the Firebase configuration in our Flutter app! Let's take a moment to celebrate this milestone. Configuring Firebase services is a crucial step in building powerful and feature-rich applications.</p>
<h2 id="heading-how-to-implement-push-notification-using-fcm-tokens">How to Implement Push Notification using FCM Tokens</h2>
<p>We'll implement the process of registering devices for push notifications and retrieving the unique FCM tokens assigned to each device. This step is crucial for sending targeted notifications to specific devices.</p>
<p>We'll dive into the implementation of sending push notifications to devices using Firebase Cloud Messaging. We'll explore how to structure and send notification messages from the Firebase console and demonstrate how to handle these messages within our Flutter app.</p>
<pre><code>flutter pub add firebase_messaging
</code></pre><p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-310.png" alt="Image" width="600" height="400" loading="lazy">
<em>Installing firebse messaging Package</em></p>
<p>Next, we need to trigger the <code>setAutoInitEnabled</code> function to enable automatic initialization of Firebase Cloud Messaging (FCM) in our Flutter app. This means that FCM will automatically initialize and retrieve a device token when the app starts. </p>
<p>Add the following function call in the <code>main</code> method:</p>
<pre><code><span class="hljs-keyword">import</span> <span class="hljs-string">'package:firebase_messaging/firebase_messaging.dart'</span>;
...
...
await FirebaseMessaging.instance.setAutoInitEnabled(<span class="hljs-literal">true</span>);
</code></pre><p>Let's run our Flutter app and verify if we receive the notification.</p>
<p>Navigate to the Firebase <a target="_blank" href="https://console.firebase.google.com/project/_/messaging/?_gl=1*gqfrc0*_ga*NDUwNTM5NDI0LjE2ODgwNTc3NjQ.*_ga_CW55HF8NVT*MTY4ODA5ODkyMC4yLjEuMTY4ODEwMjY2NS4wLjAuMA..">messaging console</a>. As it is our first message, we need to select "Create your first campaign". Select "Firebase Notification messages" and click "Create".</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-311.png" alt="Image" width="600" height="400" loading="lazy">
<em>Sample test messaging template</em></p>
<p>Now we need to enter the notification title, text, and name for the message.</p>
<p>Then we can get the FCM token manually for testing purposes using the code below. To retrieve the current registration token for an app instance, call <code>getToken()</code> in the <code>main()</code> method. This method will ask the user for notification permissions if notification permission has not been granted. Otherwise, it returns a token or rejects if there's any error.</p>
<pre><code>final fcmToken = <span class="hljs-keyword">await</span> FirebaseMessaging.instance.getToken();
log(<span class="hljs-string">"FCMToken $fcmToken"</span>);
</code></pre><p>Copy the FCM token printed on the console and paste it into the "Add an FCM registration token" input box.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/07/image-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Sent test message using FCM Token</em></p>
<p>Click on the Test button. The targeted client device (with the app in the background) should receive the notification.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/07/image.png" alt="Image" width="600" height="400" loading="lazy">
<em>Received push notification in android device</em></p>
<p>Hurray! We got the notification on our Android device. If we click on the notification it will open the app by default.</p>
<p>When we tap a notification, the default behavior on both Android and iOS is to open the application. If the application is terminated, it will be started. If it is in the background, it will be brought to the foreground.</p>
<p>Here, we can see the basic configuration to initialize Firebase messaging.</p>
<p><code>main.dart</code></p>
<pre><code>
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:firebase_core/firebase_core.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:firebase_messaging/firebase_messaging.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'firebase_options.dart'</span>;

<span class="hljs-keyword">void</span> main() <span class="hljs-keyword">async</span> {
  runApp(<span class="hljs-keyword">const</span> MyApp());
  <span class="hljs-keyword">await</span> Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );
  final fcmToken = <span class="hljs-keyword">await</span> FirebaseMessaging.instance.getToken();
  <span class="hljs-keyword">await</span> FirebaseMessaging.instance.setAutoInitEnabled(<span class="hljs-literal">true</span>);
  log(<span class="hljs-string">"FCMToken $fcmToken"</span>);
}
</code></pre><h2 id="heading-conclusion">Conclusion</h2>
<p>In this tutorial we have covered the essential steps for implementing push notifications in Flutter using Firebase Cloud Messaging (FCM). </p>
<p>By following the outlined steps, you can set up Firebase, integrate it into your Flutter project, and implement push notification functionality. </p>
<p>With the ability to send and receive notifications seamlessly, you can enhance the user experience and engage with your app's users effectively. Stay tuned for more advanced topics and features in future tutorials.</p>
<p>If you wish to learn more about Flutter, subscribe to my <a target="_blank" href="https://5minslearn.gogosoon.com/?ref=fcc_flutter_firebase">email newsletter</a> (<a target="_blank" href="https://5minslearn.gogosoon.com/?ref=fcc_flutter_firebase">https://5minslearn.gogosoon.com/</a>) and follow me on social media. </p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
