<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Bitloom Blog - Modern Software Development]]></title><description><![CDATA[Exploring the latest in web and mobile development—technical deep dives, practical insights, and career inspiration for developers who love to build and grow.]]></description><link>https://blog.bitloom.sk</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1742812087182/31fa1103-bd82-4e3a-8eb6-0a92089933d6.png</url><title>Bitloom Blog - Modern Software Development</title><link>https://blog.bitloom.sk</link></image><generator>RSS for Node</generator><lastBuildDate>Wed, 08 Apr 2026 11:48:41 GMT</lastBuildDate><atom:link href="https://blog.bitloom.sk/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Website Accessibility in the EU: What You Need to Know]]></title><description><![CDATA[Think about how often you use websites — for shopping, learning, working, or catching up with friends. But for millions of people with disabilities, using the web can be a challenge if sites aren’t designed with accessibility in mind.
Website accessi...]]></description><link>https://blog.bitloom.sk/website-accessibility-in-the-eu-what-you-need-to-know</link><guid isPermaLink="true">https://blog.bitloom.sk/website-accessibility-in-the-eu-what-you-need-to-know</guid><category><![CDATA[Accessibility]]></category><category><![CDATA[Web Accessibility]]></category><category><![CDATA[#WCAG]]></category><category><![CDATA[European Accessibility Act (EAA)]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[ux design]]></category><category><![CDATA[UX]]></category><category><![CDATA[SEO]]></category><category><![CDATA[aria]]></category><category><![CDATA[Digital Inclusion]]></category><dc:creator><![CDATA[Miroslav Pillár]]></dc:creator><pubDate>Mon, 16 Jun 2025 14:22:07 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/MHqhLm2_QGk/upload/5f9a03a8c24c4a51eb3d2f44229b6cea.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Think about how often you use websites — for shopping, learning, working, or catching up with friends. But for millions of people with disabilities, using the web can be a challenge if sites aren’t designed with accessibility in mind.</p>
<p><strong>Website accessibility</strong> means making digital content usable for everyone — including those with visual, hearing, motor, or cognitive impairments. In the European Union, accessibility isn’t just best practice — it’s legally required. You’ve already got the EU Web Accessibility Directive and EN 301 549 (based on WCAG 2.1 AA), and now <a target="_blank" href="https://accessible-eu-centre.ec.europa.eu/content-corner/news/eaa-comes-effect-june-2025-are-you-ready-2025-01-31_en"><strong>a new regulation is taking effect on 28 June 2025</strong></a> that further expands these rules.</p>
<p>From that date, the <strong>European Accessibility Act (EAA)</strong> will apply not just to public-sector websites, but also to a wide range of digital services and products — e.g. e-commerce platforms, banking apps, ticketing machines, and more — that are sold or provided in the EU.</p>
<p>But accessibility is more than just compliance. It improves <strong>user experience</strong>, strengthens <strong>SEO</strong>, and expands your audience. In this article, we’ll cover what the EU requires, the key principles of accessible design, and practical tips to help you build websites that work for everyone.</p>
<h2 id="heading-the-4-principles-of-accessible-websites-wcag-21"><strong>The 4 Principles of Accessible Websites (WCAG 2.1)</strong></h2>
<p>So, how do you actually make a website accessible? The good news is that you don’t have to guess.</p>
<p>Accessibility is based on a clear set of rules called the <strong>Web Content Accessibility Guidelines (WCAG)</strong>. At the heart of WCAG are <strong>four key principles</strong>, often remembered by the word <strong>POUR</strong>. Let’s break them down:</p>
<h3 id="heading-1-perceivable"><strong>1. Perceivable</strong></h3>
<p>People need to be able to <strong>see</strong> or <strong>hear</strong> your content, depending on how they access the web. That means:</p>
<ul>
<li><p>Adding <strong>alt text</strong> to images so screen readers can describe them</p>
</li>
<li><p>Using <strong>captions</strong> for videos</p>
</li>
<li><p>Making sure <strong>color contrast</strong> is high enough for text to be readable</p>
</li>
</ul>
<h3 id="heading-2-operable"><strong>2. Operable</strong></h3>
<p>Your website should be <strong>easy to use</strong>, even if someone can’t use a mouse.</p>
<ul>
<li><p>Can you <strong>navigate everything using just a keyboard</strong>?</p>
</li>
<li><p>Do all buttons, links, and forms have clear <strong>focus indicators</strong> when selected?</p>
</li>
<li><p>Avoid things like flashing animations that could trigger seizures</p>
</li>
</ul>
<h3 id="heading-3-understandable"><strong>3. Understandable</strong></h3>
<p>Websites should be <strong>clear and predictable</strong>.</p>
<ul>
<li><p>Use <strong>simple, consistent navigation</strong></p>
</li>
<li><p>Make sure forms have clear <strong>labels</strong> and helpful <strong>error messages</strong></p>
</li>
<li><p>Avoid using technical jargon when plain language will do</p>
</li>
</ul>
<h3 id="heading-4-robust"><strong>4. Robust</strong></h3>
<p>Your website should work well with a wide range of devices and assistive technologies, like screen readers.</p>
<ul>
<li><p>Use <strong>clean, semantic HTML</strong></p>
</li>
<li><p>Test your site with different browsers and devices</p>
</li>
<li><p>Use <strong>ARIA</strong> (Accessible Rich Internet Applications) attributes carefully to help screen readers</p>
</li>
</ul>
<p>In the next section, we’ll dive into <strong>practical steps</strong> you can take to make your website more accessible.</p>
<hr />
<h2 id="heading-practical-steps-to-build-accessible-websites"><strong>Practical Steps to Build Accessible Websites</strong></h2>
<p>Now that you know the key principles of accessibility, let’s talk about how to actually <strong>put them into practice</strong>. The good news? Many accessibility improvements are simple to add — and they often make your website easier to use for everyone.</p>
<p>Here are some of the most important things to focus on:</p>
<h3 id="heading-1-use-alt-text-for-images"><strong>1. Use Alt Text for Images</strong></h3>
<p>Alt text is a short description of an image that helps people using screen readers understand what’s being shown. If the image is decorative and doesn’t add meaning, you can leave the alt text empty — but for important images, write something short and descriptive.</p>
<p>❌ <strong>Bad</strong>: alt text is missing</p>
<p>✅ <strong>Good:</strong> alt text is present:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"product.jpg"</span> <span class="hljs-attr">alt</span>=<span class="hljs-string">"Black leather backpack with silver zippers"</span> /&gt;</span>
</code></pre>
<h3 id="heading-2-check-your-color-contrast"><strong>2. Check Your Color Contrast</strong></h3>
<p>Text should stand out clearly against the background. Use <strong>high-contrast colors</strong>, especially for buttons, links, and body text. There are free online tools to check if your color combinations meet the recommended contrast ratio.</p>
<p>❌ <strong>Bad</strong>: Light gray text on a white background:</p>
<pre><code class="lang-css">{
    <span class="hljs-attribute">color</span>: <span class="hljs-number">#ccc</span>; 
    <span class="hljs-attribute">background</span>: <span class="hljs-number">#fff</span>;
}
</code></pre>
<p>✅ <strong>Good:</strong> Dark gray text on white background:</p>
<pre><code class="lang-css">{
    <span class="hljs-attribute">color</span>: <span class="hljs-number">#333</span>; 
    <span class="hljs-attribute">background</span>: <span class="hljs-number">#fff</span>;
}
</code></pre>
<p>ℹ️ For checking color contrast, use tools like <a target="_blank" href="https://webaim.org/resources/contrastchecker/">WebAIM Contrast Checker</a>.</p>
<h3 id="heading-3-make-sure-everything-works-with-a-keyboard"><strong>3. Make Sure Everything Works with a Keyboard</strong></h3>
<p>Some users can’t use a mouse and rely entirely on a <strong>keyboard</strong> to navigate. Test your site by pressing <strong>Tab</strong> and <strong>Shift + Tab</strong> to move around. Can you reach every link, button, and form field? Can you clearly see <strong>which element is currently selected</strong>?</p>
<p>✅ <strong>Good</strong>: When you tab to a button, you see a clear <strong>outline</strong> or <strong>highlight</strong> around it.</p>
<p>❌ <strong>Bad</strong>: The focus indicator is missing, and you don’t know what’s selected.</p>
<pre><code class="lang-css"><span class="hljs-selector-tag">button</span><span class="hljs-selector-pseudo">:focus</span> {
  <span class="hljs-attribute">outline</span>: <span class="hljs-number">2px</span> solid <span class="hljs-number">#97D3F2</span>;
}
</code></pre>
<h3 id="heading-4-use-clear-labels-and-error-messages-on-forms"><strong>4. Use Clear Labels and Error Messages on Forms</strong></h3>
<p>Forms are often frustrating for everyone — and even more so if they aren’t accessible. Make sure each form field has a <strong>label</strong> that tells the user what to enter, and provide <strong>helpful error messages</strong> when something goes wrong.</p>
<p>❌ <strong>Bad</strong>: Error: Invalid input.</p>
<p>✅ <strong>Good</strong>: Please enter a valid email address (<a target="_blank" href="mailto:example@domain.com">example@domain.com</a>).</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">for</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">required</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"error"</span>&gt;</span>Please enter your email address.<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
</code></pre>
<h3 id="heading-5-structure-your-content-with-headings"><strong>5. Structure Your Content with Headings</strong></h3>
<p>Use headings (&lt;h1&gt;, &lt;h2&gt;, &lt;h3&gt;, etc.) in the correct order to create a clear structure for your pages. Screen readers use these headings to help people jump quickly to the sections they need.</p>
<p>❌ <strong>Bad</strong>: Avoid skipping levels like jumping from <code>&lt;h1&gt;</code> straight to <code>&lt;h4&gt;</code>.</p>
<p>✅ <strong>Good</strong>:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Our Services<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>Web Development<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h3</span>&gt;</span>Frontend<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h3</span>&gt;</span>Backend<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>Consulting<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
</code></pre>
<h3 id="heading-6-add-skip-to-content-links"><strong>6. Add ‘Skip to Content’ Links</strong></h3>
<p>This simple link at the top of the page lets keyboard users <strong>skip repetitive navigation</strong> (like menus) and go straight to the main content. It’s a small thing that makes a <strong>huge</strong> difference in usability.</p>
<p>❌ <strong>Bad</strong>: Missing button for skipping to content</p>
<p>✅ <strong>Good</strong>: Button with link to the content:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"#main-content"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"skip-link"</span>&gt;</span>Skip to main content
  <span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">header</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"site-header"</span>&gt;</span>
    <span class="hljs-comment">&lt;!-- Rest of your header code, like navigation menus, goes here --&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">header</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">main</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"main-content"</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Heading<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>This is the first paragraph<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>

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

<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
</code></pre>
<p>✅ Use CSS to make it visible <strong>when focused</strong> by a keyboard user.</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.skip-link</span> {
  <span class="hljs-attribute">position</span>: absolute;
  <span class="hljs-attribute">left</span>: -<span class="hljs-number">999px</span>;
}
<span class="hljs-selector-class">.skip-link</span><span class="hljs-selector-pseudo">:focus</span> {
  <span class="hljs-attribute">left</span>: <span class="hljs-number">0</span>;
  <span class="hljs-attribute">background</span>: <span class="hljs-number">#000</span>;
  <span class="hljs-attribute">color</span>: <span class="hljs-number">#fff</span>;
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">8px</span>;
}
</code></pre>
<h3 id="heading-7-use-aria-labes-and-roles"><strong>7. Use ARIA Labes and Roles</strong></h3>
<p><strong>ARIA</strong> stands for <strong>Accessible Rich Internet Applications</strong>. It’s a set of attributes you can add to HTML to help screen readers and other assistive technologies <strong>understand</strong> parts of your website that aren’t already clear from the HTML alone.</p>
<p>❗ <a target="_blank" href="https://w3c.github.io/using-aria/#rule1">The first rule of ARIA use is</a>: "If you can use a native HTML element or attribute with the semantics and behavior you require already built in, instead of re-purposing an element and adding an ARIA role, state or property to make it accessible, then do so."</p>
<p>There are <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes">many ARIA attributes</a> that can be used. Here are examples of the most common ones:</p>
<h3 id="heading-example-1-aria-label">Example 1: aria-label</h3>
<p>Provides an <strong>invisible label</strong> for screen readers if there’s no visible text.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">aria-label</span>=<span class="hljs-string">"Close menu"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">svg</span>&gt;</span>...<span class="hljs-tag">&lt;/<span class="hljs-name">svg</span>&gt;</span> <span class="hljs-comment">&lt;!-- An icon-only button --&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
</code></pre>
<p>✅ Use this for <strong>icon-only</strong> buttons so screen readers know what they do.</p>
<h3 id="heading-example-2-aria-hidden">Example 2: aria-hidden</h3>
<p>Hides content from screen readers that doesn’t add value or may be confusing.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">aria-hidden</span>=<span class="hljs-string">"true"</span>&gt;</span>*<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span> <span class="hljs-comment">&lt;!-- Decorative symbol, not meaningful --&gt;</span>
</code></pre>
<p>❗ <strong>Tip:</strong> Never use aria-hidden="true" on important content that people need to understand.</p>
<h3 id="heading-example-3-role">Example 3: role</h3>
<p>Defines the <strong>type</strong> of element when you’re using a non-semantic element like &lt;div&gt; or &lt;span&gt; for a custom component.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">role</span>=<span class="hljs-string">"button"</span> <span class="hljs-attr">tabindex</span>=<span class="hljs-string">"0"</span> <span class="hljs-attr">onclick</span>=<span class="hljs-string">"doSomething()"</span>&gt;</span>Click Me<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
</code></pre>
<p>❌ <strong>BUT</strong> — it’s better to just use:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onclick</span>=<span class="hljs-string">"doSomething()"</span>&gt;</span>Click Me<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
</code></pre>
<p>✅ Use roles <strong>only when there’s no suitable semantic HTML element</strong>.</p>
<h3 id="heading-example-4-aria-live">Example 4: aria-live</h3>
<p>Announces <strong>dynamic changes</strong> (like form validation or loading messages) to screen readers automatically.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">aria-live</span>=<span class="hljs-string">"polite"</span>&gt;</span>Your changes have been saved.<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
</code></pre>
<p>aria-live can have following values:</p>
<ul>
<li><p><code>off</code> (default):</p>
<ul>
<li><p>Updates to this region are not announced by screen readers.</p>
</li>
<li><p><strong>When to use:</strong> For content that updates but doesn’t need to be announced to users.</p>
</li>
</ul>
</li>
<li><p><code>polite</code>:</p>
<ul>
<li><p>Updates will be announced when the user is idle (won’t interrupt the current speech).</p>
</li>
<li><p><strong>When to use:</strong> For non-critical updates that are useful but not urgent.</p>
</li>
</ul>
</li>
<li><p><code>assertive</code>:</p>
<ul>
<li><p>Updates will be announced immediately, interrupting whatever the screen reader is currently reading.</p>
</li>
<li><p><strong>When to use:</strong> For important or urgent updates where the user needs to know right now.</p>
</li>
</ul>
</li>
</ul>
<hr />
<h1 id="heading-conclusion">Conclusion</h1>
<p>Website accessibility isn’t just about ticking boxes — it’s about making sure <em>everyone</em> can use what you build. By following accessibility principles and using practical steps like good color contrast, proper alt text, and keyboard-friendly navigation, you’re not only creating better experiences for people with disabilities — you’re improving your site for <em>all</em> users.</p>
<p>Plus, with regulations like the EU accessibility directive, it’s not just good practice — it’s becoming a legal requirement too.</p>
<p>Thank you for reading!</p>
]]></content:encoded></item><item><title><![CDATA[Boost Your Productivity with Multiple AI Agents Using CrewAI]]></title><description><![CDATA[Artificial Intelligence (AI) is revolutionizing the way we work by automating tasks, improving efficiency, and boosting productivity. One powerful way to leverage AI is by using multiple AI agents that collaborate to complete complex tasks. CrewAI fr...]]></description><link>https://blog.bitloom.sk/boost-your-productivity-with-multiple-ai-agents-using-crewai</link><guid isPermaLink="true">https://blog.bitloom.sk/boost-your-productivity-with-multiple-ai-agents-using-crewai</guid><category><![CDATA[vision-tool]]></category><category><![CDATA[Artificial Intelligence]]></category><category><![CDATA[openai]]></category><category><![CDATA[ai-agent]]></category><category><![CDATA[Productivity]]></category><category><![CDATA[software development]]></category><category><![CDATA[Python]]></category><category><![CDATA[Workflow Automation]]></category><category><![CDATA[CrewAI]]></category><category><![CDATA[AI]]></category><dc:creator><![CDATA[Miroslav Pillár]]></dc:creator><pubDate>Tue, 25 Mar 2025 10:38:44 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/Q9yyST0Js7I/upload/4374e072f5d5c05314b76ff5e32b2825.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Artificial Intelligence (AI) is revolutionizing the way we work by automating tasks, improving efficiency, and boosting productivity. One powerful way to leverage AI is by using multiple AI agents that collaborate to complete complex tasks. <a target="_blank" href="https://docs.crewai.com/introduction">CrewAI</a> framework provides a structured approach to building and managing these AI-powered teams.</p>
<p>In this guide, we will build a cooperative AI team that simulates the workflow of professionals, increasing efficiency on repetitive or time-consuming tasks. This can be useful for designers, product managers, and business owners looking to optimize their creative processes and plannings.</p>
<h2 id="heading-what-are-we-building">What are we building?</h2>
<p>We will define a set of AI agents working together:</p>
<ul>
<li><p><strong>Image Analysis Agent:</strong> Describes the content of an image.</p>
</li>
<li><p><strong>Design Improvement Agent:</strong> Reviews the image description and suggests improvements.</p>
</li>
</ul>
<p><strong>Product Management Agent:</strong> Creates and prioritizes user stories based on the image and suggested improvements.</p>
<p>The AI crew will analyze and process the following image:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742884575419/a804d7f7-a83f-4f7b-bcfd-523ad3f87e73.png" alt class="image--center mx-auto" /></p>
<p>The result is a simulation of a typical workflow in IT industries, such as web development. It begins with analysis, suggests UX design based on the requirements, and creates user stories that are ready for development.</p>
<p>Let's get started!</p>
<h2 id="heading-setting-up">Setting Up</h2>
<p>Before we start implementing the AI team, we need to install CrewAI package and its dependencies. Follow these steps to set up your environment:</p>
<h3 id="heading-step-1-check-your-python-version">Step 1: Check Your Python Version</h3>
<p>CrewAI requires <code>Python &gt;=3.10 and &lt;3.13</code>. To check your Python version, run the following command:</p>
<pre><code class="lang-bash">python3 --version
</code></pre>
<p>If your Python version does not meet the requirements, you may need to upgrade or install a compatible version.</p>
<h3 id="heading-step-2-set-up-a-virtual-environment-optional-but-recommended">Step 2: Set Up a Virtual Environment (Optional but Recommended)</h3>
<pre><code class="lang-bash">python -m venv crewai_env
<span class="hljs-built_in">source</span> crewai_env/bin/activate  <span class="hljs-comment"># On Windows use `crewai_env\Scripts\activate`</span>
</code></pre>
<h3 id="heading-step-3-install-required-packages">Step 3: Install Required Packages</h3>
<pre><code class="lang-bash">pip install crewai crewai_tools pillow python-dotenv
pip freeze
</code></pre>
<h3 id="heading-step-4-set-up-your-api-key">Step 4: Set Up Your API Key</h3>
<p>This project requires an <a target="_blank" href="https://platform.openai.com/docs/overview">OpenAI account</a> and some credits to use their models. CrewAI needs an OpenAI API key to work. Create a new API key and save it in a <code>.env</code> file in your project directory.</p>
<pre><code class="lang-plaintext">OPENAI_API_KEY=your_openai_api_key_here
</code></pre>
<h2 id="heading-the-coding-part">The Coding Part</h2>
<h3 id="heading-step-1-import-modules">Step 1: Import Modules</h3>
<p>Create a new file <code>main.py</code> and import the necessary modules:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> os
<span class="hljs-keyword">from</span> PIL <span class="hljs-keyword">import</span> Image
<span class="hljs-keyword">from</span> crewai <span class="hljs-keyword">import</span> Agent, Task, Crew, Process, LLM
<span class="hljs-keyword">from</span> crewai_tools <span class="hljs-keyword">import</span> VisionTool
<span class="hljs-keyword">from</span> dotenv <span class="hljs-keyword">import</span> load_dotenv
</code></pre>
<h3 id="heading-step-2-load-api-key">Step 2: Load API Key</h3>
<p>Let's load the environment variables into the project and add a simple error check for when the OpenAI API key is missing:</p>
<pre><code class="lang-python">load_dotenv()

api_key = os.getenv(<span class="hljs-string">"OPENAI_API_KEY"</span>)

<span class="hljs-keyword">if</span> api_key <span class="hljs-keyword">is</span> <span class="hljs-literal">None</span>:
    <span class="hljs-keyword">raise</span> ValueError(<span class="hljs-string">"OPENAI_API_KEY not found in .env file"</span>)

os.environ[<span class="hljs-string">"OPENAI_API_KEY"</span>] = api_key
</code></pre>
<h3 id="heading-step-3-initialize-llm-image-path-and-visiontool">Step 3: Initialize LLM, Image Path and VisionTool</h3>
<p>For this use case, we use <code>gpt-4o</code> model from OpenAI, which is efficient and relatively affordable. However, feel free to use other models if you prefer. Also, setting the <code>temperature</code> to a higher value will generate more creative and diverse results.</p>
<pre><code class="lang-python">llm = LLM(model=<span class="hljs-string">"gpt-4o"</span>, temperature=<span class="hljs-number">0.8</span>)
</code></pre>
<p>Download the input image from the <a target="_blank" href="https://github.com/Dromediansk/ai-crew-image-content-analysis/blob/master/image1.png">GitHub repository</a> or use your own image. Place it in the root directory.</p>
<pre><code class="lang-python"><span class="hljs-comment"># Replace 'image1.png' with the actual filename of your uploaded image</span>
image_path = <span class="hljs-string">"image1.png"</span>

image = Image.open(image_path)
</code></pre>
<p>Next, create an instance of <a target="_blank" href="https://docs.crewai.com/tools/visiontool#vision-tool">Vision Tool</a> from CrewAI, which is capable of extracting text from images.</p>
<pre><code class="lang-python">vision_tool = VisionTool()
</code></pre>
<h3 id="heading-step-4-define-ai-agents">Step 4: Define AI Agents</h3>
<p>As <a target="_blank" href="https://docs.crewai.com/concepts/agents">CrewAI docs</a> say, an Agent is an autonomous unit that can perform specific tasks, make decisions based on its role and goal or collaborate with other agents. Think of an agent as a specialized team member with specific skills, expertise, and responsibilities.</p>
<p>Let's define our first Agent.</p>
<h4 id="heading-agent-1-image-analyzer">Agent 1: Image Analyzer</h4>
<pre><code class="lang-python">image_analyzer_agent = Agent(
    role=<span class="hljs-string">"Image Describer"</span>,
    goal=<span class="hljs-string">f"Describe the content of the image <span class="hljs-subst">{image_path}</span> accurately and comprehensively."</span>,
    backstory=<span class="hljs-string">"Expert Designer working for a B2B Restaurant Startup."</span>,
    verbose=<span class="hljs-literal">True</span>,
    tools=[vision_tool],
    llm=llm
)
</code></pre>
<p>You can adapt the <a target="_blank" href="https://docs.crewai.com/concepts/agents#agent-attributes">Agent's attributes</a> to fit your specific use case.</p>
<p>Next, let's define additional two agents - Designer and Product Manager.</p>
<h4 id="heading-agent-2-designer">Agent 2: Designer</h4>
<pre><code class="lang-python">designer_agent = Agent(
    role=<span class="hljs-string">"Design Improvement Suggestion Agent"</span>,
    goal=<span class="hljs-string">f"Review image descriptions and provide actionable improvement suggestions to the described scene in <span class="hljs-subst">{image_path}</span>."</span>,
    backstory=<span class="hljs-string">"You are an expert designer with a keen eye for detail, specializing in B2B restaurant design."</span>,
    verbose=<span class="hljs-literal">True</span>,
    llm=llm,
    tools=[vision_tool]
)
</code></pre>
<h4 id="heading-agent-3-product-manager">Agent 3: Product Manager</h4>
<pre><code class="lang-python">product_manager_agent = Agent(
    role=<span class="hljs-string">"Product Manager"</span>,
    goal=<span class="hljs-string">f"Create user stories and prioritize them based on the image <span class="hljs-subst">{image_path}</span>."</span>,
    backstory=<span class="hljs-string">"You are a product manager experienced in B2B restaurant solutions."</span>,
    verbose=<span class="hljs-literal">True</span>,
    llm=llm,
    tools=[vision_tool]
)
</code></pre>
<h3 id="heading-step-5-define-tasks">Step 5: Define Tasks</h3>
<p>In CrewAI, a <a target="_blank" href="https://docs.crewai.com/concepts/tasks">Task</a> is a specific assignment completed by an Agent. In each task you define all the necessary details for execution.</p>
<p>We will use <code>sequential</code> execution flow, in which Tasks are executed in order they are defined.</p>
<h4 id="heading-task-1-describe-image">Task 1: Describe Image</h4>
<pre><code class="lang-python">task_describe_image = Task(
    description=<span class="hljs-string">"Describe the content of the image."</span>,
    expected_output=<span class="hljs-string">"A detailed description of the image"</span>,
    agent=image_analyzer_agent
)
</code></pre>
<h4 id="heading-task-2-suggest-design-improvements">Task 2: Suggest Design Improvements</h4>
<pre><code class="lang-python">task_suggest_improvements = Task(
    description=<span class="hljs-string">"Review the image description and suggest improvements to the image. Focus on design and visual elements."</span>,
    expected_output=<span class="hljs-string">"A list of actionable suggestions."</span>,
    agent=designer_agent,
    context=[task_describe_image]
)
</code></pre>
<h4 id="heading-task-3-create-user-stories">Task 3: Create User Stories</h4>
<pre><code class="lang-python">task_create_user_stories = Task(
    description=<span class="hljs-string">"Create user stories based on the design improvements suggested. Prioritize these user stories based on impact and feasibility."</span>,
    expected_output=<span class="hljs-string">"A prioritized list of user stories."</span>,
    agent=product_manager_agent,
    context=[task_describe_image, task_suggest_improvements]
)
</code></pre>
<h3 id="heading-step-6-define-and-run-the-ai-team">Step 6: Define and Run the AI Team</h3>
<p>Now that we defined Agents and Tasks, we are ready to define a <a target="_blank" href="https://docs.crewai.com/concepts/crews">Crew</a> and start the execution.</p>
<pre><code class="lang-python">ai_team = Crew(
    agents=[image_analyzer_agent, designer_agent, product_manager_agent],
    tasks=[task_describe_image, task_suggest_improvements, task_create_user_stories],
    process=Process.sequential,
    verbose=<span class="hljs-literal">True</span>
)

result = ai_team.kickoff()
</code></pre>
<h3 id="heading-step-7-save-the-output">Step 7: Save the Output</h3>
<p>We stored the outcome in <code>result</code> variable, but for better readability, it would be helpful to write it in Markdown and save it to a new file called <code>output.md</code>:</p>
<pre><code class="lang-python"><span class="hljs-keyword">with</span> open(<span class="hljs-string">"output.md"</span>, <span class="hljs-string">"w"</span>) <span class="hljs-keyword">as</span> md_file:
    md_file.write(<span class="hljs-string">"## Crew Execution Results in Markdown:\n"</span>)

    <span class="hljs-keyword">for</span> idx, task_output <span class="hljs-keyword">in</span> enumerate(result.tasks_output):
        md_file.write(<span class="hljs-string">f"Agent <span class="hljs-subst">{idx + <span class="hljs-number">1</span>}</span>: <span class="hljs-subst">{task_output.agent}</span>\n<span class="hljs-subst">{task_output.raw}</span>\n\n"</span>)
</code></pre>
<h3 id="heading-step-8-run-the-app-amp-refine">Step 8: Run the app &amp; refine</h3>
<p>Finally, it's time for executing and see the results:</p>
<pre><code class="lang-python">python3 main.py
</code></pre>
<p>🚀 Voila! The result should be stored in the <code>output.md</code> file, written in Markdown.</p>
<pre><code class="lang-markdown">  ## Crew Execution Results in Markdown:
  Agent 1: Image Describer
  The image shows a user interface of a restaurant management system, tailored for a restaurant named "Pizza Real." The interface features a theme setup section where users can manage different elements of the restaurant's website appearance. Key components visible in the interface include:

<span class="hljs-bullet">  1.</span> <span class="hljs-strong">**Social Media Links**</span>: There are options to manage links to various social media platforms, including TikTok, Facebook, Instagram, and YouTube. These appear to be essential elements for connecting the restaurant's online presence with social media.

<span class="hljs-bullet">  2.</span> <span class="hljs-strong">**Cover Logo Section**</span>: There is a designated area for uploading the restaurant's cover logo, which is crucial for branding purposes.

<span class="hljs-bullet">  3.</span> <span class="hljs-strong">**Background Customization**</span>: Users can choose between setting a background color or an image for the website. The interface indicates a specific color code, #102d50, which is selected for the menu background, suggesting a preference for a particular aesthetic.

<span class="hljs-bullet">  4.</span> <span class="hljs-strong">**Layout and Navigation**</span>: The interface is organized with a left sidebar that likely serves as a navigation menu. The sidebar allows users to switch between different settings and sections within the management system.

  Overall, the user interface is designed to provide a comprehensive set of tools for customizing and managing the visual theme of the restaurant's online presence, focusing on branding, aesthetic consistency, and social media integration.

  Agent 2: Design Improvement Suggestion Agent
  Based on the description of the user interface for the restaurant management system in "Pizza Real," here are some actionable design improvement suggestions:

<span class="hljs-bullet">  1.</span> <span class="hljs-strong">**Enhance Visual Hierarchy**</span>: Ensure that the most critical features, such as the cover logo and social media links, are prominently displayed with distinct visual separation. This could be achieved through size variation, color contrasts, or using dividers.

<span class="hljs-bullet">  2.</span> <span class="hljs-strong">**Social Media Link Customization**</span>: Provide customization options for social media icons, such as size, color, and shape, to align with the restaurant's brand identity, making them more visually integrated with the overall theme.

<span class="hljs-bullet">  3.</span> <span class="hljs-strong">**Cover Logo Optimization**</span>: Allow for different display options and sizes for the cover logo to ensure it displays optimally across different devices. Consider adding a preview function so users can see how their logo appears on the site.

<span class="hljs-bullet">  4.</span> <span class="hljs-strong">**Background Customization Preview**</span>: Introduce a live preview feature for background changes (color or image) to allow users to see the impact of their choices in real time. This helps in maintaining aesthetic consistency and making informed decisions.

<span class="hljs-bullet">  5.</span> <span class="hljs-strong">**Sidebar Enhancement**</span>: If the sidebar is used for navigation, consider making it collapsible to maximize the workspace in the main area, especially on smaller screens. Use icons alongside text for better recognition and quicker access.

<span class="hljs-bullet">  6.</span> <span class="hljs-strong">**Color Palette and Theme Consistency**</span>: Offer a set of pre-defined color palettes that users can choose from to maintain a cohesive theme throughout the site. Encourage the use of complementary colors to the #102d50 code to enhance visual appeal.

<span class="hljs-bullet">  7.</span> <span class="hljs-strong">**Tooltips and Help Guides**</span>: Integrate tooltips or a help guide to assist users in understanding different features and settings quickly. This can improve user experience, especially for those new to the system.

<span class="hljs-bullet">  8.</span> <span class="hljs-strong">**Responsive Design Considerations**</span>: Ensure that the user interface is fully responsive. This involves optimizing the display for various devices such as tablets and smartphones, ensuring accessibility and ease of use on any platform.

  Implementing these suggestions can lead to a more intuitive and visually appealing user interface, enhancing the management capabilities and overall online presence of "Pizza Real." 

  Agent 3: Product Manager
  Based on the design improvements suggested for the "Pizza Real" restaurant management system interface, here is a prioritized list of user stories:

<span class="hljs-bullet">  1.</span> <span class="hljs-strong">**Responsive Design Optimization**</span>
<span class="hljs-bullet">     -</span> As a user, I want the interface to be fully responsive so that it can be easily accessed and used on any device, ensuring accessibility and ease of use.

<span class="hljs-bullet">  2.</span> <span class="hljs-strong">**Cover Logo Display Options**</span>
<span class="hljs-bullet">     -</span> As a user, I want different display options and sizes for the cover logo to ensure it appears optimally across devices, and I want a preview function to see how my logo looks on the site.

<span class="hljs-bullet">  3.</span> <span class="hljs-strong">**Sidebar Enhancement**</span>
<span class="hljs-bullet">     -</span> As a user, I want the navigation sidebar to be collapsible to maximize workspace on smaller screens, with icons alongside text for better recognition.

<span class="hljs-bullet">  4.</span> <span class="hljs-strong">**Social Media Icon Customization**</span>
<span class="hljs-bullet">     -</span> As a user, I want to customize social media icons (size, color, shape) so they align with the restaurant's brand identity and integrate with the theme.

<span class="hljs-bullet">  5.</span> <span class="hljs-strong">**Background Customization Live Preview**</span>
<span class="hljs-bullet">     -</span> As a user, I want a live preview for background changes so I can see the impact of my color or image choices in real-time, ensuring aesthetic consistency.

<span class="hljs-bullet">  6.</span> <span class="hljs-strong">**Enhance Visual Hierarchy**</span>
<span class="hljs-bullet">     -</span> As a user, I want critical features like cover logo and social media links to be prominently displayed with visual separation for easy access.

<span class="hljs-bullet">  7.</span> <span class="hljs-strong">**Color Palette Options**</span>
<span class="hljs-bullet">     -</span> As a user, I want a set of predefined color palettes to maintain a cohesive theme, using complementary colors to the #102d50 code.

<span class="hljs-bullet">  8.</span> <span class="hljs-strong">**Tooltips and Help Guides Integration**</span>
<span class="hljs-bullet">     -</span> As a user, I want tooltips or a help guide to assist me in understanding different features and settings quickly, improving my user experience.

  These user stories are prioritized based on their impact on user experience and feasibility of implementation. Responsive design and cover logo optimization are top priorities due to their significant impact on usability and branding. Sidebar and social media icon enhancements follow due to their role in navigation and brand alignment. Live previews and visual hierarchy improvements are also important for maintaining an intuitive and visually appealing interface. Color palette options and help guides are prioritized last, as they are enhancements to existing functionalities.
</code></pre>
<p>Feel free to adjust the definitions of Agents and Tasks to achieve the desired outcome.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>By leveraging CrewAI, we built a team of AI agents that:</p>
<ul>
<li><p>analyze images,</p>
</li>
<li><p>suggest design improvements,</p>
</li>
<li><p>and generate user stories.</p>
</li>
</ul>
<p>This project demonstrates the power of multi-agent collaboration in solving real-world problems efficiently. Implementing such AI-powered workflows can significantly enhance productivity, allowing professionals to focus on strategic decision-making while AI handles the repetitive tasks.</p>
<p>I encourage you to experiment with different models, expand the team’s capabilities, and customize the AI agents to fit your specific needs.</p>
<p>Here is the complete <a target="_blank" href="https://github.com/Dromediansk/ai-crew-image-content-analysis">GitHub repository</a> for reference.</p>
<p>Thank you for reading!</p>
]]></content:encoded></item><item><title><![CDATA[Setting Up Sign-in with Google Using Expo and Supabase]]></title><description><![CDATA[Recently, when building a mobile app and implementing OAuth authentication in Expo and Supabase, I ran into several issues that took me hours to debug. Searching through documentation, forums, and using various AI tools, I found the right solution th...]]></description><link>https://blog.bitloom.sk/setting-up-sign-in-with-google-using-expo-and-supabase</link><guid isPermaLink="true">https://blog.bitloom.sk/setting-up-sign-in-with-google-using-expo-and-supabase</guid><category><![CDATA[supabase]]></category><category><![CDATA[OAuth2]]></category><category><![CDATA[React Native]]></category><category><![CDATA[Expo]]></category><dc:creator><![CDATA[Miroslav Pillár]]></dc:creator><pubDate>Mon, 24 Mar 2025 10:47:33 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/n31x0hhnzOs/upload/5c0e387c2a090bec92532efa5bf49739.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Recently, when building a mobile app and implementing OAuth authentication in Expo and Supabase, I ran into several issues that took me hours to debug. Searching through documentation, forums, and using various AI tools, I found the right solution that worked for both Android and iOS.</p>
<p>Since these problems can be frustrating and time-consuming, I want to share the insights I gathered to help you set up Google Sign-In quickly and avoid common pitfalls.</p>
<h3 id="heading-step-1-install-expo-google-sign-in"><strong>Step 1: Install Expo Google Sign-In</strong></h3>
<p>Install <a target="_blank" href="https://github.com/react-native-google-signin/google-signin">@react-native-google-signin/google-signin</a> package by running command:</p>
<pre><code class="lang-bash">npx expo install @react-native-google-signin/google-signin
</code></pre>
<ul>
<li><p>it will install required package for authentication in Expo</p>
</li>
<li><p>it will update app.json <code>plugins</code></p>
</li>
</ul>
<h3 id="heading-step-2-set-up-your-google-cloud-project"><strong>Step 2: Set Up Your Google Cloud Project</strong></h3>
<ul>
<li>Open <a target="_blank" href="https://console.cloud.google.com/">Google Cloud Console</a> and <strong>create a new project</strong>.</li>
</ul>
<h4 id="heading-configure-the-oauth-consent-screen"><strong>Configure the OAuth Consent Screen</strong></h4>
<ul>
<li><p>Navigate to APIs &amp; Services &gt; OAuth consent screen.</p>
</li>
<li><p>Choose <em>External</em> for the user type</p>
</li>
<li><p>Fill out the necessary information, such as <em>App name, User support email</em>, and <em>Developer contact information</em>.</p>
</li>
<li><p>Add <em>Test users</em> -&gt; from my experience, this is required for correct iOS setup in development.</p>
</li>
<li><p>Click <em>Save</em>.</p>
</li>
</ul>
<h4 id="heading-create-client-id-for-web"><strong>Create Client ID For Web</strong></h4>
<ul>
<li><p>Select <em>Web application</em> as the application type.</p>
</li>
<li><p>Enter appropriate <em>Name.</em></p>
</li>
<li><p>Click <em>Create</em>. The generated Client ID will be used in your Expo app.</p>
</li>
</ul>
<hr />
<h4 id="heading-create-client-id-for-android"><strong>Create Client ID For Android</strong></h4>
<ul>
<li><p>Select <em>Android</em> as the application type.</p>
</li>
<li><p>Enter your <em>Package Name</em> from your <code>app.json</code> / <code>app.config.js</code> / <code>app.config.ts</code> file (e.g., <code>com.myorg.myapp</code>).</p>
</li>
<li><p>When creating <em>SHA-1 certificate fingerprint</em> there are currently 2 options:</p>
</li>
</ul>
<p><strong>Option 1 - Recommended by Google (worked for me):</strong></p>
<ul>
<li>Get the fingerprint certificate by running:</li>
</ul>
<pre><code class="lang-bash">keytool -keystore path-to-debug-or-production-keystore -list -v
</code></pre>
<ul>
<li><p>where <code>path-to-debug-or-production-keystore</code> is your path to android build: <code>./android/app/debug.keystore</code></p>
</li>
<li><p>Hit <em>Enter</em>. For keystore password, leave it empty (used for development)</p>
</li>
<li><p>Find and copy the <em>SHA-1 fingerprint</em> into the appropriate field in the Google Cloud Console.</p>
</li>
<li><p>Click <em>Create</em>.</p>
</li>
</ul>
<p><strong>Option 2: Recommended by Supabase (official way)</strong></p>
<p>This solution is stated in <a target="_blank" href="https://supabase.com/docs/guides/auth/social-login/auth-google?queryGroups=platform&amp;platform=react-native">Supabase documentation</a>. However, for some reason this did not work for me, but at a time you reading this it might work again:</p>
<ul>
<li><p>Run command <code>eas credentials</code> from the project root and select <em>Android</em> to retrieve the SHA-1 fingerprint.</p>
</li>
<li><p>Find and copy the SHA-1 fingerprint into the appropriate field in the Google Cloud Console.</p>
</li>
</ul>
<hr />
<h4 id="heading-create-client-id-for-ios"><strong>Create Client ID For iOS</strong></h4>
<ul>
<li><p>Select <em>iOS</em> as the application type.</p>
</li>
<li><p>Enter the <em>Name</em> you like</p>
</li>
<li><p>Modify your app.json to add this configuration:</p>
</li>
</ul>
<pre><code class="lang-json"><span class="hljs-string">"expo"</span>: {
  <span class="hljs-attr">"ios: {
    "</span>bundleIdentifier<span class="hljs-attr">": "</span>com.&lt;YOUR_USERNAME.APP_SCHEME&gt;<span class="hljs-attr">"
  },
  "</span>plugins<span class="hljs-attr">": [
      [
        "</span>@react-native-google-signin/google-signin<span class="hljs-attr">",
        {
          "</span>iosUrlScheme<span class="hljs-attr">": "</span>&lt;YOUR_IOS_URL_SCHEME&gt;<span class="hljs-attr">"
        }
      ]
    ],
}</span>
</code></pre>
<ul>
<li><p>In Google Cloud Console, enter <em>Bundle ID**</em>,** which should match with the <code>bundleIdentifier</code> in app.json (if not there, add it)</p>
</li>
<li><p>Copy <em>iOS URL Scheme</em> from Google Cloud Console and paste it app.json under <code>plugins</code></p>
</li>
</ul>
<h3 id="heading-step-3-configure-supabase"><strong>Step 3: Configure Supabase</strong></h3>
<ul>
<li><p>In your Supabase project, go to Authentication &gt; Sign-in/ Up &gt; section Auth Providers &gt; Google.</p>
</li>
<li><p>Enable <em>Sign in with Google</em></p>
</li>
<li><p>Copy both <em>OAuth Client ID</em> for web and Android from your Google Cloud console and paste it to the <em>Authorized Client IDs</em> field, separting by comma.</p>
</li>
<li><p>Copy the <em>Client Secret</em> from web client ID and paste it to the field <em>Client Secret (for OAuth)</em></p>
</li>
<li><p>Enable <em>Skip nonce checks**</em>.** By default, Supabase Auth implements nonce validation during the authentication flow.</p>
</li>
<li><p>From field <em>Callback URL (for OAuth)</em> copy the value and paste the URI in the <em>Google Cloud Console -&gt; Web Client ID -&gt; Authorized redirect URIs</em>. This ensures that your Supabase project is valid for Google authentication.</p>
</li>
</ul>
<h3 id="heading-step-4-update-your-expo-app"><strong>Step 4: Update Your Expo App</strong></h3>
<p>Now the coding part. To implement Google button, you should use the one from @react-native-google-signin/google-signin.</p>
<p>Here is an example:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// GoogleSignIn.tsx</span>

<span class="hljs-keyword">import</span> {
  GoogleSignin,
  GoogleSigninButton,
} <span class="hljs-keyword">from</span> <span class="hljs-string">"@react-native-google-signin/google-signin"</span>;
<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> { supabase } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/utils/supabase"</span>;
<span class="hljs-keyword">import</span> { router } <span class="hljs-keyword">from</span> <span class="hljs-string">"expo-router"</span>;
<span class="hljs-keyword">import</span> { useAuthStore } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/store/auth.store"</span>;

GoogleSignin.configure({
  webClientId: process.env.EXPO_PUBLIC_GOOGLE_WEB_CLIENT_ID,
  iosClientId: process.env.EXPO_PUBLIC_GOOGLE_IOS_CLIENT_ID,
});

<span class="hljs-keyword">const</span> GoogleSignIn = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> { authLoading, setAuthLoading, setUser } = useAuthStore();

  <span class="hljs-keyword">const</span> handleSignIn = <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">try</span> {
      setAuthLoading(<span class="hljs-literal">true</span>);
      <span class="hljs-keyword">await</span> GoogleSignin.hasPlayServices();
      <span class="hljs-keyword">const</span> userInfo = <span class="hljs-keyword">await</span> GoogleSignin.signIn();

      <span class="hljs-keyword">if</span> (userInfo.data?.idToken) {
        <span class="hljs-keyword">const</span> { data, error } = <span class="hljs-keyword">await</span> supabase.auth.signInWithIdToken({
          provider: <span class="hljs-string">"google"</span>,
          token: userInfo.data.idToken,
        });

        <span class="hljs-keyword">if</span> (error) {
          <span class="hljs-built_in">console</span>.error(error);
        }

        <span class="hljs-keyword">const</span> user = data?.user;

        <span class="hljs-keyword">if</span> (user) {
          setUser({
            id: user.id,
            email: user.email || <span class="hljs-string">""</span>,
            name: user.user_metadata.name,
            fullName: user.user_metadata.full_name,
            avatarUrl: user.user_metadata.picture,
          });
          router.replace(<span class="hljs-string">"/"</span>);
        }
      } <span class="hljs-keyword">else</span> {
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"no ID token present!"</span>);
      }
    } <span class="hljs-keyword">catch</span> (error: unknown) {
      <span class="hljs-built_in">console</span>.error(error);
    } <span class="hljs-keyword">finally</span> {
      setAuthLoading(<span class="hljs-literal">false</span>);
    }
  };

  <span class="hljs-keyword">return</span> (
    &lt;GoogleSigninButton
      size={GoogleSigninButton.Size.Wide}
      color={GoogleSigninButton.Color.Light}
      onPress={handleSignIn}
      disabled={authLoading}
    /&gt;
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> GoogleSignIn;
</code></pre>
<p>Be careful when configuring <code>GoogleSignIn.configure:</code></p>
<ul>
<li><p>in <code>webClientId</code> paste client ID from Web Client ID, NOT Android Client ID.</p>
</li>
<li><p>in <code>iosClientId</code> paste client ID from iOS.</p>
</li>
</ul>
<h3 id="heading-step-4-build-and-test"><strong>Step 4: Build and Test</strong></h3>
<ul>
<li>Build your app using expo prebuild:</li>
</ul>
<pre><code class="lang-bash">npx expo prebuild --clean
</code></pre>
<ul>
<li><p>Now you can run your app on both Android and iOS (either simulator or physical device).</p>
</li>
<li><p><em>Make sure you use development build</em> as this is a requirement of google-signin package.</p>
</li>
</ul>
<pre><code class="lang-bash">npx expo run:android
</code></pre>
<p>On another terminal window:</p>
<pre><code class="lang-shell">npx expo run:ios
</code></pre>
<ul>
<li>Go to your device and click the Google Sign-In button and verify that you can sign in successfully.</li>
</ul>
<hr />
<h3 id="heading-common-issues-and-troubleshooting"><strong>Common Issues and Troubleshooting</strong></h3>
<ul>
<li><p><strong>Error: DEVELOPER_ERROR</strong></p>
<ul>
<li><p>This error often indicates a problem with the SHA-1 certificate fingerprint or the OAuth client configuration.</p>
</li>
<li><p>Try to use options mentioned above.</p>
</li>
</ul>
</li>
<li><p><strong>Error: Must specify an idToken or an accessToken</strong></p>
<ul>
<li>This usually means the wrong client ID is being used. Ensure you are using the <em>Web client ID</em> from the Google Cloud Console.</li>
</ul>
</li>
</ul>
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>Setting up Google Sign-In in an Expo project with Supabase can be tricky. The most common pitfalls involve incorrect OAuth credentials, missing SHA-1 fingerprints, or using the wrong client ID.</p>
<p>By following this guide, you should be able to set up authentication smoothly and avoid wasting hours debugging.</p>
<p>If you run into issues or need further assistance, feel free to reach out to me. You can also check out the full source code in my GitHub repository for a complete working example:</p>
<p>👉 <a target="_blank" href="https://github.com/Dromediansk/mindtaker-rn">GitHub Repository</a></p>
<p>Happy coding! 🚀</p>
]]></content:encoded></item><item><title><![CDATA[Understand How Javascript Closures Work with Simple Examples]]></title><description><![CDATA[When I first started learning JavaScript, it gave me plenty of headaches. It’s easy to learn but hard to master—a language that can bring you joy and frustration on the same day.
As Douglas Crockford, an author of JavaScript: The Good Parts, once sai...]]></description><link>https://blog.bitloom.sk/understand-how-javascript-closures-work-with-simple-examples</link><guid isPermaLink="true">https://blog.bitloom.sk/understand-how-javascript-closures-work-with-simple-examples</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[closures in javascript]]></category><dc:creator><![CDATA[Miroslav Pillár]]></dc:creator><pubDate>Mon, 24 Mar 2025 10:46:18 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/m_HRfLhgABo/upload/6f790206f94a02d827adc8dd902e0208.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When I first started learning JavaScript, it gave me plenty of headaches. It’s easy to learn but hard to master—a language that can bring you joy and frustration on the same day.</p>
<p>As Douglas Crockford, an author of <em>JavaScript: The Good Parts</em>, once said:</p>
<blockquote>
<p><em>JavaScript is the world’s most misunderstood programming language.</em></p>
</blockquote>
<p>To truly master JavaScript and stand out, you need to understand its core principles and one of the most powerful among them is closures.</p>
<p>I guarantee that learning how closures work will ease your life and save nerves when solving daily problems. If you truly grasp the point, you can implement it in your project as a good practice and I’m sure other team members will appreciate your hard work.</p>
<p>I will explain to you how closures work by providing simple examples and giving you two real cases when they are useful. Big thanks belong to <a target="_blank" href="https://twitter.com/AndreiNeagoie">Andrei Neagoie</a>, who gave me huge inspiration for writing this article.</p>
<p><em>The requirements are familiarity with functions and Javascript runtime environment.</em></p>
<hr />
<p>If you want to grasp the main point of why closures are useful, the prerequisite is good knowledge of <em>functions</em> and <em>lexical scoping</em> (or <em>lexical environment</em>). A combination of these two aspects is the core of closures.</p>
<p>So let’s take it over one by one.</p>
<h2 id="heading-functions"><strong>Functions</strong></h2>
<p>I am pretty sure that you have at least a basic knowledge of functions if you code in Javascript. Otherwise, it would be like baking a cake without cooking tools.</p>
<p>However, you should also know the term which is closely bound with closures and that is <em>higher-order functions.</em></p>
<h3 id="heading-higher-order-function-hoc"><strong>Higher-order function (HOC)</strong></h3>
<p>It’s a type of function that either takes a function as an argument, returns a function as its return value, or both.</p>
<p>Here is a basic example from daily praxis:</p>
<p>If you work with arrays, I suppose you came across array methods such as <code>Array.map()</code>, <code>Array.filter()</code> or <code>Array.reduce()</code>. All of them take a function as an argument. All of them are higher-order functions.</p>
<p>Let’s write another example:</p>
<pre><code class="lang-javascript"> <span class="hljs-keyword">const</span> handleFamily = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> myGrandpa = <span class="hljs-string">'grandpa'</span>;

  <span class="hljs-keyword">return</span> sayHello = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> myFather = <span class="hljs-string">'father'</span>;
    <span class="hljs-keyword">return</span> <span class="hljs-string">'Hello there!'</span>;
  }
}
</code></pre>
<p>The <code>handleFamily</code> function is using a <em>fat arrow</em> function expression, which was introduced in ECMAScript 2015 (ES6). Inside this function, we have declared a variable <code>myGrandpa</code> and returned another function <code>sayHello</code>, so it is a Higher Order function.</p>
<p>If we call (invoke) <code>handleFamily()</code>, this is what we get:</p>
<p><code>==&gt; Function sayHello</code></p>
<p>That is pretty logical — we get another function. So in order to get the desired output, we have to call it like this:</p>
<pre><code class="lang-javascript">handleFamily()()
</code></pre>
<p><code>==&gt; 'Hello there!'</code></p>
<p>These two brackets one after another basically mean that whatever we return from <code>handleFamily</code>, we call again. We could also assign the called <code>handleFamily</code> to a variable so it would be like this:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> holdFamily = handleFamily();

holdFamily();
</code></pre>
<p>We tend to use HOC all the time. If you’re a JavaScript programmer or if you’ve ever done any JavaScript, you’ve probably used them all over the place.</p>
<h4 id="heading-execution-scope"><strong>Execution scope</strong></h4>
<p>It’s also required to know what really happens if a function is called. In Javascript, every function creates its <em>local execution scope</em> (or also the so-called <em>local execution context</em>). What does it mean?</p>
<ul>
<li><p>It means, that every variable, which is declared in this scope is also local to this scope.</p>
</li>
<li><p>The outer scope of the function — in our case, <em>global scope</em> — has no access to these variables.</p>
</li>
<li><p>On the other hand, the local scope can access variables from its outer scope. The reason is <em>because of closures</em>, which I explain later in this story.</p>
</li>
<li><p>It is a good practice to write variables, where they are needed. Therefore you might avoid side-effects and memory overloading of the Javascript engine.</p>
</li>
</ul>
<p>In our example, the local execution scope of <code>handleFamily</code> is where the first opening curly bracket is and ends with the last closing bracket. Its local variables are <code>myGrandpa</code> and <code>sayHello</code>.</p>
<p>The <code>sayHello</code> function has of course also its local execution scope, where <code>myFather</code> is declared. This means if you want to access this variable from <code>handleFamily</code> (the outer scope) you get an error because Javascript won’t see it.</p>
<h2 id="heading-lexical-scope"><strong>Lexical scope</strong></h2>
<p>It is a quite fancy name, but yet easy to understand. The best way how to explain to you this term is by dividing these words:</p>
<ul>
<li><p><em>lexical</em> — means <strong>where</strong> is a code written,</p>
</li>
<li><p><em>scope</em> — <strong>what</strong> variables we have access to.</p>
</li>
</ul>
<p>What do I mean by that?</p>
<p>In the first phase, even before executing the code, the Javascript engine will save all variables to the temporary memory of the Javascript engine (also called <em>memory heap)</em> for future usage. At the same time, the engine will recognize which function has access to which variables. This is done by determining function execution scopes and chaining them properly (often so-called <em>scope chaining</em>).</p>
<p>Eventually, in terms of scoping it matters, where a function is written, not where is called.</p>
<hr />
<h2 id="heading-the-coherence"><strong>The coherence</strong></h2>
<p>Now, since we are familiar with functions and lexical scoping, let’s combine them. I will modify our <code>handleFamily</code> function in the following manner:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> handleFamily = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> myGrandpa = <span class="hljs-string">'grandpa'</span>;

  <span class="hljs-keyword">return</span> sayHello = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> myFather = <span class="hljs-string">'father'</span>;
    <span class="hljs-keyword">return</span> <span class="hljs-string">`Hello <span class="hljs-subst">${myGrandpa}</span> and <span class="hljs-subst">${myFather}</span>!`</span>
  }
}

<span class="hljs-keyword">const</span> hold = handleFamily();
hold();
</code></pre>
<p>What do you expect the result would be?</p>
<p><code>==&gt; 'Hello grandpa and father!'</code></p>
<p>If you work with JavaScript I assume you are familiar with such behavior, but I think it’s quite important to understand why this happens. So let’s dismantle the whole process.</p>
<h4 id="heading-dismantling-the-execution"><strong>Dismantling the execution</strong></h4>
<p>When I had assigned <code>handleFamily</code> to a variable <code>hold</code>, its execution context was executed and popped off the <em>call stack</em>. When this happens, it usually means that the variable environment is destroyed as well. However, every function creates a closure, which has reserved space in the memory heap. If some variable is being referenced in one of the inner functions, the variable is saved in the closure memory waiting for use.</p>
<p>By calling <code>hold()</code> we execute <code>sayHello</code> function. The <code>myFather</code> variable is declared in the same scope and immediately used. On the other hand, <code>myGrandpa</code> is declared in the outer scope. That means the Javascript engine will go up to the outer scope and search for the <code>myGrandpa</code> in the closure memory. In our case, the variable is present there.</p>
<p>Theoretically, if <code>myGrandpa</code> wouldn’t be declared in the outer scope, it would go up to another level (in our case in the global scope) and search for the variable there. If it's still absent, it would return an error.</p>
<p>This is quite a unique behavior compared to other languages. Javascript engine will always make sure that functions have access to all of the variables outside the function using closures.</p>
<p>Finally, after calling <code>sayHello</code>, it’s popped off the call stack, and variables that are not referenced anywhere are cleaned up from memory by a cleaning mechanism of the Javascript engine called <em>garbage collection</em>.</p>
<hr />
<h2 id="heading-why-so-much-enthusiasm"><strong>Why so much enthusiasm</strong></h2>
<p>Now you might be thinking that closures are just big hype. But I will show you two cases when they could improve your code in terms of performance and security. I am talking about <em>memory efficiency</em> and <em>data encapsulation</em>.</p>
<h4 id="heading-memory-efficiency"><strong>Memory efficiency</strong></h4>
<p>Consider the following function called <code>produceCandy</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> produceCandy = <span class="hljs-function">(<span class="hljs-params">index</span>) =&gt;</span> {  
  <span class="hljs-keyword">const</span> candyFactory = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Array</span>(<span class="hljs-number">7000</span>).fill(<span class="hljs-string">'sweet candy'</span>);

  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'the candy factory was established'</span>);  
  <span class="hljs-keyword">return</span> candyFactory[index];
}
</code></pre>
<ul>
<li><p>The variable <code>candyFactory</code> creates an array, which has 7000 items.</p>
</li>
<li><p>It logs the text in the console whenever the function is called and returns an item with the desired index.</p>
</li>
</ul>
<p>Now, what is the main problem with this function?</p>
<p>Consider we need to return any items from <code>candyFactory</code>. In practice, it could be some data we need to get from a database and do something with it:</p>
<pre><code class="lang-javascript">candyFactory(<span class="hljs-number">34</span>);
candyFactory(<span class="hljs-number">4574</span>);
candyFactory(<span class="hljs-number">875</span>);
</code></pre>
<p><code>// returns ==&gt;   the candy factory was established   the candy factory was established   the candy factory was established   sweet candy</code></p>
<p>It means, <code>candyFactory</code> is created in the memory heap before execution and destroyed after execution <strong>every time it is called</strong>.</p>
<p>In our case, it’s not a big issue, but imagine you would call it ten thousand times. It would create the variable and destroy it ten thousand times, which is <strong>memory inefficient</strong> and might cause performance issues.</p>
<p>So what could be a solution? You’re guessing right — using closures.</p>
<p>I would modify our function like this:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> produceCandyEfficiently = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> candyFactory = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Array</span>(<span class="hljs-number">7000</span>).fill(<span class="hljs-string">'sweet candy'</span>) 
  <span class="hljs-comment">// candyFactory is stored in the closure memory, </span>
  <span class="hljs-comment">// because it has reference in execution scope of inner function below</span>
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'the candy factory was established'</span>);
  <span class="hljs-keyword">return</span> <span class="hljs-function">(<span class="hljs-params">index</span>) =&gt;</span> {
      <span class="hljs-keyword">return</span> candyFactory[index];
  }
}

<span class="hljs-keyword">const</span> getProduceCandyEfficiently = produceCandyEfficiently();

getProduceCandyEfficiently(<span class="hljs-number">1243</span>);
getProduceCandyEfficiently(<span class="hljs-number">6832</span>);
getProduceCandyEfficiently(<span class="hljs-number">345</span>);
</code></pre>
<p>And after calling <code>getProduceCandyEfficiently</code> 3 times this is the output:</p>
<p><code>==&gt; the candy factory was established   sweet candy</code></p>
<p>So <code>candyFactory</code> is created only once no matter how many times you call <code>getProduceCandyEfficiently</code>. After the last call, this function is eventually removed from the memory.</p>
<p>In tasks such as processing an immense amount of data, it can significantly improve performance. Cool, right?</p>
<h4 id="heading-data-encapsulation"><strong>Data encapsulation</strong></h4>
<p>This is another case when closures might be useful.</p>
<p>Suppose we have another candy factory, which monitors the duration time of its production since the function <code>monitorProductivityTime</code> has been called.</p>
<p>We will store this amount of time in the variable <code>timeWithoutReset</code>. The value is incremented every second in <code>setInterval</code>. Along with that, we’d like to also have a function, which would reset this duration time if needed. Let’s call it <code>reset</code>.</p>
<p>This is what would it look like (try to test it in the browser console to receive proper results):</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> monitorProductivityTime = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">let</span> timeWithoutReset = <span class="hljs-number">0</span>;
    <span class="hljs-keyword">const</span> productivityTime = <span class="hljs-function">() =&gt;</span> timeWithoutReset++;
    <span class="hljs-keyword">const</span> totalProductivityTime = <span class="hljs-function">() =&gt;</span> timeWithoutReset;
    <span class="hljs-keyword">const</span> reset = <span class="hljs-function">() =&gt;</span> {
      timeWithoutReset = <span class="hljs-number">-1</span>;
      <span class="hljs-keyword">return</span> <span class="hljs-string">'production time has been restarted!'</span>;
    }

    <span class="hljs-built_in">setInterval</span>(productivityTime, <span class="hljs-number">1000</span>);
    <span class="hljs-keyword">return</span> {
      totalProductivityTime, 
      reset
    }
  }
<span class="hljs-keyword">const</span> holdTime = monitorProductivityTime();
holdTime.totalProductivityTime();
</code></pre>
<p>When we call <code>holdTime.totalProductivityTime()</code> In the last line, we are measuring the duration of time from this moment. If you’d like to reset this time, just call <code>holdTime.reset()</code>.</p>
<p>Because <code>reset</code> function is inside <code>monitorProductivityTime</code>, it uses closure for accessing the variable <code>timeWithoutReset</code>.</p>
<p>However, the main problem with this function is that <em>everybody</em> can access <code>reset</code>. It could be your new team member, who is not acquainted with the project yet and could mess some things up. Or any third party from outside of your company. This can cause many troubles and security issues in practice, therefore some data or functions should not be directly exposed.</p>
<p>For this reason, you should always keep in mind the <em>principle of least privilege,</em> which is one of the security principles you should observe. It means that data can be accessed only by competent individuals.</p>
<p>The solution to this example might be to return <code>reset</code>, only if it’s appropriate, e.g. user has a competent role to access this function.</p>
<hr />
<h2 id="heading-summary"><strong>Summary</strong></h2>
<p>There’s no question, closures are one of the core principles in Javascript. It brings developers huge benefits if they’re used in the right way, but many times they’re used in code unknowingly.</p>
<p>Therefore, you should catch the following points about closures:</p>
<ul>
<li><p>Every function has reserved space in the memory heap for closures.</p>
</li>
<li><p>Javascript engine will always make sure that functions have access to all of the variables outside the function.</p>
</li>
<li><p>When you’re processing an immense amount of data you can optimize memory usage.</p>
</li>
<li><p>When you’re working with sensitive data you can observe the principle of least privilege using encapsulation.</p>
</li>
<li><p>Closures can significantly improve the performance and security of your code.</p>
</li>
</ul>
<p><em>Thank you for reading.</em></p>
]]></content:encoded></item></channel></rss>