<?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[Blogs & articles]]></title><description><![CDATA[I will be mostly posting stuff on books I am currently reading, philosophical thoughts or ideas which I get when I am bored, about tech and things I am building]]></description><link>https://blog.anirudhsingh.in</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1754314797871/f28c442c-3e67-4693-aa87-66d29d21bd93.png</url><title>Blogs &amp; articles</title><link>https://blog.anirudhsingh.in</link></image><generator>RSS for Node</generator><lastBuildDate>Thu, 09 Apr 2026 15:48:03 GMT</lastBuildDate><atom:link href="https://blog.anirudhsingh.in/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Architecture Is Mostly About Boundaries]]></title><description><![CDATA[Many engineers think architecture means diagrams.
Boxes.Arrows.Layers.
Controller → Service → Repository.
But most real problems in software are not caused by missing layers.They happen because bounda]]></description><link>https://blog.anirudhsingh.in/architecture-is-mostly-about-boundaries</link><guid isPermaLink="true">https://blog.anirudhsingh.in/architecture-is-mostly-about-boundaries</guid><category><![CDATA[software architecture]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[software design]]></category><category><![CDATA[clean code]]></category><category><![CDATA[System Design]]></category><dc:creator><![CDATA[Anirudh Singh]]></dc:creator><pubDate>Tue, 10 Mar 2026 09:37:12 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6890ae4cba32d67a0780b628/e289ae47-615c-4761-9b80-7b2fb1a6574c.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Many engineers think architecture means diagrams.</p>
<p>Boxes.<br />Arrows.<br />Layers.</p>
<p>Controller → Service → Repository.</p>
<p>But most real problems in software are not caused by missing layers.<br />They happen because <strong>boundaries are unclear</strong>.</p>
<p>Architecture is mostly about boundaries.</p>
<p>Not patterns.<br />Not frameworks.<br />Not folder structure.</p>
<p>Boundaries.</p>
<hr />
<h2>What is a boundary?</h2>
<p>A boundary is simply a line that answers this question.</p>
<blockquote>
<p>Who owns this responsibility?</p>
</blockquote>
<p>If that question is unclear, the system slowly becomes harder to change.</p>
<p>Code starts to leak into places where it does not belong.</p>
<p>Logic spreads.</p>
<p>Dependencies grow.</p>
<p>Soon every change touches five different files.</p>
<hr />
<h2>A simple example</h2>
<p>Imagine a screen that shows a user profile.</p>
<p>You fetch the user and pass the whole object to another screen.</p>
<pre><code class="language-typescript">Navigator.push(context, ProfilePage(user))
</code></pre>
<p>It works.</p>
<p>But now the profile page depends on how the previous screen fetched the user.</p>
<p>If you want to open that page from a deep link, you cannot.<br />If you refresh the app, the screen breaks.</p>
<p>The problem is not navigation.</p>
<p>The problem is a <strong>broken boundary</strong>.</p>
<p>The profile page should own the responsibility of loading the user.</p>
<p>Instead of passing objects around, the screen should receive an id.</p>
<pre><code class="language-plaintext">/profile/:id
</code></pre>
<p>Now the page is independent.</p>
<p>The boundary is clear.</p>
<p>P.S: Read more about this principle <a href="https://blog.anirudhsingh.in/flutter-cross-platform-navigation-survive-refresh">here.</a></p>
<hr />
<h2>When boundaries are weak</h2>
<p>Weak boundaries create hidden dependencies.</p>
<p>Consider a common backend example.</p>
<p>A service function:</p>
<pre><code class="language-plaintext">createOrder(user, items, payment)
</code></pre>
<p>Inside it:</p>
<ul>
<li><p>It calculates prices</p>
</li>
<li><p>It writes to the database</p>
</li>
<li><p>It sends an email</p>
</li>
<li><p>It updates analytics</p>
</li>
</ul>
<p>At first it feels convenient.</p>
<p>Later every change becomes dangerous.</p>
<p>Changing pricing might affect analytics.<br />Fixing email logic might break order creation.</p>
<p>Everything is coupled.</p>
<p>The boundary is gone.</p>
<hr />
<h2>Boundaries reduce fear</h2>
<p>Good boundaries make code safe to change.</p>
<p>If a module has a clear responsibility, you know where changes belong.</p>
<p>For example:</p>
<p>Pricing module<br />Order module<br />Email module</p>
<p>Each module has one job.</p>
<p>When pricing rules change, you go to one place.</p>
<p>This reduces cognitive load.</p>
<p>You do not need to hold the entire system in your head.</p>
<hr />
<h2>Boundaries are about ownership</h2>
<p>Good architecture answers questions like:</p>
<ul>
<li><p>Who owns business rules?</p>
</li>
<li><p>Who talks to the database?</p>
</li>
<li><p>Who handles external APIs?</p>
</li>
<li><p>Who controls navigation?</p>
</li>
</ul>
<p>When these answers are obvious, the codebase feels calm.</p>
<p>When they are unclear, every file starts doing a little bit of everything.</p>
<hr />
<h2>Folder structure is not architecture</h2>
<p>Many teams confuse folders with architecture.</p>
<pre><code class="language-javascript">controllers/
services/
repositories/
models/
</code></pre>
<p>This looks organized.</p>
<p>But boundaries might still be weak.</p>
<p>A service might call another service that calls another service that touches the database directly.</p>
<p>The structure exists.</p>
<p>The boundaries do not.</p>
<p>Architecture is not where the files live.</p>
<p>It is <strong>who is allowed to talk to whom</strong>.</p>
<hr />
<h2>Good boundaries limit communication</h2>
<p>In a healthy system, most parts should not talk to each other.</p>
<p>This sounds restrictive.</p>
<p>But it actually makes systems simpler.</p>
<p>If every module can call every other module, the system becomes a web of dependencies.</p>
<p>Soon nothing can change safely.</p>
<p>Boundaries act like walls in a building.</p>
<p>They limit how things connect.</p>
<p>And that limitation creates stability.</p>
<hr />
<h2>Boundaries evolve</h2>
<p>You rarely get boundaries right on the first try.</p>
<p>Software grows.</p>
<p>Requirements change.</p>
<p>New concepts appear.</p>
<p>Architecture should adapt.</p>
<p>But if the system already has clear boundaries, these changes become easier.</p>
<p>You can move things without breaking everything else.</p>
<hr />
<h2>A small test</h2>
<p>A simple way to check architecture is to ask this question.</p>
<blockquote>
<p>If I change this rule, how many places will I need to touch?</p>
</blockquote>
<p>If the answer is one or two places, the boundaries are probably good.</p>
<p>If the answer is "I am not sure", the boundaries are weak.</p>
<hr />
<h2>Final thought</h2>
<p>Architecture often sounds complicated.</p>
<p>It is not.</p>
<p>Most of it comes down to one simple idea.</p>
<p>Clear ownership.</p>
<p>Clear responsibility.</p>
<p>Clear limits on how parts of the system interact.</p>
<p>Architecture is mostly about boundaries.</p>
<p>When the boundaries are right, the system feels easy to work with.</p>
<p>When they are wrong, even small changes become painful.</p>
]]></content:encoded></item><item><title><![CDATA[Money is not a double: A Hard Lesson from Building a Trading App]]></title><description><![CDATA[When you build your first fintech app in Flutter, using double for money feels completely normal.
double price = 10.25;

It compiles.It runs.The UI looks correct.QA passes it.
So what's the problem?
T]]></description><link>https://blog.anirudhsingh.in/money-is-not-a-double-flutter-currency-precision</link><guid isPermaLink="true">https://blog.anirudhsingh.in/money-is-not-a-double-flutter-currency-precision</guid><category><![CDATA[Flutter]]></category><category><![CDATA[fintech]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[programming]]></category><category><![CDATA[Mobile Development]]></category><category><![CDATA[Software Engineering]]></category><category><![CDATA[clean code]]></category><category><![CDATA[architecture]]></category><dc:creator><![CDATA[Anirudh Singh]]></dc:creator><pubDate>Fri, 20 Feb 2026 04:18:14 GMT</pubDate><enclosure url="https://cloudmate-test.s3.us-east-1.amazonaws.com/uploads/covers/6890ae4cba32d67a0780b628/2a5e41fd-b32b-48be-acc6-bfb0c376a8c0.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When you build your first fintech app in Flutter, using <code>double</code> for money feels completely normal.</p>
<pre><code class="language-javascript">double price = 10.25;
</code></pre>
<p>It compiles.<br />It runs.<br />The UI looks correct.<br />QA passes it.</p>
<p>So what's the problem?</p>
<p>The problem is that <code>double</code> was never meant to store money.</p>
<p>And one day, usually when your product grows, that decision quietly comes back to hurt you.</p>
<hr />
<h2>The Problem Nobody Sees at First</h2>
<p>In Dart (like most languages), <code>double</code> uses floating-point representation.</p>
<p>Here's something simple:</p>
<pre><code class="language-javascript">void main() {
  double a = 0.1;
  double b = 0.2;
  print(a + b);
}
</code></pre>
<p>Output:</p>
<pre><code class="language-javascript">0.30000000000000004
</code></pre>
<p>That extra <code>4</code> at the end?</p>
<p>That's not a bug in Dart.<br />That's how floating-point numbers work.</p>
<p>Most decimal numbers cannot be stored exactly in binary.</p>
<p>And <code>double</code> stores numbers in binary.</p>
<hr />
<h2>Why This Doesn’t Break Your App Immediately</h2>
<p>Most traditional fiat currencies use only 2 decimal places:</p>
<ul>
<li><p>10.25</p>
</li>
<li><p>99.99</p>
</li>
<li><p>1.50</p>
</li>
</ul>
<p>Even if the internal number is slightly wrong, we usually format it like this:</p>
<pre><code class="language-javascript">value.toStringAsFixed(2)
</code></pre>
<p>And the UI shows:</p>
<pre><code class="language-plaintext">10.25
</code></pre>
<p>So everything <em>looks</em> correct.</p>
<p>The error is still there.<br />It's just hidden.</p>
<hr />
<h2>When Things Start Breaking: Crypto</h2>
<p>Now imagine your app starts supporting:</p>
<ul>
<li><p>Bitcoin - 8 decimal places</p>
</li>
<li><p>Ethereum - 18 decimal places</p>
</li>
<li><p>Tether - 6 decimal places</p>
</li>
</ul>
<p>Now precision really matters.</p>
<p>Example:</p>
<pre><code class="language-plaintext">0.00000013 BTC
</code></pre>
<p>That last digit is real money.</p>
<p>If you use <code>double</code>:</p>
<ul>
<li><p>Adding values can shift decimals.</p>
</li>
<li><p>Multiplication introduces rounding.</p>
</li>
<li><p>Conversions between currencies create tiny mismatches.</p>
</li>
<li><p>Formatting hides precision loss.</p>
</li>
<li><p>Parsing user input mutates values silently.</p>
</li>
</ul>
<p>And the worst part, its a bug hidden in plain sight but still goes away unnoticed.</p>
<hr />
<h2>Issue entrypoint: UI &amp; Backend Round Trip</h2>
<p>This is where things actually break.</p>
<p>Imagine this flow:</p>
<ol>
<li><p>Backend sends: <code>"0.00000013"</code></p>
</li>
<li><p>You convert it to <code>double</code></p>
</li>
<li><p>User edits the amount</p>
</li>
<li><p>You format and send it back</p>
</li>
</ol>
<p>Somewhere in that round trip, the value changes slightly.</p>
<p>You expected:</p>
<pre><code class="language-plaintext">0.00000013
</code></pre>
<p>You send:</p>
<pre><code class="language-plaintext">0.00000012
</code></pre>
<p>In financial systems, that is unacceptable.</p>
<p>Small rounding issues lead to:</p>
<ul>
<li><p>Ledger mismatches</p>
</li>
<li><p>Transaction validation failures</p>
</li>
<li><p>“Amount does not match” backend errors</p>
</li>
</ul>
<p>Floating point errors are small.</p>
<p>But money systems require exactness, not approximation.</p>
<hr />
<h2>Why double Is the Wrong Tool</h2>
<p>Floating point numbers are stored like this:</p>
<pre><code class="language-markdown">sign × mantissa × 2^exponent
</code></pre>
<p>They are optimised for:</p>
<ul>
<li><p>Scientific calculations</p>
</li>
<li><p>Graphics</p>
</li>
<li><p>Physics simulations</p>
</li>
</ul>
<p>They are not optimised for:</p>
<ul>
<li><p>Accounting</p>
</li>
<li><p>Trading systems</p>
</li>
<li><p>Ledger calculations</p>
</li>
<li><p>User-entered currency</p>
</li>
</ul>
<p>Money is decimal.<br />Floating point is binary.</p>
<p>That mismatch is the root cause.</p>
<hr />
<h1>So what's the right way?</h1>
<h2>1. Store the Smallest Unit</h2>
<p>Instead of:</p>
<pre><code class="language-javascript">double amount = 10.25;
</code></pre>
<p>Do this:</p>
<pre><code class="language-javascript">int amountInCents = 1025;
</code></pre>
<p>For crypto:</p>
<ul>
<li><p>1 BTC = 100,000,000 satoshis</p>
</li>
<li><p>Store satoshis as <code>int</code></p>
</li>
</ul>
<p>No rounding.<br />No precision loss.<br />No floating point.</p>
<hr />
<h2>2. Create a Proper Money Type</h2>
<p>Instead of passing raw numbers everywhere, define a safe structure.</p>
<pre><code class="language-javascript">class Money {
  final BigInt amount;   // smallest unit
  final int scale;       // decimal places
  final String currency;

  Money({
    required this.amount,
    required this.scale,
    required this.currency,
  });

  String format() {
    final divisor = BigInt.from(10).pow(scale);
    final whole = amount ~/ divisor;
    final fraction = (amount % divisor)
        .toString()
        .padLeft(scale, '0');
    return "\(whole.\)fraction $currency";
  }
}
</code></pre>
<p>Now:</p>
<ul>
<li><p>Precision is controlled.</p>
</li>
<li><p>Each currency defines its scale.</p>
</li>
<li><p>UI formatting is deterministic.</p>
</li>
<li><p>Math operations stay exact.</p>
</li>
</ul>
<p>This is how robust financial systems are built.</p>
<hr />
<h1>Example: Showing Safe Values on a Transaction History Screen</h1>
<p>Let's say your backend sends smallest unit values.</p>
<p>Transaction model:</p>
<pre><code class="language-javascript">class TransactionModel {
  final String title;
  final BigInt amount; // smallest unit
  final int scale;
  final String currency;

  TransactionModel({
    required this.title,
    required this.amount,
    required this.scale,
    required this.currency,
  });

  String formattedAmount() {
    final divisor = BigInt.from(10).pow(scale);
    final whole = amount ~/ divisor;
    final fraction = (amount % divisor)
        .toString()
        .padLeft(scale, '0');

    return "\(whole.\)fraction $currency";
  }
}
</code></pre>
<p>UI:</p>
<pre><code class="language-javascript">ListView.builder(
  itemCount: transactions.length,
  itemBuilder: (context, index) {
    final txn = transactions[index];

    return ListTile(
      title: Text(txn.title),
      trailing: Text(
        txn.formattedAmount(),
        style: const TextStyle(
          fontWeight: FontWeight.bold,
        ),
      ),
    );
  },
);
</code></pre>
<p>Notice what's missing?</p>
<p>No <code>double</code>.<br />No <code>toStringAsFixed</code>.<br />No floating point math.</p>
<p>Just exact numbers.</p>
<hr />
<h2>One Rule You Should Never Break</h2>
<p>Never do this for currency:</p>
<pre><code class="language-typescript">double.parse(userInput)
</code></pre>
<p>Instead:</p>
<ul>
<li><p>Keep input as string</p>
</li>
<li><p>Validate format</p>
</li>
<li><p>Convert manually to smallest unit</p>
</li>
<li><p>Store as <code>BigInt</code> or <code>int</code></p>
</li>
</ul>
<p>Money should never pass through floating point.</p>
<hr />
<h1>The Lesson</h1>
<p>Using <code>double</code> for money is easy.</p>
<p>It works in prototypes.<br />It works in demos.<br />It works when you only support 2 decimal currencies.</p>
<p>But products evolve.</p>
<p>They add:</p>
<ul>
<li><p>Crypto</p>
</li>
<li><p>Micro-transactions</p>
</li>
<li><p>Fractional trading</p>
</li>
<li><p>High precision calculations</p>
</li>
</ul>
<p>And that's when floating point becomes a silent production bug.</p>
<p>It doesn't crash your app.</p>
<p>It just slowly corrupts your numbers.</p>
<hr />
<h2>Final Thought</h2>
<p>Floating point errors are invisible in the beginning.</p>
<p>But in financial systems, invisible errors become very visible money.</p>
<p>And systems involving money don't forgive approximation.</p>
]]></content:encoded></item><item><title><![CDATA[Navigation as State: Applying the Coordinator Pattern in SwiftUI]]></title><description><![CDATA[When apps grow, navigation becomes messy.
At first, it’s just:

show login

push home

present details


But as features increase, flows start crossing each other:

Splash → Auth → Home

Auth → Forgot password → OTP → Reset

Home → Profile → Edit → B...]]></description><link>https://blog.anirudhsingh.in/navigation-as-state-coordinator-pattern-swiftui</link><guid isPermaLink="true">https://blog.anirudhsingh.in/navigation-as-state-coordinator-pattern-swiftui</guid><category><![CDATA[SwiftUI]]></category><category><![CDATA[iOS development]]></category><category><![CDATA[Mobile Development]]></category><category><![CDATA[software development]]></category><category><![CDATA[software architecture]]></category><category><![CDATA[design patterns]]></category><dc:creator><![CDATA[Anirudh Singh]]></dc:creator><pubDate>Fri, 13 Feb 2026 01:30:22 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/IcEKJ7KHZUM/upload/64620bc5931fe5bf6b240d8ed22ac991.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When apps grow, navigation becomes messy.</p>
<p>At first, it’s just:</p>
<ul>
<li><p>show login</p>
</li>
<li><p>push home</p>
</li>
<li><p>present details</p>
</li>
</ul>
<p>But as features increase, flows start crossing each other:</p>
<ul>
<li><p>Splash → Auth → Home</p>
</li>
<li><p>Auth → Forgot password → OTP → Reset</p>
</li>
<li><p>Home → Profile → Edit → Back</p>
</li>
<li><p>Deep links</p>
</li>
<li><p>Logout from anywhere</p>
</li>
</ul>
<p>If navigation logic lives inside views, things quickly become hard to reason about.</p>
<p>That’s where the <strong>Coordinator Pattern</strong> helps.</p>
<hr />
<h1 id="heading-what-is-the-coordinator-pattern">What Is the Coordinator Pattern?</h1>
<p>The coordinator pattern is a way to move navigation logic out of views and into dedicated objects called <em>coordinators</em>.</p>
<p>Instead of views deciding:</p>
<blockquote>
<p>“When button is tapped, push this screen”</p>
</blockquote>
<p>They say:</p>
<blockquote>
<p>“Hey coordinator, user tapped login”</p>
</blockquote>
<p>And the coordinator decides what happens next.</p>
<p>In short:</p>
<blockquote>
<p>Coordinators control navigation flow.<br />Views describe UI only.</p>
</blockquote>
<hr />
<h1 id="heading-thinking-of-navigation-as-state">Thinking of Navigation as State</h1>
<p>In SwiftUI especially, navigation is just <strong>state</strong>.</p>
<p>Instead of saying:</p>
<ul>
<li><p>push screen A</p>
</li>
<li><p>present screen B</p>
</li>
</ul>
<p>We say:</p>
<ul>
<li><p>currentRoute = .login</p>
</li>
<li><p>currentRoute = .home</p>
</li>
<li><p>authMode = .signup</p>
</li>
</ul>
<p>UI simply reacts to that state.</p>
<p>So coordinator becomes:</p>
<ul>
<li><p>A state holder</p>
</li>
<li><p>A flow controller</p>
</li>
<li><p>A source of truth for navigation</p>
</li>
</ul>
<hr />
<h1 id="heading-high-level-flow">High-Level Flow</h1>
<p>Here’s a simple app flow:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770782846435/871273f9-8e92-4184-bb79-89cdc044d391.png" alt class="image--center mx-auto" /></p>
<p>This is a very simple app:</p>
<ul>
<li><p>Splash screen checks login</p>
</li>
<li><p>If logged in → Home</p>
</li>
<li><p>If not → Auth</p>
</li>
<li><p>Auth switches between Login and Signup</p>
</li>
<li><p>After login → Home</p>
</li>
</ul>
<p>Now let’s see how this was done in UIKit and how it differs in SwiftUI.</p>
<hr />
<h1 id="heading-coordinator-pattern-in-uikit">Coordinator Pattern in UIKit</h1>
<p>In UIKit, coordinators usually:</p>
<ul>
<li><p>Own a <code>UINavigationController</code></p>
</li>
<li><p>Push and present view controllers</p>
</li>
<li><p>Directly decide what to show</p>
</li>
</ul>
<p>Example idea:</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AppCoordinator</span> </span>{
    <span class="hljs-keyword">let</span> navigationController: <span class="hljs-type">UINavigationController</span>

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">start</span><span class="hljs-params">()</span></span> {
        showSplash()
    }

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">showSplash</span><span class="hljs-params">()</span></span> {
        <span class="hljs-keyword">let</span> vc = <span class="hljs-type">SplashViewController</span>()
        vc.onFinish = { [<span class="hljs-keyword">weak</span> <span class="hljs-keyword">self</span>] isLoggedIn <span class="hljs-keyword">in</span>
            <span class="hljs-keyword">if</span> isLoggedIn {
                <span class="hljs-keyword">self</span>?.showHome()
            } <span class="hljs-keyword">else</span> {
                <span class="hljs-keyword">self</span>?.showAuth()
            }
        }
        navigationController.setViewControllers([vc], animated: <span class="hljs-literal">false</span>)
    }

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">showHome</span><span class="hljs-params">()</span></span> {
        <span class="hljs-keyword">let</span> homeVC = <span class="hljs-type">HomeViewController</span>()
        navigationController.setViewControllers([homeVC], animated: <span class="hljs-literal">true</span>)
    }
}
</code></pre>
<h3 id="heading-key-point-in-uikit">Key Point in UIKit</h3>
<p>Coordinator decides:</p>
<ul>
<li><p>What view controller to create</p>
</li>
<li><p>When to push</p>
</li>
<li><p>When to present</p>
</li>
<li><p>When to replace stack</p>
</li>
</ul>
<p>It fully controls navigation stack.</p>
<p>UIKit = <strong>Imperative navigation</strong></p>
<blockquote>
<p>“Push this now.”</p>
</blockquote>
<hr />
<h1 id="heading-coordinator-pattern-in-swiftui">Coordinator Pattern in SwiftUI</h1>
<p>SwiftUI works differently.</p>
<p>Navigation is state-driven.</p>
<p>Instead of pushing screens, we change state and SwiftUI updates the UI.</p>
<p>So in SwiftUI:</p>
<p>Coordinator does NOT push views.</p>
<p>Instead, it:</p>
<ul>
<li><p>Exposes <code>@Published</code> state</p>
</li>
<li><p>Exposes methods to modify state</p>
</li>
<li><p>Root view decides what to render</p>
</li>
</ul>
<p>UIKit coordinator:</p>
<blockquote>
<p>“Show Home”</p>
</blockquote>
<p>SwiftUI coordinator:</p>
<blockquote>
<p><code>currentRoute = .home</code></p>
</blockquote>
<p>And the view reacts.</p>
<p>SwiftUI = <strong>Declarative navigation</strong></p>
<blockquote>
<p>“When route is .home, show HomeView.”</p>
</blockquote>
<hr />
<h1 id="heading-folder-structure-for-a-large-swiftui-app">Folder Structure for a Large SwiftUI App</h1>
<p>Let’s imagine this simple app:</p>
<ul>
<li><p>Splash</p>
</li>
<li><p>Auth (Login / Signup)</p>
</li>
<li><p>Home</p>
</li>
</ul>
<p>A clean structure might look like:</p>
<pre><code class="lang-markdown">App/
 ├── AppEntry.swift
 ├── RootCoordinator.swift
 ├── RootView.swift

Features/
 ├── Splash/
 │    ├── SplashView.swift
 │
 ├── Auth/
 │    ├── AuthCoordinator.swift
 │    ├── AuthView.swift
 │    ├── LoginView.swift
 │    └── SignupView.swift
 │
 ├── Home/
 │    └── HomeView.swift
</code></pre>
<p>Notice:</p>
<ul>
<li><p>Root has its own coordinator</p>
</li>
<li><p>Auth has its own coordinator</p>
</li>
<li><p>Features are separated</p>
</li>
</ul>
<p>This scales well for large apps.</p>
<hr />
<h1 id="heading-step-1-root-coordinator-swiftui-version">Step 1: Root Coordinator (SwiftUI Version)</h1>
<p>Root decides between:</p>
<ul>
<li><p>Splash</p>
</li>
<li><p>Auth</p>
</li>
<li><p>Home</p>
</li>
</ul>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">RootRoute</span> </span>{
    <span class="hljs-keyword">case</span> splash
    <span class="hljs-keyword">case</span> auth
    <span class="hljs-keyword">case</span> home
}

<span class="hljs-keyword">final</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RootCoordinator</span>: <span class="hljs-title">ObservableObject</span> </span>{
    @<span class="hljs-type">Published</span> <span class="hljs-keyword">var</span> route: <span class="hljs-type">RootRoute</span> = .splash

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">decideInitialFlow</span><span class="hljs-params">(isLoggedIn: Bool)</span></span> {
        route = isLoggedIn ? .home : .auth
    }

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">didLogin</span><span class="hljs-params">()</span></span> {
        route = .home
    }

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">logout</span><span class="hljs-params">()</span></span> {
        route = .auth
    }
}
</code></pre>
<p>This coordinator does not create views.</p>
<p>It only changes state.</p>
<hr />
<h1 id="heading-step-2-root-view-decides-what-to-show">Step 2: Root View Decides What to Show</h1>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">RootView</span>: <span class="hljs-title">View</span> </span>{
    @<span class="hljs-type">StateObject</span> <span class="hljs-keyword">var</span> coordinator = <span class="hljs-type">RootCoordinator</span>()

    <span class="hljs-keyword">var</span> body: some <span class="hljs-type">View</span> {
        <span class="hljs-keyword">switch</span> coordinator.route {
        <span class="hljs-keyword">case</span> .splash:
            <span class="hljs-type">SplashView</span> {
                coordinator.decideInitialFlow(isLoggedIn: $<span class="hljs-number">0</span>)
            }

        <span class="hljs-keyword">case</span> .auth:
            <span class="hljs-type">AuthView</span>(
                onLoginSuccess: {
                    coordinator.didLogin()
                }
            )

        <span class="hljs-keyword">case</span> .home:
            <span class="hljs-type">HomeView</span>(
                onLogout: {
                    coordinator.logout()
                }
            )
        }
    }
}
</code></pre>
<p>Notice the difference from UIKit:</p>
<ul>
<li><p>No push</p>
</li>
<li><p>No navigation controller</p>
</li>
<li><p>Just a <code>switch</code> on state</p>
</li>
</ul>
<p>SwiftUI handles rendering.</p>
<hr />
<h1 id="heading-step-3-auth-has-its-own-coordinator">Step 3: Auth Has Its Own Coordinator</h1>
<p>Inside Auth, we want to switch between:</p>
<ul>
<li><p>Login</p>
</li>
<li><p>Signup</p>
</li>
</ul>
<p>So we create an <code>AuthCoordinator</code>.</p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">AuthMode</span> </span>{
    <span class="hljs-keyword">case</span> login
    <span class="hljs-keyword">case</span> signup
}

<span class="hljs-keyword">final</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AuthCoordinator</span>: <span class="hljs-title">ObservableObject</span> </span>{
    @<span class="hljs-type">Published</span> <span class="hljs-keyword">var</span> mode: <span class="hljs-type">AuthMode</span> = .login

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">showSignup</span><span class="hljs-params">()</span></span> {
        mode = .signup
    }

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">showLogin</span><span class="hljs-params">()</span></span> {
        mode = .login
    }
}
</code></pre>
<hr />
<h1 id="heading-authview-uses-authcoordinator">AuthView Uses AuthCoordinator</h1>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">AuthView</span>: <span class="hljs-title">View</span> </span>{
    @<span class="hljs-type">StateObject</span> <span class="hljs-keyword">private</span> <span class="hljs-keyword">var</span> coordinator = <span class="hljs-type">AuthCoordinator</span>()
    <span class="hljs-keyword">var</span> onLoginSuccess: () -&gt; <span class="hljs-type">Void</span>

    <span class="hljs-keyword">var</span> body: some <span class="hljs-type">View</span> {
        <span class="hljs-keyword">switch</span> coordinator.mode {
        <span class="hljs-keyword">case</span> .login:
            <span class="hljs-type">LoginView</span>(
                onSignupTapped: {
                    coordinator.showSignup()
                },
                onLoginSuccess: {
                    onLoginSuccess()
                }
            )

        <span class="hljs-keyword">case</span> .signup:
            <span class="hljs-type">SignupView</span>(
                onBackToLogin: {
                    coordinator.showLogin()
                }
            )
        }
    }
}
</code></pre>
<p>Now Auth handles its own internal navigation.</p>
<p>Root doesn’t care whether Auth is showing login or signup.</p>
<p>This is separation of responsibility.</p>
<hr />
<h1 id="heading-the-big-difference-uikit-vs-swiftui">The Big Difference (UIKit vs SwiftUI)</h1>
<div class="hn-table">
<table>
<thead>
<tr>
<td>UIKit Coordinator</td><td>SwiftUI Coordinator</td></tr>
</thead>
<tbody>
<tr>
<td>Owns navigation controller</td><td>Owns navigation state</td></tr>
<tr>
<td>Pushes view controllers</td><td>Updates published state</td></tr>
<tr>
<td>Imperative</td><td>Declarative</td></tr>
<tr>
<td>Controls stack directly</td><td>UI reacts to state</td></tr>
</tbody>
</table>
</div><p>UIKit:</p>
<blockquote>
<p>“Push LoginViewController”</p>
</blockquote>
<p>SwiftUI:</p>
<blockquote>
<p><code>route = .login</code></p>
</blockquote>
<p>This is the mindset shift.</p>
]]></content:encoded></item><item><title><![CDATA[Designing Navigation Pages That Actually Survive Refresh]]></title><description><![CDATA[Problem statement: it works… until someone refreshes
I’ve seen this pattern again and again, especially with newer Flutter developers.
You build navigation using go_router.You pass objects from one page to another.Everything works perfectly during no...]]></description><link>https://blog.anirudhsingh.in/flutter-cross-platform-navigation-survive-refresh</link><guid isPermaLink="true">https://blog.anirudhsingh.in/flutter-cross-platform-navigation-survive-refresh</guid><category><![CDATA[Flutter]]></category><category><![CDATA[navigation]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[architecture]]></category><category><![CDATA[go_router]]></category><category><![CDATA[DeepLinking]]></category><category><![CDATA[flutter web]]></category><category><![CDATA[Mobile Development]]></category><dc:creator><![CDATA[Anirudh Singh]]></dc:creator><pubDate>Fri, 06 Feb 2026 04:06:14 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/gOdavfpH-3s/upload/89826e9c67b5360904a3cb0884b7d0c5.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-problem-statement-it-works-until-someone-refreshes">Problem statement: it works… until someone refreshes</h2>
<p>I’ve seen this pattern again and again, especially with newer Flutter developers.</p>
<p>You build navigation using <code>go_router</code>.<br />You pass objects from one page to another.<br />Everything works perfectly during normal app usage.</p>
<p>Then someone refreshes the page.</p>
<p>Or opens a link directly.</p>
<p>Or lands on a URL from Google.</p>
<p>And suddenly:</p>
<ul>
<li><p>the page crashes</p>
</li>
<li><p>or shows a blank screen</p>
</li>
<li><p>or awkwardly redirects back to a list</p>
</li>
</ul>
<p>At this point, most people start adding workarounds:<br />“if data is null, go back”<br />“if object is missing, fetch again”<br />“if state is gone, redirect home”</p>
<p>The router isn’t the problem here.<br />The page design is.</p>
<hr />
<h2 id="heading-whats-actually-going-wrong">What’s actually going wrong</h2>
<p>The root issue is <strong>tight coupling between pages</strong>.</p>
<p>A lot of pages are built with the assumption that:</p>
<blockquote>
<p>“This page will always be opened from its parent.”</p>
</blockquote>
<p>That assumption is no longer safe.</p>
<p>With <strong>Navigator 2.0</strong>, deep linking is not optional anymore.<br />On web, every page is already a deep link — whether you planned for it or not.</p>
<p>If a page depends on parent state to exist, that page is fragile by design.</p>
<hr />
<h2 id="heading-one-simple-question-that-changes-everything">One simple question that changes everything</h2>
<p>Whenever I add a new route, I ask myself one question:</p>
<blockquote>
<p>From a business point of view, should this page be able to exist on its own?</p>
</blockquote>
<p>This question decides everything.</p>
<h3 id="heading-standalone-pages">Standalone pages</h3>
<p>Examples:</p>
<ul>
<li><p><code>/user/:id</code></p>
</li>
<li><p><code>/order/:id</code></p>
</li>
<li><p><code>/product/:id</code></p>
</li>
</ul>
<p>These pages:</p>
<ul>
<li><p>should open directly from a URL</p>
</li>
<li><p>should survive refresh</p>
</li>
<li><p>should not depend on parent state</p>
</li>
<li><p>should be able to load themselves</p>
</li>
</ul>
<h3 id="heading-flow-dependent-pages">Flow-dependent pages</h3>
<p>Examples:</p>
<ul>
<li><p>payment steps</p>
</li>
<li><p>checkout confirmation</p>
</li>
<li><p>OTP verification</p>
</li>
<li><p>onboarding flows</p>
</li>
</ul>
<p>These pages:</p>
<ul>
<li><p>only make sense inside a flow</p>
</li>
<li><p>often should not be deep linked</p>
</li>
<li><p>can depend on parent state</p>
</li>
<li><p>usually redirect if opened directly</p>
</li>
</ul>
<p>Not every page needs to be standalone.<br />But the ones that should be, <strong>must be designed that way from day one</strong>.</p>
<hr />
<h2 id="heading-the-common-mistake-passing-full-objects-through-routes">The common mistake: passing full objects through routes</h2>
<p>Let’s look at a very common pattern.</p>
<h3 id="heading-fragile-approach">Fragile approach</h3>
<pre><code class="lang-dart"><span class="hljs-comment">// From user list page</span>
context.go(
  <span class="hljs-string">'/user-details'</span>,
  extra: user,
);
</code></pre>
<pre><code class="lang-dart"><span class="hljs-comment">// User details page</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserDetailsPage</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-keyword">final</span> User user;

  <span class="hljs-keyword">const</span> UserDetailsPage({<span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.user});

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> Text(user.name);
  }
}
</code></pre>
<p>This feels convenient.<br />No extra API call.<br />No loading state.</p>
<p>But this page has one big problem:</p>
<p>It <strong>cannot exist on its own</strong>.</p>
<p>If the user refreshes:</p>
<ul>
<li><p><code>extra</code> is gone</p>
</li>
<li><p><code>user</code> is null</p>
</li>
<li><p>now the page doesn’t know what to do</p>
</li>
</ul>
<p>So people start patching:</p>
<ul>
<li><p>redirect back to list</p>
</li>
<li><p>show error</p>
</li>
<li><p>refetch with hacks</p>
</li>
</ul>
<p>All of this complexity exists because the page depends on navigation state.</p>
<hr />
<h2 id="heading-a-more-resilient-approach-routes-as-contracts">A more resilient approach: routes as contracts</h2>
<p>If a page is standalone, the route should describe <strong>what the page needs</strong>, not <strong>what the parent happens to have</strong>.</p>
<h3 id="heading-better-route-definition">Better route definition</h3>
<pre><code class="lang-dart">GoRoute(
  path: <span class="hljs-string">'/user/:id'</span>,
  builder: (context, state) {
    <span class="hljs-keyword">final</span> userId = state.pathParameters[<span class="hljs-string">'id'</span>]!;
    <span class="hljs-keyword">return</span> UserDetailsPage(userId: userId);
  },
);
</code></pre>
<p>Now the route itself answers one question clearly:</p>
<blockquote>
<p>“What does this page need to load?”</p>
</blockquote>
<p>Answer: a <code>userId</code>.</p>
<hr />
<h2 id="heading-move-data-responsibility-into-the-page">Move data responsibility into the page</h2>
<p>The page should not care <strong>where it came from</strong>.<br />It should only care about <strong>what it needs</strong>.</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserDetailsPage</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> userId;

  <span class="hljs-keyword">const</span> UserDetailsPage({<span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.userId});

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> BlocProvider(
      create: (_) =&gt; UserDetailsCubit(userId)..load(),
      child: <span class="hljs-keyword">const</span> UserDetailsView(),
    );
  }
}
</code></pre>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserDetailsCubit</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Cubit</span>&lt;<span class="hljs-title">UserDetailsState</span>&gt; </span>{
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> userId;

  UserDetailsCubit(<span class="hljs-keyword">this</span>.userId) : <span class="hljs-keyword">super</span>(UserDetailsLoading());

  Future&lt;<span class="hljs-keyword">void</span>&gt; load() <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">final</span> user = <span class="hljs-keyword">await</span> userRepository.getUser(userId);
    emit(UserDetailsLoaded(user));
  }
}
</code></pre>
<p>Now this page:</p>
<ul>
<li><p>works on refresh</p>
</li>
<li><p>works with deep links</p>
</li>
<li><p>works without parent context</p>
</li>
<li><p>is easy to reason about</p>
</li>
</ul>
<hr />
<h2 id="heading-but-why-make-another-api-call-the-hybrid-approach">“But why make another API call?” — the hybrid approach</h2>
<p>This is a fair question.</p>
<p>If I already have the user object, why fetch it again?</p>
<p>You don’t have to.</p>
<h3 id="heading-hybrid-page-design">Hybrid page design</h3>
<p>Let the page accept an optional object <strong>without depending on it</strong>.</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserDetailsPage</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> userId;
  <span class="hljs-keyword">final</span> User? user;

  <span class="hljs-keyword">const</span> UserDetailsPage({
    <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.userId,
    <span class="hljs-keyword">this</span>.user,
  });

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> BlocProvider(
      create: (_) =&gt; UserDetailsCubit(
        userId: userId,
        initialUser: user,
      )..load(),
      child: <span class="hljs-keyword">const</span> UserDetailsView(),
    );
  }
}
</code></pre>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserDetailsCubit</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Cubit</span>&lt;<span class="hljs-title">UserDetailsState</span>&gt; </span>{
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> userId;
  <span class="hljs-keyword">final</span> User? initialUser;

  UserDetailsCubit({
    <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.userId,
    <span class="hljs-keyword">this</span>.initialUser,
  }) : <span class="hljs-keyword">super</span>(UserDetailsLoading());

  Future&lt;<span class="hljs-keyword">void</span>&gt; load() <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">if</span> (initialUser != <span class="hljs-keyword">null</span>) {
      emit(UserDetailsLoaded(initialUser!));
      <span class="hljs-keyword">return</span>;
    }

    <span class="hljs-keyword">final</span> user = <span class="hljs-keyword">await</span> userRepository.getUser(userId);
    emit(UserDetailsLoaded(user));
  }
}
</code></pre>
<h3 id="heading-what-you-gain">What you gain</h3>
<ul>
<li><p>No unnecessary API calls</p>
</li>
<li><p>Refresh still works</p>
</li>
<li><p>Deep links still work</p>
</li>
<li><p>Page is not tied to navigation history</p>
</li>
</ul>
<p>This pattern scales very well as the app grows.</p>
<hr />
<h2 id="heading-what-about-flow-based-pages">What about flow-based pages?</h2>
<p>Not every page needs this treatment.</p>
<p>For example, a payment confirmation step:</p>
<ul>
<li><p>doesn’t make sense outside the flow</p>
</li>
<li><p>probably should redirect if opened directly</p>
</li>
<li><p>can depend on parent state safely</p>
</li>
</ul>
<p>The key is <strong>being intentional</strong>.</p>
<p>Problems happen when we treat <em>every</em> page the same.</p>
<hr />
<h2 id="heading-why-this-matters-long-term">Why this matters long-term</h2>
<p>Pages that can load themselves:</p>
<ul>
<li><p>are easier to debug</p>
</li>
<li><p>are easier to test</p>
</li>
<li><p>are easier for new developers to understand</p>
</li>
<li><p>break less often on web</p>
</li>
</ul>
<p>Most “navigation bugs” are actually <strong>page design bugs</strong>.</p>
<hr />
<h2 id="heading-final-thoughts">Final thoughts</h2>
<p>Navigation is not just about moving between screens.<br />It’s about defining boundaries and responsibilities.</p>
<p>Before adding a route, ask:</p>
<blockquote>
<p>Should this page survive refresh?</p>
</blockquote>
<p>That one question will save you a lot of pain later.</p>
]]></content:encoded></item><item><title><![CDATA[Scaling Flutter Codebases: Enforcing Rules Without Slowing Teams Down]]></title><description><![CDATA[Working in mid to large-sized teams often means everyone brings their own working style—code formatting preferences, commit message habits, naming conventions, and even different interpretations of language or framework best practices. This diversity...]]></description><link>https://blog.anirudhsingh.in/flutter-pre-commit-hooks-code-quality</link><guid isPermaLink="true">https://blog.anirudhsingh.in/flutter-pre-commit-hooks-code-quality</guid><category><![CDATA[extreme programming]]></category><category><![CDATA[Flutter]]></category><category><![CDATA[Dart]]></category><category><![CDATA[Devops]]></category><category><![CDATA[Git]]></category><category><![CDATA[github-actions]]></category><category><![CDATA[Mobile Development]]></category><category><![CDATA[mobile app development]]></category><category><![CDATA[scaling]]></category><dc:creator><![CDATA[Anirudh Singh]]></dc:creator><pubDate>Sun, 01 Feb 2026 03:35:08 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/7HbD1OOoWl0/upload/02d87f3da6eee60d0dd7bfd252b8a20a.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Working in mid to large-sized teams often means everyone brings their own working style—code formatting preferences, commit message habits, naming conventions, and even different interpretations of language or framework best practices. This diversity works fine initially, but without enforced standards, it quickly turns into a serious problem.</p>
<p>Over time, lack of consistency slows everything down. Onboarding new developers takes longer than it should, moving code from development to production becomes frustrating, and the overall developer experience degrades—costing both productivity and money.</p>
<p>I’ve personally seen teams struggle with untraceable changes due to inconsistent commit messages, unnecessary merge conflicts caused by mismatched formatting, and blocked deployments because a single broken test brought the entire CI pipeline to a halt. Add to this the increased time from writing code to shipping it, and delayed timelines become inevitable.</p>
<p>But what if these issues could be prevented at the source? What if language, framework, and project-specific rules were enforced automatically—so code that didn’t meet the standards simply couldn’t be committed in the first place?</p>
<h3 id="heading-this-is-where-git-hooks-come-into-play">This is where <strong>Git hooks</strong> come into play.</h3>
<p>Git hooks are configurable scripts that run at specific points in a code’s lifecycle—such as before a commit or before a push. They allow teams to automatically run checks and decide whether an action like a commit or push should succeed or fail, entirely based on predefined rules.</p>
<p>In this article, I’ll focus specifically on setting up a <strong>pre-commit hook for a Flutter project</strong> to enforce essential quality checks before any code is committed.</p>
<p>A commit will only succeed if:</p>
<ul>
<li><p>There are no compilation or linting issues</p>
</li>
<li><p>The code is formatted according to Dart’s formatting guidelines</p>
</li>
<li><p>All tests pass successfully</p>
</li>
<li><p>Commit messages follow project-specific conventions</p>
</li>
</ul>
<p>You can learn more about Git hooks and how to configure them here. <em>(</em><a target="_blank" href="https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks"><em>link</em></a><em>)</em></p>
<h3 id="heading-pre-commit-workflow-from-a-developers-perspective">Pre-Commit Workflow From a Developer’s Perspective</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1769915838089/ba8510c1-c84d-4c06-9518-7eb71d4afe38.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-setting-up-the-pre-commit-hook">Setting Up the Pre-Commit Hook</h3>
<p>Git hooks live inside the <code>.git/hooks</code> directory of a repository. These hooks are <strong>local to the developer’s machine</strong>, which means they don’t get committed to version control by default.</p>
<p>To start, navigate to your project root and create a <code>pre-commit</code> file:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> .git/hooks
touch pre-commit
chmod +x pre-commit
</code></pre>
<p>This <code>pre-commit</code> file is the script Git will automatically execute every time a developer runs <code>git commit</code>.</p>
<blockquote>
<p>If this script exits with a non-zero status code, the commit is rejected.</p>
</blockquote>
<p>That’s the only rule you need to remember.</p>
<h2 id="heading-writing-the-pre-commit-script">Writing the Pre-Commit Script</h2>
<p>The script can be written in Bash (most common), and its responsibility is simple:<br /><strong>run checks → fail if something breaks</strong>.</p>
<p>Below is a basic but production-ready example for a Flutter project.</p>
<pre><code class="lang-bash"><span class="hljs-meta">#!/bin/sh</span>

<span class="hljs-built_in">echo</span> <span class="hljs-string">"Running pre-commit checks..."</span>

<span class="hljs-comment"># 1. Static analysis</span>
<span class="hljs-built_in">echo</span> <span class="hljs-string">"Running flutter analyze..."</span>
flutter analyze
<span class="hljs-keyword">if</span> [ $? -ne 0 ]; <span class="hljs-keyword">then</span>
  <span class="hljs-built_in">echo</span> <span class="hljs-string">"❌ flutter analyze failed"</span>
  <span class="hljs-built_in">exit</span> 1
<span class="hljs-keyword">fi</span>

<span class="hljs-comment"># 2. Code formatting</span>
<span class="hljs-built_in">echo</span> <span class="hljs-string">"Checking code formatting..."</span>
dart format --set-exit-if-changed .
<span class="hljs-keyword">if</span> [ $? -ne 0 ]; <span class="hljs-keyword">then</span>
  <span class="hljs-built_in">echo</span> <span class="hljs-string">"❌ Code formatting issues found"</span>
  <span class="hljs-built_in">echo</span> <span class="hljs-string">"Run: dart format ."</span>
  <span class="hljs-built_in">exit</span> 1
<span class="hljs-keyword">fi</span>

<span class="hljs-comment"># 3. Tests</span>
<span class="hljs-built_in">echo</span> <span class="hljs-string">"Running tests..."</span>
flutter <span class="hljs-built_in">test</span>
<span class="hljs-keyword">if</span> [ $? -ne 0 ]; <span class="hljs-keyword">then</span>
  <span class="hljs-built_in">echo</span> <span class="hljs-string">"❌ Tests failed"</span>
  <span class="hljs-built_in">exit</span> 1
<span class="hljs-keyword">fi</span>

<span class="hljs-built_in">echo</span> <span class="hljs-string">"✅ All pre-commit checks passed"</span>
<span class="hljs-built_in">exit</span> 0
</code></pre>
<p>At this point, you’ve already eliminated:</p>
<ul>
<li><p>broken builds</p>
</li>
<li><p>inconsistent formatting</p>
</li>
<li><p>failing tests entering the repo</p>
</li>
</ul>
<p>All <strong>before</strong> the code ever leaves a developer’s machine.</p>
<h2 id="heading-enforcing-commit-message-conventions">Enforcing Commit Message Conventions</h2>
<p>Pre-commit hooks don’t receive the commit message by default.<br />For that, Git provides another hook called <code>commit-msg</code>.</p>
<p>Create it alongside <code>pre-commit</code>:</p>
<pre><code class="lang-bash">touch .git/hooks/commit-msg
chmod +x .git/hooks/commit-msg
</code></pre>
<p>Example <code>commit-msg</code> script:</p>
<pre><code class="lang-bash"><span class="hljs-meta">#!/bin/sh</span>

COMMIT_MSG_FILE=<span class="hljs-variable">$1</span>
COMMIT_MSG=$(cat <span class="hljs-string">"<span class="hljs-variable">$COMMIT_MSG_FILE</span>"</span>)

PATTERN=<span class="hljs-string">"^(feat|fix|chore|docs|refactor|test): .{10,}"</span>

<span class="hljs-keyword">if</span> ! <span class="hljs-built_in">echo</span> <span class="hljs-string">"<span class="hljs-variable">$COMMIT_MSG</span>"</span> | grep -Eq <span class="hljs-string">"<span class="hljs-variable">$PATTERN</span>"</span>; <span class="hljs-keyword">then</span>
  <span class="hljs-built_in">echo</span> <span class="hljs-string">"❌ Invalid commit message format"</span>
  <span class="hljs-built_in">echo</span> <span class="hljs-string">"Expected format:"</span>
  <span class="hljs-built_in">echo</span> <span class="hljs-string">"feat: short meaningful description"</span>
  <span class="hljs-built_in">exit</span> 1
<span class="hljs-keyword">fi</span>

<span class="hljs-built_in">exit</span> 0
</code></pre>
<p>Now:</p>
<ul>
<li><p>commits without context</p>
</li>
<li><p>vague messages like <code>fix</code>, <code>temp</code>, <code>wip</code></p>
</li>
</ul>
<p><strong>never make it into history</strong>.</p>
<h2 id="heading-making-this-work-in-large-teams">Making This Work in Large Teams</h2>
<p>One important thing to address:<br /><code>.git/hooks</code> is not shared by default.</p>
<p>In real teams, you’ll want to:</p>
<ul>
<li><p>store these scripts in a versioned directory (e.g. <code>scripts/git-hooks</code>)</p>
</li>
<li><p>add a small setup script that copies them into <code>.git/hooks</code> and probably add it to the <strong>Makefile</strong> with a simple setup alias</p>
</li>
<li><p>document this as a <strong>mandatory onboarding step in the README</strong></p>
</li>
</ul>
<p>This keeps the rules consistent.</p>
<h2 id="heading-why-this-approach-scales">Why This Approach Scales</h2>
<p>At scale, this setup does something very important:</p>
<ul>
<li><p>Developers get <strong>instant feedback</strong></p>
</li>
<li><p>CI pipelines stop failing for trivial reasons</p>
</li>
<li><p>Code reviews focus on logic, not hygiene</p>
</li>
<li><p>Teams stop arguing about formatting and conventions</p>
</li>
<li><p>New developers ramp up faster without memorizing rules</p>
</li>
</ul>
<p>Most importantly, it removes humans from enforcing things that machines are better at enforcing.</p>
<h2 id="heading-closing-thoughts">Closing Thoughts</h2>
<p>Pre-commit hooks are not about control or restriction.<br />They’re about <strong>protecting focus</strong>.</p>
<p>By enforcing basic quality checks at the earliest possible point, teams avoid unnecessary friction later in the development lifecycle. When done right, these hooks become invisible—developers don’t think about them, because things “just work”.</p>
<p>If you’re working with a growing Flutter codebase and struggling with consistency, start small:<br />add one check, let the team feel the benefit, and evolve from there.</p>
<p><mark>This approach closely follows the principles from </mark> <a target="_blank" href="https://martinfowler.com/bliki/ExtremeProgramming.html"><em><mark>Extreme Programming Explained</mark></em></a> <mark> by Kent Beck—particularly fast feedback and building quality in from the start. Pre-commit hooks bring those ideas directly into the local development workflow, long before CI or code reviews come into play.</mark></p>
]]></content:encoded></item><item><title><![CDATA[Lessons learnt from How to win friends and influence people]]></title><description><![CDATA[I recently completed “How to Win Friends and Influence People” and oh my god, what a change in perspective I have now. You have no idea how even little efforts can change your image in the eyes of others.

Don’t criticise or condemn anyone. It’s far ...]]></description><link>https://blog.anirudhsingh.in/lessons-learnt-from-how-to-win-friends-and-influence-people</link><guid isPermaLink="true">https://blog.anirudhsingh.in/lessons-learnt-from-how-to-win-friends-and-influence-people</guid><category><![CDATA[how to win friends and influence people]]></category><category><![CDATA[dale carnegie lessons]]></category><category><![CDATA[build relationships]]></category><category><![CDATA[influence people]]></category><category><![CDATA[communication skills]]></category><category><![CDATA[Life lessons]]></category><category><![CDATA[motivation]]></category><category><![CDATA[self-help]]></category><dc:creator><![CDATA[Anirudh Singh]]></dc:creator><pubDate>Wed, 03 Sep 2025 05:32:37 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/zYFXOpfcjc0/upload/8910727db0ead87b59196b6d87137cb4.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I recently completed <em>“How to Win Friends and Influence People”</em> and oh my god, what a change in perspective I have now. You have no idea how even little efforts can change your image in the eyes of others.</p>
<ol>
<li><p>Don’t criticise or condemn anyone. It’s far better (from a selfish POV and profitable as well) to fix your habits and condition first. If you really want to express something about someone, praise them for their good habits rather than pointing out the bad ones. If things are that bad between you and the other person that you don’t even want to talk then write all your frustration and complaints down and forget about it.</p>
</li>
<li><p>The big secret of dealing with people is real good appreciation and finding good about them rather than digging for flaws. We can do anything for our families so that they are not hungry, but the universal craving for appreciation gets hidden somewhere unnoticed.</p>
</li>
<li><p>If you really want to make someone do something, Instead of telling them how is it going to impact or profit you, tell them how it will benefit them. The core condition being it should be mutually beneficial.</p>
</li>
<li><p>Be genuinely interested in people. There’s no point in assuming that people are interested in you. No-one interested in anyone else apart from themselves. So if you truly want to be someone’s friend then show genuine interest.</p>
</li>
<li><p>Carry a smile when meeting/greeting someone. That’s the best first impression anyone can create on anybody. It shows that you are really glad to see/interact with that person.</p>
</li>
<li><p>This one might be obvious, but really tough (at least for me 🥹), bring this into habit to remember people’s names. Only and the most pleasant word for a person is their own name. People like hearing their names, so it can bring a lot to the table if we follow this wisely.</p>
</li>
<li><p>Be a really really good and empathetic listener. Instead of listening what the other person is saying, most of the folks tend to think on the back of their heads that “What to say next?“ leading to lost focus and a disconnect from the conversation. Most of the people just want to get listened to rather than needing advice or sympathy or anything like that.</p>
</li>
<li><p>Before interacting with anyone, if you know or can know what they are or might be interested in, do a bit of studying around that if you don’t already know and talk to the person about their interests first and foremost, it makes you instantly likeable.</p>
</li>
<li><p>Make the other person feel genuinely important and being listened to.</p>
</li>
<li><p>Avoid unnecessary arguments, struggling through the unnecessary arguments is nothing more than pure waste of your energy and in most of the cases reputation as well. At the end of the day, you should have as many happy people around you as possible.</p>
</li>
<li><p>Never tell anyone that they’re wrong directly, deal with the situation diplomatically. If all goes well they’ll accept it themselves.</p>
</li>
<li><p>If you know you’re wrong, admit it ASAP and don’t let or wait for someone else to point it out.</p>
</li>
<li><p>Start every conversation in a friendly way, even the tough ones like raising a grievance. Even the toughest people can be melted like this.</p>
</li>
<li><p>Start the conversation in such a way which makes other person respond in Yes’es. Once through the initial opening conversation you get a couple of yeses, the probability of other person agreeing with you will be really high as it is psychologically easier for anyone to end a conversation in a affirmative direction if most of it was positive.</p>
</li>
<li><p>Let the other person do most of the talking, or else when you are boasting or speaking they will either be (not everyone but a few for sure) becoming jealous of you or they might start thinking in their brains whatever they wanted to tell you about, not paying attention to even a single bit you said.</p>
</li>
<li><p>Whenever you are selling or convincing someone for something, never try to push your ideas down their throats. Instead converse wisely in such a way which makes feel the other person that the idea is theirs.</p>
</li>
<li><p>Praise before you point out someone’s errors. It’s psychologically more probable that the person would listen to their faults or failures after hearing some genuine praise about them.</p>
</li>
</ol>
]]></content:encoded></item><item><title><![CDATA[Lessons learnt from How to steal like an artist]]></title><description><![CDATA[I don’t know how many of you follow this, but I recently started this habit of asking ChatGPT about acting like a constructive critique, analyse my personality from all the day to day conversations we have and recommend me books based on things I can...]]></description><link>https://blog.anirudhsingh.in/lessons-learnt-from-how-to-steal-like-an-artist</link><guid isPermaLink="true">https://blog.anirudhsingh.in/lessons-learnt-from-how-to-steal-like-an-artist</guid><category><![CDATA[books]]></category><category><![CDATA[Life lessons]]></category><dc:creator><![CDATA[Anirudh Singh]]></dc:creator><pubDate>Mon, 04 Aug 2025 13:21:01 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/cqK6Ysfhhb0/upload/9e0a73db20a756c02e2ec876099183f8.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I don’t know how many of you follow this, but I recently started this habit of asking ChatGPT about acting like a constructive critique, analyse my personality from all the day to day conversations we have and recommend me books based on things I can improve with proper reasoning for every suggestion.</p>
<p>It has suggested me quite a good reads like “<strong>The Almanack of Naval Ravikant</strong>“, “<strong>Talking to Strangers</strong>“ and the one which I recently read “<strong>How to steal like an artist</strong>“.</p>
<p>This book changed my perspective of how I perceived creativity. This one quote which stuck with me from the same book</p>
<p><strong>“You shouldn’t want to look like your hero, you should want to see like your hero“.</strong></p>
<p>These are the few things which stuck with me and I have either already incorporated these in my life or I will soon. Here goes the list</p>
<ol>
<li><p>Nothing in this universe is original, as also said in the Bible “<strong>There is nothing new under the Sun</strong>“.</p>
</li>
<li><p>Creativity = Pre-existing thoughts and ideas re-imagined or remixed.</p>
</li>
<li><p>A method which I learnt and am very curios to try out is, Think about who you superheroes are (people whom or whose work you look upon to), go as deep as you can and understand/observer each piece of their work and their thinking philosophy. Then create your own thought tree and see what if some of your heroes collaborated and made something. Think about that or build that and most probably this remix would be uniquely creative and somewhat your own.</p>
</li>
<li><p>Creativity is stealing and making it better, whereas Rip-Offs or de-graded products are just failed attempts of trying the same.</p>
</li>
<li><p><strong>Garbage in - Garbage out</strong>: You might know this already, “Always surround yourself with people better than you” or simply put “If your are the smartest in the room, its high time to change the room“. As the environment reflects on one’s work.</p>
</li>
<li><p>Try engaging with your work as much emotionally as you can. If you’re an artist, there are tools nowadays where you just click, drag mouse or hit buttons on the keyboard and get stuff done. Same goes for most of the professions nowadays, separate out an analog and a digital setup (or just keep aside your laptop in separate table drawer) and scribble things out using pen and paper get the good old touch and feel of paper. For engineering backgrounds, use notebook for drawing flowcharts and charting out thought processes, you’ll feel much more deeply engaged with your work.</p>
</li>
<li><p>In this digital-era, where everyone is chasing cheap dopamine at the cost of lowered attention spans. Take a step back and embrace <strong>boredom.</strong> Book some time for yourself where you just get “bored“. Stare a random spot on wall, lay on your bed and look at the ceiling, stand in your balcony/garden and watch the trees. This is the mental state where you’ll encourage creative ideas to form.</p>
</li>
</ol>
]]></content:encoded></item></channel></rss>