<?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[Ikram Hasan's Blog]]></title><description><![CDATA[Software engineer 🚀 with an immense thirst for learning new technologies 👌]]></description><link>https://blog.ikramhasan.com</link><generator>RSS for Node</generator><lastBuildDate>Tue, 12 May 2026 20:55:45 GMT</lastBuildDate><atom:link href="https://blog.ikramhasan.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[I made an app that turns your email inbox into tinder]]></title><description><![CDATA[Are you drowning in a sea of unread emails? Do you find yourself scrolling endlessly, trying to clean up the clutter? Managing your inbox can feel like a never-ending chore, but what if it could be as easy and fun as swiping through Tinder? That’s ex...]]></description><link>https://blog.ikramhasan.com/i-made-an-app-that-turns-your-email-inbox-into-tinder</link><guid isPermaLink="true">https://blog.ikramhasan.com/i-made-an-app-that-turns-your-email-inbox-into-tinder</guid><category><![CDATA[inbox zero]]></category><category><![CDATA[email]]></category><category><![CDATA[Mobile apps]]></category><category><![CDATA[iOS]]></category><category><![CDATA[Android]]></category><dc:creator><![CDATA[Ikram Hasan]]></dc:creator><pubDate>Mon, 10 Mar 2025 14:30:34 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1741596857569/5840e0eb-3be1-440c-9151-ca467b38cb70.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Are you drowning in a sea of unread emails? Do you find yourself scrolling endlessly, trying to clean up the clutter? Managing your inbox can feel like a never-ending chore, but what if it could be as easy and fun as swiping through Tinder? That’s exactly why I built <a target="_blank" href="https://inboxswipe.com/"><strong>InboxSwipe</strong></a> - an app that transforms your Gmail inbox into an intuitive <strong>swipe-based</strong> experience, making email management <strong>fast</strong>, <strong>fun</strong>, and <strong>effortless</strong>.</p>
<h2 id="heading-the-idea">💡 The Idea</h2>
<p>The idea for the app came to me during my college days, around 7-8 years ago when I missed an important job acceptance email due to my cluttered inbox. By the time I saw the email, it was already too late. I was thinking of ways to help me stay on top of my emails, and a tinder style swipe based ui was what I thought would be the best way to achieve this. The reason it took so long to start working on the project was because I wanted the app to be incredibly secure by default and didn’t know how to achieve this at that time. But now with more than 6 years of industry experience, being a senior software engineer, I thought it would be a good time to tackle this.</p>
<h2 id="heading-the-email-overload-problem">📩 The Email Overload Problem</h2>
<p>Let’s face it - email overload is a real struggle. Studies show that the <a target="_blank" href="https://www.campaignmonitor.com/resources/knowledge-base/how-many-emails-does-the-average-person-receive-per-day/#:~:text=Keeping%20all%20this%20in%20mind%2C%20experts%20generally%20agree%20that%20121%20business%20emails%20are%20sent%20and%20received%20each%20day.%20However%2C%20Radicati%20predicts%20that%2C%20by%20the%20end%20of%202019%2C%20that%20number%20will%20be%20closer%20to%20126.">average person receives over 120 emails per day</a>, and if you’re like most people, your inbox is probably cluttered with newsletters, promotions, spam, and important work emails buried in the mess. The traditional method of sorting emails- clicking checkboxes, selecting bulk actions, and manually unsubscribing - is slow and frustrating.</p>
<p>That’s where <a target="_blank" href="https://inboxswipe.com/"><strong>InboxSwipe</strong></a> comes in. Instead of wasting time clicking through menus, just swipe your way to a clean inbox.</p>
<h2 id="heading-how-inboxswipe-works">🎯 How InboxSwipe Works</h2>
<p><a target="_blank" href="https://inboxswipe.com/">InboxSwipe</a> <strong>gamifies email management</strong> with a <strong>Tinder-style card interface</strong>. Instead of a traditional email list, each email appears as a card on your screen, and you can take action with just a swipe:</p>
<ul>
<li><p><strong>Swipe Left ⬅️</strong> – Delete unwanted emails instantly</p>
</li>
<li><p><strong>Swipe Right ➡️</strong> – Mark emails as starred for easy access later</p>
</li>
<li><p><strong>Swipe Up ⬆️</strong> – Unsubscribe from newsletters with one tap</p>
</li>
<li><p><strong>Swipe Down ⬇️</strong> – Block spam senders forever</p>
</li>
</ul>
<p>No more tedious selections, no more overwhelming email lists - just simple, intuitive swiping.</p>
<h2 id="heading-customizable-swipes-for-maximum-efficiency">✨ Customizable Swipes for Maximum Efficiency</h2>
<p>One of the best things about <a target="_blank" href="https://inboxswipe.com/"><strong>InboxSwipe</strong></a> is that it’s <strong>fully customizable</strong>. You’re not stuck with default actions - you can assign swipes to match your workflow. Choose from actions like:</p>
<p>✅ <strong>Archive</strong> – Keep emails but remove them from your inbox<br />❌ <strong>Delete</strong> – Permanently remove emails with a single swipe<br />📩 <strong>Mark as Read</strong> – Instantly clear unread notifications<br />⭐ <strong>Mark as Starred</strong> – Highlight important messages<br />📌 <strong>Mark as Important</strong> – Prioritize crucial emails<br />🚫 <strong>Unsubscribe &amp; Delete All</strong> – Wipe out junk emails forever!<br />🗑️ <strong>Unsubscribe &amp; Delete Current</strong> – Remove only the latest email from a sender<br />🔕 <strong>Unsubscribe</strong> – Stop future emails without deleting past ones<br />🔒 <strong>Block</strong> – Prevent unwanted senders from ever contacting you again<br />🙅‍♂️ <strong>Do Nothing</strong> – Skip an email without taking action</p>
<p>This level of customization ensures <a target="_blank" href="https://inboxswipe.com/"><strong>InboxSwipe</strong></a> <strong>works exactly how you want it to</strong>, making email cleanup a seamless experience.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1741597690564/ffb9eb12-c2c2-408b-916a-3bb6a4470438.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-supports-multiple-gmail-accounts">📬 Supports Multiple Gmail Accounts</h2>
<p>If you manage multiple Gmail accounts, <a target="_blank" href="https://inboxswipe.com/"><strong>InboxSwipe</strong></a> makes it easy to switch and clean up all your inboxes in one place. Whether it’s personal, work, or business emails, InboxSwipe helps you <strong>declutter effortlessly</strong>.</p>
<h2 id="heading-powered-by-ai">🧠 Powered By AI</h2>
<p><a target="_blank" href="https://inboxswipe.com/"><strong>InboxSwipe</strong></a> has an <strong>AI powered reply feature</strong> that allows you to quickly jot down a reply for an email. This allows you to get through your emails very easily. You can also compose emails manually, for which we have a great block based editor right in the app. Keep in mind we do our best to strip any personally identifiable information before sending it to the AI, however, if you still don’t want to use this feature, it is completely optional. We don’t do any kind of AI processing without the user’s explicit consent.</p>
<h2 id="heading-try-inboxswipe-free-for-7-days">⏳ Try InboxSwipe Free for 7 Days!</h2>
<p><a target="_blank" href="https://inboxswipe.com/">InboxSwipe</a> offers a <strong>7-day free trial</strong> so you can experience the magic before committing. After that, you can choose between:</p>
<p>💰 <strong>Monthly Plan:</strong> $7.99/month<br />💰 <strong>Yearly Plan:</strong> $69.99/year (Get 3 months free!)</p>
<p>With <strong>unlimited swipes, unlimited accounts, unlimited ai replies, and a hassle-free inbox</strong> included in all the plans.</p>
<h2 id="heading-stay-on-top-of-your-inbox-with-daily-reminders">🔔 Stay on Top of Your Inbox With Daily Reminders</h2>
<p>Life gets busy, and it’s easy to forget about email maintenance. <a target="_blank" href="https://inboxswipe.com/"><strong>InboxSwipe</strong></a> <strong>sends you scheduled daily reminders</strong> to clean up your inbox so you never let emails pile up again. This feature is disabled by default and you can enable it by going to the settings page and choosing your preferred time to receive the notification.</p>
<h2 id="heading-secured-by-default">🛡️ Secured by default</h2>
<p>I built InboxSwipe with security and privacy in mind. It is not an afterthought.</p>
<ul>
<li><p>Your email content is never stored on our server/database. The app follows a zero knowledge strategy when it comes to fetching and sending your emails</p>
</li>
<li><p>Sensitive information is encrypted with AES-258-GSM encryption algorithm which provides both authentication and encryption</p>
</li>
<li><p>InboxSwipe passed the <a target="_blank" href="https://appdefensealliance.dev/casa">CASA</a> tier two security audit by <a target="_blank" href="https://tacsecurity.com/">TAC Security</a> which is a google endorsed security audit farm</p>
</li>
<li><p>You can opt-out of any kinds of anonymous analytics and error reporting right from the app with only one tap in settings.</p>
</li>
</ul>
<h2 id="heading-screenshots">📱 Screenshots</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1741599576366/f41a5415-ce26-4825-bb32-368f1987bb3a.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-download-inboxswipe-today">📥 Download InboxSwipe Today!</h2>
<p>If you’ve ever wished for a <strong>fast, intuitive, and enjoyable way to manage emails</strong>, <a target="_blank" href="https://inboxswipe.com/">InboxSwipe</a> is the answer. Say goodbye to clutter and hello to Inbox Zero with just a few swipes.</p>
<p>Download <a target="_blank" href="https://inboxswipe.com/">InboxSwipe</a> now and take control of your inbox! 🚀📩</p>
<hr />
<p>Website: <a target="_blank" href="https://inboxswipe.com">https://inboxswipe.com</a></p>
<p>App store: <a target="_blank" href="https://apps.apple.com/us/app/inboxswipe-reach-inbox-zero/id6742030436?platform=iphone">https://apps.apple.com/us/app/inboxswipe-reach-inbox-zero/id6742030436?platform=iphone</a></p>
<p>Play store: <a target="_blank" href="https://play.google.com/store/apps/details?id=com.ikramhasan.inbox_swipe">https://play.google.com/store/apps/details?id=com.ikramhasan.inbox_swipe</a></p>
<hr />
]]></content:encoded></item><item><title><![CDATA[GG: The Journey of Building and Marketing an App]]></title><description><![CDATA[Introduction
GG: Game Giveaways Notifier is a passion project of mine. The app shows you a comprehensive list of running game giveaways and notifies you through email (opt-in) and push notifications whenever a game goes free.
Current feature list:

I...]]></description><link>https://blog.ikramhasan.com/gg-the-journey-of-building-and-marketing-an-app</link><guid isPermaLink="true">https://blog.ikramhasan.com/gg-the-journey-of-building-and-marketing-an-app</guid><category><![CDATA[app development]]></category><category><![CDATA[Flutter]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[Technical writing ]]></category><category><![CDATA[marketing]]></category><dc:creator><![CDATA[Ikram Hasan]]></dc:creator><pubDate>Wed, 06 Sep 2023 03:30:09 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1693955047432/36cda8dd-fa69-4b0b-bdf6-8e0d860a14ad.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-introduction">Introduction</h1>
<p><a target="_blank" href="https://play.google.com/store/apps/details?id=com.ikramhasan.free_games_giveaways">GG: Game Giveaways Notifier</a> is a passion project of mine. The app shows you a comprehensive list of running game giveaways and notifies you through email (opt-in) and push notifications whenever a game goes free.</p>
<p><strong>Current feature list:</strong></p>
<ul>
<li><p><strong>Instant Notifications 📲</strong>: Never miss a free game again! Get push notifications whenever a game goes free.</p>
</li>
<li><p><strong>Comprehensive List of Giveaways 📜</strong>: From free games to loot, coins, skins, and battle passes, see a detailed list of giveaways with instructions on how to redeem them.</p>
</li>
<li><p><strong>Save Money and Compete 🏆</strong>: Keep track of how much money you've saved through the app and compete in the leaderboard. Bragging rights guaranteed! The top user in the leaderboard has saved more than $6000 through the app.</p>
</li>
<li><p><strong>Clean and Minimal UI 🎨</strong>: Enjoy a nice, clean, and easy-to-navigate user interface.</p>
</li>
<li><p><strong>All Storefronts Covered 🏪</strong>: Whether it's Epic Games, Steam, <a target="_blank" href="http://itch.io">itch.io</a>, or others, the app got you covered.</p>
</li>
<li><p><strong>Open Source and Actively Maintained 💻</strong>: GG is open source and constantly updated.</p>
</li>
<li><p><strong>Email Notifications 📧</strong>: Soon, you'll be able to receive notifications via email too! It's a toggle in the app and the feature is complete. Just ironing out some bugs.</p>
</li>
</ul>
<h1 id="heading-conceptualizing-the-app">Conceptualizing the app</h1>
<h3 id="heading-idea">Idea</h3>
<p>I'm a game hoarder and I've managed to get more than 500 games for free through giveaways and saved thousands of dollars. This obsession began when I discovered the <a target="_blank" href="https://www.epicgames.com/">EPIC!</a> games store, which offers a free game every week. I religiously logged in to redeem each giveaway, but occasionally, life got in the way, and I missed out on some incredible titles. Frustrated by this recurring problem, I was venting with my friend, and that's when the seed of an idea was planted in my mind. While competing to see who could accumulate the most free games, we both realized the need for a solution that would ensure we never missed out on a giveaway again. It was during one of our friendly gaming battles that my friend suggested I create an app to solve this problem once and for all. The idea resonated with me, and I couldn't shake off the excitement of bringing this concept to life.</p>
<h3 id="heading-research">Research</h3>
<p>Before diving headfirst into the development of the app, thorough research was essential to determine its feasibility. My primary objective was to find a reliable source that could provide a comprehensive list of ongoing game giveaways. Initially, I considered web scraping as a potential solution, but the daunting task of implementing custom scrapers for each storefront made me reconsider. Another approach I contemplated was fetching Twitter posts with the hashtag "giveaway." However, this method proved to be problematic, as the results were often flooded with scams and unreliable information. In hindsight, I'm grateful that I didn't pursue this direction, especially considering that Twitter has recently implemented a paywall for their API. After extensive exploration, I stumbled upon the ideal solution that met all my requirements: the <a target="_blank" href="http://gamerpower.com">gamerpower.com</a> API. This elegant and user-friendly API provided a ready-made solution for accessing a comprehensive list of running giveaways. Not only did it save me from the complexities of web scraping or unreliable Twitter searches, but it also aligned perfectly with the vision I had for the app (PS. they even featured my app in their API section).</p>
<h3 id="heading-choosing-the-right-technologies">Choosing the right technologies</h3>
<p>I wanted the app to be cross-platform. Since I already knew <a target="_blank" href="https://flutter.dev">Flutter</a>, that's what I've chosen as the app development framework. I'm using <a target="_blank" href="https://firebase.google.com/">Firebase</a> to authenticate users and store their data. An <a target="_blank" href="https://expressjs.com/">express.js</a> server hosted on <a target="_blank" href="https://railway.app">Railway</a> as my notification service. And recently, I've added <a target="_blank" href="https://admob.google.com/home/">Admob</a> to show ads to users, and <a target="_blank" href="https://shorebird.dev">Shorebird</a> for automatic code push.</p>
<h1 id="heading-building-gg-the-development-process">Building GG: The Development Process</h1>
<h3 id="heading-mobile-app">Mobile App</h3>
<p>I used <a target="_blank" href="https://flutter.dev">Flutter</a> to build the application. The development journey couldn't have been smoother. I was able to ship super fast without ever feeling like I was limited by the framework. I used a <a target="_blank" href="https://www.geeksforgeeks.org/domain-driven-design-ddd/">Domain Driven Design</a> pattern to develop the app. This made the code more maintainable. Everything was separated feature-wise. The app also uses <a target="_blank" href="https://bloclibrary.dev/">Flutter Bloc</a> to manage state.</p>
<h3 id="heading-database">Database</h3>
<p>The database that powers GG, is <a target="_blank" href="https://firebase.google.com/docs/firestore">Firestore Database</a>. This NoSQL database served as the backbone for storing user information and tracking their savings through the app. The leaderboard page utilized a paginated query to showcase the rankings of all users, providing a dynamic and engaging experience.</p>
<h3 id="heading-notification-api">Notification API</h3>
<p>To ensure timely notifications for new giveaways, I implemented a custom notification server. This server, running on a cron job, checks for new giveaways every hour and sends push notifications to users. This seamless integration allows users to stay up-to-date with the latest opportunities to save money on games. Here's how it works:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1693955224541/06958dc9-d285-41f3-9b69-318ead71f78d.png" alt /></p>
<p>The code is provided at the bottom of this article if you're interested.</p>
<h1 id="heading-testing-and-refining-gg">Testing and Refining GG</h1>
<p>During the initial stages of development, I included a section in the app where users could directly email me to report bugs or request new features. This user feedback was invaluable as it provided real-life use cases that no amount of testing could fully anticipate. It allowed me to address issues and make improvements based on actual user experiences. However, as the app gained popularity and attracted a significant user base, the influx of emails became overwhelming. Consequently, I made the decision to remove the direct email reporting feature from the app. Since then, I've replaced email reporting with two things: Automated crash reporting through <a target="_blank" href="https://firebase.google.com/docs/crashlytics">Firebase Crashlytics</a>. This transition proved to be a pivotal change, as it provided me with detailed stack traces and crash reports. With this information at hand, I could quickly identify and address issues, ensuring a smoother and more stable user experience. I also added a <a target="_blank" href="https://tally.so">Tally</a> form to let users submit a bug report through a form.</p>
<h1 id="heading-launching-gg-marketing-and-promotion">Launching GG: Marketing and Promotion</h1>
<h2 id="heading-grassroots-and-organic-marketing">Grassroots and Organic Marketing</h2>
<p>From the very beginning, the marketing and promotion strategy for GG has been grassroots and organic, relying on the power of online communities and word-of-mouth. Traditional advertising has not been a part of the strategy, as the focus has been on engaging directly with gamers in their online spaces.</p>
<h2 id="heading-engaging-with-online-communities">Engaging with Online Communities</h2>
<p>To connect with the gaming community, I actively participate in relevant subreddits, gaming websites, and social media platforms. By immersing myself in these communities, I have been able to understand the needs of users and continuously improve GG based on their feedback. Reddit, in particular, has proven to be an effective platform for sharing GG in gaming, giveaway, and app development subreddits, sparking discussions and gaining valuable insights.</p>
<h2 id="heading-harnessing-the-power-of-social-media">Harnessing the Power of Social Media</h2>
<p>Social media platforms like Twitter and Facebook have played a crucial role in promoting GG. Through these channels, I keep users updated on the latest giveaways, provide sneak peeks of upcoming features, and foster friendly competition on the leaderboard. This direct engagement with the user base has helped build a loyal community around GG.</p>
<h2 id="heading-future-marketing-strategies">Future Marketing Strategies</h2>
<p>Looking ahead, I am excited to explore new avenues for connecting with the gaming community and spreading the word about GG. I am considering paid promotions to gauge their effectiveness in reaching a wider audience and driving app adoption.</p>
<h1 id="heading-performance-amp-metrics">Performance &amp; Metrics</h1>
<h2 id="heading-monitoring-app-performance-and-user-engagement">Monitoring App Performance and User Engagement</h2>
<p>Since the initial launch of GG, I have placed great importance on monitoring the app's performance and user engagement. To achieve this, I utilize Google Analytics to track various metrics such as monthly active users, total engagement time, ad clicks, and more. This data provides valuable insights into user behavior and helps me make informed decisions to enhance the app's performance and user experience.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1693955248680/91ce1515-c025-42ca-8078-48959f2f08ea.png" alt /></p>
<h2 id="heading-tracking-user-acquisition-and-geographical-performance">Tracking User Acquisition and Geographical Performance</h2>
<p>One key aspect of monitoring app performance is tracking user acquisition and understanding where our users are coming from. Google Analytics allows me to analyze the sources of app installations and identify the most effective channels for user acquisition. Additionally, it provides insights into the geographical performance of the app, enabling me to identify countries where GG is performing exceptionally well.</p>
<h2 id="heading-localization-for-targeted-countries">Localization for Targeted Countries</h2>
<p>To further optimize user acquisition and engagement, I have implemented localizations for the top-performing countries. By tailoring the app's content and language to specific regions, I have been able to attract more users and provide a more personalized experience. This strategic approach has proven effective in expanding the user base and increasing overall engagement.</p>
<h1 id="heading-lessons-learned-amp-future-plans">Lessons Learned &amp; Future Plans</h1>
<p>The journey of developing GG: Games and Giveaways has been a valuable learning experience. Throughout the process, I gained insights into various aspects of app development, user engagement, and problem-solving. However, there were also challenges along the way. One significant hurdle was when the app was unexpectedly removed from the Play Store due to a copyright claim regarding the app icon. Despite using an icon from <a target="_blank" href="https://icons8.com/">Icons8</a> and crediting the artist, the app was removed without prior notice. This incident resulted in the loss of traction and a setback for the app. Looking ahead, however, I have exciting plans to further enhance GG and expand its user base. Some of the features that are already in the pipeline include:</p>
<ol>
<li><p><strong>More Localization Support</strong>: To cater to a wider audience, I plan to add additional localization support, ensuring that the app is accessible and user-friendly for users from different regions.</p>
</li>
<li><p><strong>In-App Purchases to Remove Ads</strong>: To provide users with an ad-free experience and support the sustainability of the app, I will introduce in-app purchases that allow users to remove ads.</p>
</li>
<li><p><strong>Enhanced User Interaction</strong>: To foster a sense of community and engagement, I am planning to introduce features that enable users to interact with each other. This will create a more dynamic and social experience within the app.</p>
</li>
</ol>
<h1 id="heading-conclusion">Conclusion</h1>
<p><a target="_blank" href="https://play.google.com/store/apps/details?id=com.ikramhasan.free_games_giveaways">GG: Game Giveaways Notifier</a> has been a labor of love from day one. I've never thought of making money while developing it. I don't know how many people use their own apps, but I've been using GG since day one. I'm super proud of what I was able to achieve with it. It has been a rewarding experience to see the app grow and evolve, and I hope that you find it as valuable and enjoyable as I do. I would greatly appreciate any feedback you may have on both the blog series and the app itself. Your input is invaluable in helping me improve and refine GG. Thank you for taking the time to read through this blog series, and I look forward to hearing from you.</p>
<hr />
<p>Try out the app here: <a target="_blank" href="https://play.google.com/store/apps/details?id=com.ikramhasan.free_games_giveaways">GG: Game Giveaways Notifier</a></p>
<p>If you want to connect with me, you can find my socials here: <a target="_blank" href="http://ikramhasan.com">ikramhasan.com</a></p>
<hr />
<p>The custom notification service:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> express, { Express } <span class="hljs-keyword">from</span> <span class="hljs-string">'express'</span>
<span class="hljs-keyword">import</span> cron <span class="hljs-keyword">from</span> <span class="hljs-string">'node-cron'</span>
<span class="hljs-keyword">import</span> fetch <span class="hljs-keyword">from</span> <span class="hljs-string">'node-fetch'</span>
<span class="hljs-keyword">import</span> admin <span class="hljs-keyword">from</span> <span class="hljs-string">'firebase-admin'</span>
<span class="hljs-keyword">import</span> serviceAccount <span class="hljs-keyword">from</span> <span class="hljs-string">'../serviceAccountKey.json'</span> assert { <span class="hljs-keyword">type</span>: <span class="hljs-string">'json'</span> }
<span class="hljs-keyword">import</span> { Resend } <span class="hljs-keyword">from</span> <span class="hljs-string">'resend'</span>

<span class="hljs-keyword">const</span> app: Express = express()
<span class="hljs-keyword">const</span> port: <span class="hljs-built_in">string</span> | <span class="hljs-built_in">number</span> = process.env.PORT || <span class="hljs-number">3000</span>

<span class="hljs-keyword">interface</span> Schema {
  collection: <span class="hljs-built_in">string</span>;
  doc: <span class="hljs-built_in">string</span>;
  map: <span class="hljs-function">(<span class="hljs-params">data: <span class="hljs-built_in">any</span></span>) =&gt;</span> <span class="hljs-built_in">any</span>;
}

<span class="hljs-keyword">interface</span> NotifyMethod {
  notify: <span class="hljs-function">(<span class="hljs-params">data: <span class="hljs-built_in">any</span></span>) =&gt;</span> <span class="hljs-built_in">void</span>;
}

<span class="hljs-keyword">class</span> CustomLogger {
  isLoggingEnabled: <span class="hljs-built_in">boolean</span>;

  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"></span>) {
    <span class="hljs-built_in">this</span>.isLoggingEnabled = process.env.NODE_ENV === <span class="hljs-string">'development'</span>;
  }

  log(message: <span class="hljs-built_in">string</span>, data?: <span class="hljs-built_in">any</span>): <span class="hljs-built_in">void</span> {
    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.isLoggingEnabled) {
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`[<span class="hljs-subst">${<span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>().toISOString()}</span>] <span class="hljs-subst">${message}</span>`</span>, data);
    }
  }

  error(message: <span class="hljs-built_in">string</span>, error: <span class="hljs-built_in">any</span>): <span class="hljs-built_in">void</span> {
    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.isLoggingEnabled) {
      <span class="hljs-built_in">console</span>.error(<span class="hljs-string">`[<span class="hljs-subst">${<span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>().toISOString()}</span>] <span class="hljs-subst">${message}</span>`</span>, error);
    }
  }
}

<span class="hljs-keyword">class</span> NotifyService {
  apiUrl: <span class="hljs-built_in">string</span>;
  schema: Schema;
  notifyMethods: NotifyMethod[];
  logger: CustomLogger;
  db: FirebaseFirestore.Firestore;

  <span class="hljs-keyword">constructor</span>(<span class="hljs-params">apiUrl: <span class="hljs-built_in">string</span>, schema: Schema, notifyMethods: NotifyMethod[], logger: CustomLogger</span>) {
    <span class="hljs-built_in">this</span>.apiUrl = apiUrl;
    <span class="hljs-built_in">this</span>.schema = schema;
    <span class="hljs-built_in">this</span>.notifyMethods = notifyMethods;
    <span class="hljs-built_in">this</span>.logger = logger;

    admin.initializeApp({
      credential: admin.credential.cert(serviceAccount),
    })

    <span class="hljs-built_in">this</span>.db = admin.firestore()
  }

  <span class="hljs-keyword">async</span> notify(): <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">void</span>&gt; {
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(<span class="hljs-built_in">this</span>.apiUrl)
      <span class="hljs-keyword">const</span> dataList: <span class="hljs-built_in">any</span>[] = <span class="hljs-keyword">await</span> response.json() <span class="hljs-keyword">as</span> <span class="hljs-built_in">any</span>

      <span class="hljs-keyword">const</span> latestData = dataList[<span class="hljs-number">0</span>]

      <span class="hljs-keyword">const</span> dataFromDB = (<span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.db.collection(<span class="hljs-built_in">this</span>.schema.collection).doc(<span class="hljs-built_in">this</span>.schema.doc).get()).data()

      <span class="hljs-keyword">if</span> (dataFromDB &amp;&amp; dataFromDB.id === latestData.id) {
        <span class="hljs-built_in">this</span>.logger.log(<span class="hljs-string">'Data already updated'</span>)
      } <span class="hljs-keyword">else</span> {
        <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.db.collection(<span class="hljs-built_in">this</span>.schema.collection).doc(<span class="hljs-built_in">this</span>.schema.doc).update(<span class="hljs-built_in">this</span>.schema.map(latestData))
        <span class="hljs-built_in">this</span>.logger.log(<span class="hljs-string">'Updated data'</span>, latestData)
        <span class="hljs-comment">// Send notification</span>
        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> method <span class="hljs-keyword">of</span> <span class="hljs-built_in">this</span>.notifyMethods) {
          method.notify(latestData);
        }
      }
    } <span class="hljs-keyword">catch</span> (error) {
      <span class="hljs-built_in">this</span>.logger.error(<span class="hljs-string">'Error in notify function'</span>, error)
    }
  }

  start(interval = <span class="hljs-string">'0 */12 * * *'</span>): <span class="hljs-built_in">void</span> {
    cron.schedule(interval, <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-built_in">this</span>.logger.log(<span class="hljs-string">'Started a new job.'</span>)
      <span class="hljs-built_in">this</span>.notify().catch(<span class="hljs-function">(<span class="hljs-params">error</span>) =&gt;</span> <span class="hljs-built_in">this</span>.logger.error(<span class="hljs-string">'Error in cron job'</span>, error))
    })

    app.listen(port, <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-built_in">this</span>.logger.log(<span class="hljs-string">`App listening on port <span class="hljs-subst">${port}</span>`</span>)
    })
  }
}

<span class="hljs-keyword">class</span> FirebaseNotifyMethod <span class="hljs-keyword">implements</span> NotifyMethod {
  topic: <span class="hljs-built_in">string</span>;
  logger: CustomLogger;

  <span class="hljs-keyword">constructor</span>(<span class="hljs-params">topic = 'all', logger: CustomLogger</span>) {
    <span class="hljs-built_in">this</span>.topic = topic;
    <span class="hljs-built_in">this</span>.logger = logger;
  }

  notify(data: <span class="hljs-built_in">any</span>): <span class="hljs-built_in">void</span> {
    <span class="hljs-keyword">const</span> message = {
      notification: {
        title: data.title,
        body: data.description,
        image: data.thumbnail,
      },
      topic: <span class="hljs-built_in">this</span>.topic,
    }

    admin
      .messaging()
      .send(message)
      .then(<span class="hljs-function">(<span class="hljs-params">response</span>) =&gt;</span> {
        <span class="hljs-built_in">this</span>.logger.log(<span class="hljs-string">'Successfully sent message:'</span>, response)
      })
      .catch(<span class="hljs-function">(<span class="hljs-params">error</span>) =&gt;</span> {
        <span class="hljs-built_in">this</span>.logger.error(<span class="hljs-string">'Error sending message:'</span>, error)
      })
  }
}

<span class="hljs-keyword">interface</span> EmailConfig {
  resendKey: <span class="hljs-built_in">string</span>;
  <span class="hljs-keyword">from</span>: <span class="hljs-built_in">string</span>;
  to: <span class="hljs-built_in">string</span>;
}

<span class="hljs-keyword">class</span> EmailNotifyMethod <span class="hljs-keyword">implements</span> NotifyMethod {
  resend: Resend;
  <span class="hljs-keyword">from</span>: <span class="hljs-built_in">string</span>;
  to: <span class="hljs-built_in">string</span>;

  <span class="hljs-keyword">constructor</span>(<span class="hljs-params">emailConfig: EmailConfig</span>) {
    <span class="hljs-built_in">this</span>.resend = <span class="hljs-keyword">new</span> Resend(emailConfig.resendKey);
    <span class="hljs-built_in">this</span>.from = emailConfig.from;
    <span class="hljs-built_in">this</span>.to = emailConfig.to;
  }

  notify(data: <span class="hljs-built_in">any</span>): <span class="hljs-built_in">void</span> {
    <span class="hljs-built_in">this</span>.resend.emails.send({
      <span class="hljs-keyword">from</span>: <span class="hljs-built_in">this</span>.from,
      to: <span class="hljs-built_in">this</span>.to,
      subject: <span class="hljs-string">`Notification: <span class="hljs-subst">${data.title}</span>`</span>,
      html: <span class="hljs-string">`&lt;p&gt;<span class="hljs-subst">${<span class="hljs-built_in">JSON</span>.stringify(data)}</span>&lt;/p&gt;`</span>,
      attachments: [
        {
          filename: <span class="hljs-string">'thumbnail.png'</span>,
          path: data.thumbnail,
        },
      ],
    })
  }
}

<span class="hljs-comment">// Usage</span>
<span class="hljs-keyword">const</span> logger = <span class="hljs-keyword">new</span> CustomLogger();

<span class="hljs-keyword">const</span> service = <span class="hljs-keyword">new</span> NotifyService(
  <span class="hljs-string">'https://www.gamerpower.com/api/giveaways'</span>,
  {
    collection: <span class="hljs-string">'giveaways'</span>,
    doc: <span class="hljs-string">'notify'</span>,
    map: <span class="hljs-function">(<span class="hljs-params">data: <span class="hljs-built_in">any</span></span>) =&gt;</span> ({
      id: data.id,
      title: data.title,
      description: data.description,
      thumbnail: data.thumbnail,
      open_giveaway: data.open_giveaway,
    }),
  },
  [
    <span class="hljs-keyword">new</span> FirebaseNotifyMethod(<span class="hljs-string">'all'</span>, logger),
    <span class="hljs-keyword">new</span> EmailNotifyMethod({
      resendKey: <span class="hljs-string">'RESEND_API_KEY_HERE'</span>,
      <span class="hljs-keyword">from</span>: <span class="hljs-string">'no-reply@ikramhasan.com'</span>,
      to: <span class="hljs-string">'[EMAIL_SUBSCRIBERS_LIST]'</span>,
    }),
  ],
  logger
);
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Automagically translate your entire app with just one command using AI ✨]]></title><description><![CDATA[First thing first
Thank you so much for all your support on my very first blog, titled How I Made a TicTacToe Game That You Cannot Beat 🙅‍♂️. It was featured both in hashnode and in daily.dev, and viewed almost 6000 times.
Introduction
In an increas...]]></description><link>https://blog.ikramhasan.com/automagically-translate-your-entire-app-with-just-one-command-using-ai</link><guid isPermaLink="true">https://blog.ikramhasan.com/automagically-translate-your-entire-app-with-just-one-command-using-ai</guid><category><![CDATA[Python]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[openai]]></category><category><![CDATA[chatgpt]]></category><category><![CDATA[localization]]></category><dc:creator><![CDATA[Ikram Hasan]]></dc:creator><pubDate>Sun, 13 Aug 2023 15:20:08 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/5u6bz2tYhX8/upload/1753f9d517c0f04cb38563f9a750cd54.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-first-thing-first">First thing first</h1>
<p>Thank you so much for all your support on my very first blog, titled <a target="_blank" href="https://blog.ikramhasan.com/how-i-made-a-tictactoe-game-that-you-cannot-beat">How I Made a TicTacToe Game That You Cannot Beat 🙅‍♂️</a>. It was featured both in <a target="_blank" href="https://hashnode.com/community">hashnode</a> and in <a target="_blank" href="http://daily.dev">daily.dev</a>, and viewed almost 6000 times.</p>
<h1 id="heading-introduction">Introduction</h1>
<p>In an increasingly globalized world, language translation is more important than ever. Localization is a key aspect in every app if you want to target users from all across the world. However, current solutions like <a target="_blank" href="https://localazy.com/">localazy</a>, <a target="_blank" href="https://lokalise.com/">localise</a>, and <a target="_blank" href="https://www.locize.app/">locize</a> can be very pricy costing you upwards of 500 dollars per month just to provide localization support to your app. Almost all of them charge you on a word basis which can get costly over time with multiple languages. And manually translating your app also takes a lot of time and money as you need to hire someone to translate them for you. To mitigate these issues, I used to primarily use <a target="_blank" href="https://translate.google.com/">Google Translate</a> to translate my apps but this is also not ideal as the translation quality is very poor and lacks important app-specific context. But after trying out <a target="_blank" href="https://chat.openai.com/">chatgpt</a> I was really impressed with the translation quality. It performed as well as any native translator would. Hence, I made a simple Python script to generate translation files from one single base language file (en.json) in real-time and today I'd like to share the process of creating such a script with you.</p>
<h1 id="heading-demo">Demo</h1>
<p><img src="https://github.com/ikramhasan/Onubad-Translation-Automation-with-AI/blob/main/docs/assets/demo.gif?raw=true" alt="Git" /></p>
<p>Before I explain how I made this tool, let me show you how you can run it on your machine first.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<ol>
<li><p><a target="_blank" href="https://python.org">Python</a></p>
</li>
<li><p>OpenAI API key that can be generated from <a target="_blank" href="https://platform.openai.com/account/api-keys">here</a></p>
</li>
<li><p>Git</p>
</li>
</ol>
<h2 id="heading-setting-up-the-project">Setting up the project</h2>
<h3 id="heading-i-clone-the-repository">I. Clone the repository</h3>
<p>Run the command <code>git clone</code> <a target="_blank" href="https://github.com/ikramhasan/Onubad-Translation-Automation-with-AI.git"><code>https://github.com/ikramhasan/Onubad-Translation-Automation-with-AI.git</code></a> to clone the project on your device (Make sure you have git installed)</p>
<h3 id="heading-ii-install-necessary-libraries">II. Install necessary libraries</h3>
<p>Run <code>pip install -r requirements.txt</code> in the project folder to install all the required libraries. If the above command doesn't work, Try running <code>pip3 install -r requirements.txt</code> . If that also fails, make sure you have Python installed and added to your PATH</p>
<h3 id="heading-iii-add-the-openai-api-key-in-your-config-file-optional">III. Add the OpenAI API key in your config file (Optional)</h3>
<p>Add your API key to the config so that you don't have to manually add it every time you run the project. In the project folder you'll find a file called <code>config.json</code> that looks like this:</p>
<pre><code class="lang-json">{
<span class="hljs-attr">"openai_api_key"</span>: <span class="hljs-string">"your-api-key"</span>
}
</code></pre>
<p>Here, replace the "your-api-key" text with the API key you generated. It should look something like this: <code>sk-XXX...</code></p>
<h2 id="heading-run-the-script">Run the script</h2>
<p>Now that the setup is complete, run <code>python</code> <a target="_blank" href="http://onubad.py"><code>onubad.py</code></a> or <code>python3</code> <a target="_blank" href="http://onubad.py"><code>onubad.py</code></a> to run the script. Follow the instruction given in the terminal and you should have a translated JSON file in no time.</p>
<blockquote>
<p>Note: Keep in mind that the <code>gpt-4</code> model may not be available to you. You need to join a waitlist for that. In that case, using <code>gpt-4</code> will give you an error. use the model <code>gpt-3.5-turbo</code> instead.</p>
</blockquote>
<p>After running the command, you should see a newly translated JSON file in your directory (example: bd.json)</p>
<h1 id="heading-how-it-works">How it works</h1>
<p>Every time you run the command this is the step the program goes through to translate your files:</p>
<ol>
<li><p>Take the directory of the base translation file (ex: en.json) from the user</p>
</li>
<li><p>List all the JSON files in that directory and ask the user to select the base file that they want to translate</p>
</li>
<li><p>Ask the user to enter the name of the output file (bd.json)</p>
</li>
<li><p>Duplicate the base file (en.json) and rename it to the output file name (bd.json)</p>
</li>
<li><p>Iterate over each key of the JSON file and get the value</p>
</li>
<li><p>Translate the value with a custom prompt using OpenAI API</p>
</li>
<li><p>Update the output JSON file with the newly translated value</p>
</li>
<li><p>Repeat from step 5 until the entire file is translated</p>
</li>
</ol>
<h1 id="heading-dive-into-the-python-script">Dive into the Python script</h1>
<p>Now let's look at how the script is able to do these tasks. The codebase is divided into different modules such as <code>io_service.py</code>, <code>openai_service.py</code> and <code>prompt_engine.py</code> to increase maintainability. Purpose of each module:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Module</td><td>Description</td></tr>
</thead>
<tbody>
<tr>
<td><code>io_service.py</code></td><td>This module is responsible for all the input/output operations such as taking input from the user, listing all the files in a directory, reading and writing JSON files, etc. It uses the library <a target="_blank" href="https://github.com/Textualize/rich">rich</a> under the hood to show the prompt and take input</td></tr>
<tr>
<td><code>openai_service.py</code></td><td>This is where we call the OpenAI API to translate the texts. In the future, we may have more services adding support for other LLMs</td></tr>
<tr>
<td><code>prompt_engine.py</code></td><td>This is the file that returns us the prompt that we send to OpenAI to translate the files. Currently only has one prompt inside. This was created to provide an easy way to switch prompts without much hassle (Let me know if you have a better prompt for creating translations)</td></tr>
<tr>
<td><a target="_blank" href="http://onubad.py"><code>onubad.py</code></a></td><td>The main file that you need to run. This file imports all the modules and lets you call them from one single function for simplicity.</td></tr>
</tbody>
</table>
</div><h1 id="heading-limitations-and-future-works">Limitations and future works</h1>
<p>This is a very basic script that was made to match my personal needs. It is not yet suitable to be used in general. To be able to use it under any environment and project I need to add the following features:</p>
<ul>
<li><p>Add support for nested JSON keys</p>
</li>
<li><p>Save the progress so that the user doesn't have to translate the entire file again when a new key is added</p>
</li>
<li><p>Add support for other LLMs</p>
</li>
<li><p>Turn it into a package so that we can publish it to pip and users can use it from the command line without running it from the source.</p>
</li>
</ul>
<h1 id="heading-thank-you">Thank you</h1>
<p>Thank you for reading the entire article. If you want to connect with me on my socials, you can the links here: <a target="_blank" href="http://ikramhasan.onetapfolio.com">ikramhasan.onetapfolio.com</a></p>
<hr />
<p>The code for this project can be found here: <a target="_blank" href="https://github.com/ikramhasan/Onubad-Translation-Automation-with-AI">Onubad-Translation-Automation-with-AI</a></p>
]]></content:encoded></item><item><title><![CDATA[How I Made a TicTacToe Game That You Cannot Beat 🙅‍♂️]]></title><description><![CDATA[For reasons I cannot remember now, I once searched tic tac toe on Google. I noticed that when you search it, Google gives you a game board where you can play the game. You can even choose the difficulty. What spiked my interest the most is, that ther...]]></description><link>https://blog.ikramhasan.com/how-i-made-a-tictactoe-game-that-you-cannot-beat</link><guid isPermaLink="true">https://blog.ikramhasan.com/how-i-made-a-tictactoe-game-that-you-cannot-beat</guid><category><![CDATA[Game Development]]></category><category><![CDATA[algorithms]]></category><category><![CDATA[Artificial Intelligence]]></category><category><![CDATA[Flutter]]></category><category><![CDATA[General Programming]]></category><dc:creator><![CDATA[Ikram Hasan]]></dc:creator><pubDate>Tue, 26 Apr 2022 16:10:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1650989303399/6-QoPOhaK.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>For reasons I cannot remember now, I once searched <a target="_blank" href="https://www.google.com/search?q=tic+tac+toe">tic tac toe on Google</a>. I noticed that when you search it, Google gives you a game board where you can play the game. You can even choose the difficulty. What spiked my interest the most is, that there was a difficulty option named "Impossible". It completely blew my mind that there is a way to make sure you never lose. I became so fascinated that I started to dig deeper. And that's how I ended up here.</p>
<h1 id="heading-dig-deeper">Dig Deeper 🤨</h1>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1650984159418/hG4um6VTC.gif" alt="InceptionDeeperGIF.gif" /></p>
<p>I found that the secret ingredient for such a bold claim was a very simple algorithm that can be written in less than 30 lines of code. It's called the <a target="_blank" href="https://en.wikipedia.org/wiki/Minimax">MiniMax</a> algorithm. </p>
<blockquote>
<p>Mini-max algorithm is a recursive or backtracking algorithm which is used in decision-making and game theory. It provides an optimal move for the player assuming that opponent is also playing optimally - JavaPoint</p>
</blockquote>
<p>As soon as I saw that it was not actually a machine learning solution, but rather an algorithm, I became very demotivated. </p>
<p>Why?</p>
<h1 id="heading-confronting-my-fears">Confronting my fears 💪</h1>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1650987994495/Jz0epyp9X.gif" alt="YodaStarWarsGIF.gif" /></p>
<p>Because it was the same time I took the Algorithms course at my university. And I did miserably. I did not want anything to do with algorithms. I was planning to recreate the impossible tic tac toe game but was not sure if I'd learn an algorithm for it. On one hand, this could be a great opportunity for me to lose my fear of algorithms, on the other hand, it would mean tackling something I feared most of my life. </p>
<p>That's when I found <a target="_blank" href="https://www.youtube.com/watch?v=l-hh51ncgDI">this video</a> by <a target="_blank" href="https://www.youtube.com/channel/UCmtyQOKKmrMVaKuRXz02jbQ">Sebastian Lague</a>, one of my most favorite YouTubers. This video really made me think I can complete the project if I tried. And I did. The rest is a story of sheer perseverance, and an eagerness to improve myself. </p>
<h1 id="heading-hard-decisions">Hard Decisions 🤔</h1>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1650988027444/bxkQlOit-8.gif" alt="WhenYouMakeABigMistakeGIF.gif" /></p>
<p>The first decision I needed to make is what I'm going to use for the frontend. Basically displaying the game board and handling the user interactions. I knew it had to be a cross-platform solution. Because I had a play store account where I want to publish the app as a technical project, but also wanted to host it on the web so that my friends could play it easily without downloading an app. My first choice was <a target="_blank" href="https://unity.com/">Unity</a>. It passed all my requirements and is a solid framework for game development. But it was also very overkill for a simple game like this. Not to mention, the learning curve is also very high. I undertook this project to learn about algorithms. I didn't want to learn game development as well. This is why I decided to go with <a target="_blank" href="https://flutter.dev/">Flutter</a>.</p>
<h1 id="heading-why-flutter">Why Flutter❓</h1>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1650988523425/QHTOoo54-.gif" alt="WhyGIF.gif" /></p>
<p>Because I already knew the framework well enough and it supports more platforms than Unity. I realized it would take me significantly less time to develop the frontend with Flutter. Now, you may be thinking, if Flutter is so great, why wasn't this your first choice? Because Flutter is primarily an application development framework. It never occurred to me that, a tic tac toe game board is nothing but some grids and common app UI elements. Flutter is great for them. It can also take any type of interaction from the user, be it single taps, press and hold, or even gestures. </p>
<p>After I had chosen my framework of choice, it was now time to build the UI. And surprisingly, It only took me around 2 hours to do so. I then watched <a target="_blank" href="https://www.youtube.com/watch?v=trKjYdBASyQ">this video</a> by another personal hero of mine, <a target="_blank" href="https://www.youtube.com/channel/UCvjgXvBlbQiydffZU7m1_aw">Daniel Shiffman from the Coding Train</a>. His video really helped me understand the implementation, and edge cases for the algorithm. </p>
<h1 id="heading-the-not-so-fun-part">The not so fun part 😒</h1>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1650990825624/x5RXbK6D6.gif" alt="UnamusedGIF.gif" /></p>
<p>Although it took me a very short time to create the UI, I wish I could say the same for the algorithm. The programming language I'm using, called <a target="_blank" href="https://dart.dev/">Dart</a>, is actually very similar to javascript, the language Dan used to create the AI. So in my mind, I thought it was going to be a very easy task. But boy was I wrong. While trying to code, I faced the infamous <a target="_blank" href="https://en.wikipedia.org/wiki/Stack_overflow">StackOverflow</a> error for the first time in my life. And this has to be one of the scariest errors I've gotten in my life. The program would take up so much resources that I had no option but to shut down my then crappy PC. But one bug at a time, I managed to fix all of them. But it was not smooth sailing yet.</p>
<h1 id="heading-optimizations">Optimizations ⚡</h1>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1650990806472/M0OzE8-C5.gif" alt="CatComputerGIF.gif" /></p>
<p>I managed to complete the project finally. I could play the game and the AI would check for all the possible moves ahead and choose the best one. But there was one problem. After I place my first "X", the AI had to check for 255,168 possible steps ahead (If I'm not mistaken). This meant that I had to wait a long time for the AI to complete the calculations and choose the best option. Which, not to mention, was not fun at all. This meant only one thing. I had to optimize the algorithm. That's when I learned about <a target="_blank" href="https://en.wikipedia.org/wiki/Alpha%E2%80%93beta_pruning">alpha-beta pruning</a>. </p>
<blockquote>
<p>Alpha-beta pruning is a search algorithm that seeks to decrease the number of nodes that are evaluated by the minimax algorithm in its search tree - Wikipedia</p>
</blockquote>
<p>In layman's terms, it drastically reduced the number of steps the AI has to check. And believe it or not, even this wasn't enough for me. I then learned about <a target="_blank" href="https://dart.dev/guides/language/concurrency">Isolates</a>. This is actually a fancy way of saying I implemented multithreading. This would allow me to not block the UI while the calculation is going on in the background since both these tasks are handled in separate threads. After all of these was done, the project was finally complete. I created a tic tac toe game that no one can beat. And I cannot explain how happy I was. </p>
<h1 id="heading-taste-of-success">Taste of success 🏆</h1>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1650988320836/DzaemBFB_.gif" alt="SmellsLikeSuccessAndrewMcfarlaneGIF.gif" /></p>
<p>I undertook a challenge, did extensive research, defeated one of my biggest fears, and successfully completed the project. I know I'm making it sound like I'm describing the plot of <a target="_blank" href="https://www.imdb.com/title/tt1396484/?ref_=kw_li_tt">IT (2017)</a>, but that's what it felt like to me. </p>
<p>Thank you very much if you came this far. This is the first time I wrote something for myself. So your thoughts and criticisms are much appreciated. Don't forget to leave a comment.</p>
<hr />
<h1 id="heading-try-it-out">Try it out! 😌</h1>
<ul>
<li><a target="_blank" href="https://impossible-tictactoe.web.app/">The website</a> (It may take a while to load)</li>
<li><a target="_blank" href="https://play.google.com/store/apps/details?id=com.ikramhasan.tic_tac_toe">The app</a></li>
</ul>
<hr />
<p>The actual algorithm if you are interested:</p>
<pre><code class="lang-dart">minimax(board, depth, isMaximizingPlayer) {
    checkCount++;
    <span class="hljs-keyword">var</span> result = checkAIWinner(board);
    <span class="hljs-keyword">if</span> (result != <span class="hljs-string">'NONE'</span>) {
      <span class="hljs-keyword">return</span> scores[result];
    }

    <span class="hljs-keyword">if</span> (isMaximizingPlayer) {
      <span class="hljs-built_in">int</span> bestScore = <span class="hljs-number">-999999999</span>;
      <span class="hljs-keyword">for</span> (<span class="hljs-built_in">int</span> i = <span class="hljs-number">0</span>; i &lt; board.length; i++) {
        <span class="hljs-keyword">if</span> (board[i].isEmpty) {
          board[i] = <span class="hljs-string">'O'</span>;
          <span class="hljs-keyword">var</span> score = minimax(board, depth + <span class="hljs-number">1</span>, <span class="hljs-keyword">false</span>);
          board[i] = <span class="hljs-string">''</span>;
          <span class="hljs-comment">//alpha = max&lt;double&gt;(alpha, score);</span>
          <span class="hljs-comment">//if (beta &lt;= alpha) break;</span>
          <span class="hljs-keyword">if</span> (score &gt; bestScore) {
            bestScore = score;
          }
        }
      }
      <span class="hljs-keyword">return</span> bestScore;
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-built_in">int</span> bestScore = <span class="hljs-number">999999999</span>;
      <span class="hljs-keyword">for</span> (<span class="hljs-built_in">int</span> i = <span class="hljs-number">0</span>; i &lt; board.length; i++) {
        <span class="hljs-keyword">if</span> (board[i].isEmpty) {
          board[i] = <span class="hljs-string">'X'</span>;
          <span class="hljs-keyword">var</span> score = minimax(board, depth + <span class="hljs-number">1</span>, <span class="hljs-keyword">true</span>);
          board[i] = <span class="hljs-string">''</span>;
          <span class="hljs-keyword">if</span> (score &lt; bestScore) {
            bestScore = score;
          }
        }
      }
      <span class="hljs-keyword">return</span> bestScore;
    }
  }
</code></pre>
<hr />
<p>Tech Stack Used: </p>
<ul>
<li><a target="_blank" href="https://flutter.dev/">Flutter</a></li>
<li><a target="_blank" href="https://dart.dev/">Dart</a></li>
<li><a target="_blank" href="https://codemagic.io/start/">Codemagic</a></li>
</ul>
<p>Credits:</p>
<ul>
<li><a target="_blank" href="https://www.youtube.com/channel/UCmtyQOKKmrMVaKuRXz02jbQ">Sebastian Lague</a> for his amazing MiniMax explainer video.</li>
<li><a target="_blank" href="https://www.youtube.com/channel/UCvjgXvBlbQiydffZU7m1_aw">Daniel Shiffman</a> for the coding implementation tutorial.</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[BEST VSCode Settings for Flutter Developers 🚀]]></title><description><![CDATA[Flutter is a UI toolkit from Google which lets you build apps for any screen - android, ios, windows, linux, mac, and more! It has seen an outstanding amount of love and support from users all around the world, over a very short period of time. In th...]]></description><link>https://blog.ikramhasan.com/best-vscode-settings-for-flutter-developers</link><guid isPermaLink="true">https://blog.ikramhasan.com/best-vscode-settings-for-flutter-developers</guid><category><![CDATA[Flutter]]></category><category><![CDATA[Visual Studio Code]]></category><category><![CDATA[setup]]></category><dc:creator><![CDATA[Ikram Hasan]]></dc:creator><pubDate>Mon, 25 Apr 2022 18:52:44 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/Gll-v69L8iA/upload/v1650912449015/NVi3KWzAz.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a target="_blank" href="https://flutter.dev/">Flutter</a> is a UI toolkit from Google which lets you build apps for any screen - android, ios, windows, linux, mac, and more! It has seen an outstanding amount of love and support from users all around the world, over a very short period of time. In this article, I'll try my best to present the best VSCode settings, custom snippets, and extensions for flutter that I managed to find/invent over the period of my professional experience. I hope you find something useful. </p>
<h2 id="heading-who-am-i">Who Am I ❓</h2>
<p>My name is Ikramul Hasan and I worked as a flutter trainer for the <a target="_blank" href="https://www.basictrainingsdmga.com/">App Development Training</a> program organized by the ICT Division, Government of Bangladesh. These are the setup I made all my students go through. Currently, I'm working as a Senior Application Developer for a multinational company called MakeBell. </p>
<h2 id="heading-best-settings">Best Settings 👍</h2>
<p>First of all, although most of you know how to get to the settings page in VSCode, I'd still like to start by showing the process to anyone unaware. </p>
<h3 id="heading-how-do-i-get-to-settings">How do I get to Settings?</h3>
<p>There are two primary ways to get to VSCode settings. </p>
<ol>
<li>Launch VSCode and click on the gear icon at the bottom left side of your side panel. Then click on <code>Settings</code></li>
<li>Press <code>Ctrl+Shift+P</code> on Windows or <code>Cmd+Shift+P</code> on Mac to launch the command palette, type settings, and click on the Open Settings (UI) option.</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1650907653331/6ZLp_azSi.png" alt="2022-04-25 23_17_38-Greenshot.png-mh.png" /></p>
<p>Let us now start changing the settings. </p>
<h3 id="heading-enable-format-on-save">Enable format on save 📝</h3>
<p>Perhaps the most important settings for flutter. Let flutter format your dart code on save. </p>
<ol>
<li>Start by searching <code>Editor: Format On Save</code> in the search bar above </li>
<li>Enable the option.
It should look like this:</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1650910481907/RUp73gSZq.png" alt="2022-04-26 00_14_11-Epic Pen Content Surface_ __._DISPLAY1.png" /></p>
<h3 id="heading-enable-bracket-pair-colorization-and-guides">Enable bracket pair colorization and guides 🔴🟡🟢</h3>
<p>Everyone working with flutter knows how important it is to distinguish between brackets. Previously, we needed to achieve this using an extension called bracket pair colorizer. But now, VSCode supports this feature natively and it performs much better than the extension. Here are the steps:</p>
<ol>
<li>Search for <code>bracket pair</code> in the search bar above </li>
<li>Check the tickmark on the option <code>Editor › Bracket Pair Colorization</code>. If it is already enabled, don't change it.</li>
<li>Set <code>Editor › Guides: Bracket Pairs</code> to <code>true</code> from the dropdown. </li>
<li>Then check the option named <code>Editor › Guides: Highlight Active Bracket Pair</code>. This will enable the guides around the brackets. </li>
</ol>
<p>After all these steps, it should look like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1650908218613/F_jHbfGDt.png" alt="2022-04-25 23_36_46-Epic Pen Content Surface_ __._DISPLAY2.png" /></p>
<p>And your brackets? Much cooler. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1650908243349/6eMBPCfnV.png" alt="2022-04-25 23_29_33-Epic Pen Toolbar.png" /></p>
<h3 id="heading-automatically-rename-a-class-name-after-changing-its-filename">Automatically rename a class name after changing its filename ✍️</h3>
<p>This tells flutter whether to rename files when renaming classes with matching names (for example renaming 'class Person' inside 'person.dart'). If set to 'prompt', will ask each time before renaming. If set to 'always', the file will automatically be renamed. </p>
<ol>
<li>Search for <code>Dart: Rename Files With Classes</code></li>
<li>Set it to <code>prompt</code> in the dropdown.</li>
</ol>
<h3 id="heading-change-the-default-organization-name">Change the default organization name 🏛️</h3>
<p>When you create an app using the command palette in VSCode, the default organization looks something like this: <code>com.example</code>. If you want to change it to something like <code>com.your_org</code> then enable this feature:</p>
<ol>
<li>Search for <code>Dart: Flutter Create Organization</code>.</li>
<li>Click on <code>Edit in settings.json</code></li>
<li>Change the line <code>"dart.flutterCreateOrganization": "com.example",</code> to this <code>"dart.flutterCreateOrganization": "com.your_org",</code> If the line is not present, just add it yourself. </li>
<li>Now every time you create an app from VSCode, it will have that custom org name. </li>
</ol>
<h3 id="heading-enable-ui-guides">Enable UI guides 📏</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1650909318683/V7ueOJjsZ.png" alt="ui_guides.png" />
This tells flutter whether to enable the UI guide. UI guides are the white lines you see in the screenshot above. </p>
<ol>
<li>Search for <code>Dart: Preview Flutter Ui Guides</code> and enable it. </li>
<li>Search for <code>Dart: Preview Flutter Ui Guides Custom Tracking</code> and enable it. </li>
</ol>
<p>Note: Both of these features are experimental, and eat too much resources. If you have a particularly slow PC, I recommend disabling it. </p>
<h3 id="heading-automatically-add-const-modifier">Automatically add <code>const</code> modifier 💁</h3>
<p>Flutter now includes linting by default in every flutter app. As a result, if you do not add the const modifier, it shows a warning. This can become very messy and adding const manually can become very tedious. To add const modifier automatically every time you save, do the following:</p>
<ol>
<li>Open the command palette and type <code>settings</code></li>
<li>Select <code>Open Settings (json)</code></li>
<li>Add this code at the bottom of the file:</li>
</ol>
<pre><code class="lang-json"><span class="hljs-string">"editor.codeActionsOnSave"</span>: {
     <span class="hljs-attr">"source.fixAll"</span>: <span class="hljs-literal">true</span>,
 }
</code></pre>
<h3 id="heading-enable-settings-sync">Enable settings sync ☁️</h3>
<p>After all these efforts of creating the best settings, you don't want to lose them after every new VSCode installation. Enabling settings sync will let you sync your settings using your GitHub account. As a result, if you install VSCode on a brand new PC and log in to your GitHub account in VSCode, all these settings will be automatically synced for you. This means you need to do these steps only once. </p>
<ol>
<li>Click on the account icon on the bottom left of the sidebar. </li>
<li>Login using your GitHub account.</li>
<li>Make sure you see 'Settings sync is on' in the menu. </li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1650911349837/m8O1VWWXH.png" alt="2022-04-26 00_25_11-Greenshot.png-mh.png" /></p>
<h2 id="heading-thank-you-for-coming-this-far">Thank you for coming this far 🎉🎉🎉</h2>
<p>Let me know if I missed some settings. You'll find all my socials here on my <a target="_blank" href="https://ikramhasan.com/">portfolio site</a></p>
]]></content:encoded></item></channel></rss>