<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged solid</title>
<link>https://coderlegion.com/tag/solid</link>
<description>Powered by Question2Answer</description>
<item>
<title>The Indispensable Practice of Abstraction: Decoupling Your Frontend Logic from External Libraries</title>
<link>https://coderlegion.com/6656/indispensable-practice-abstraction-decoupling-your-frontend-logic-external-libraries</link>
<description>&lt;p&gt;In modern frontend architecture, the principle of &lt;strong&gt;separation of concerns&lt;/strong&gt; is paramount. Your application's core logic (business rules, state, and UI) must be entirely isolated from the specific Application Programming Interfaces (APIs) of third-party libraries. This best practice, known as creating an &lt;strong&gt;Anti-Corruption Layer (ACL)&lt;/strong&gt;, is the difference between a flexible, maintainable codebase and a fragile, tightly coupled one.&lt;/p&gt;
&lt;h3 id=&quot;the-problem-tight-coupling-and-vendor-lock-in&quot;&gt;The Problem: Tight Coupling and Vendor Lock-in&lt;/h3&gt;
&lt;p&gt;When you directly integrate a library's specific methods into your components, you create a dependency that is rigid and difficult to change. For instance, directly using &lt;code&gt;window.Stripe.redirectToCheckout()&lt;/code&gt; in a checkout button component couples your UI directly to the &lt;strong&gt;Stripe SDK&lt;/strong&gt;.&lt;/p&gt;
&lt;h4 id=&quot;the-core-risks-&quot;&gt;The Core Risks:&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Vendor Lock-in:&lt;/strong&gt; You are locked into a specific vendor (e.g., Supabase, Auth0, Stripe). If a better, cheaper, or more performant alternative emerges, changing libraries means a massive, error-prone refactor across your entire application.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Difficult Testing:&lt;/strong&gt; Unit testing a component that directly calls an external SDK requires complex mocking of that entire library's API, cluttering your test setup.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Inconsistent Code:&lt;/strong&gt; Error handling, data formatting, and asynchronous logic become inconsistent when repeated across many components.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;the-solution-the-abstraction-layer&quot;&gt;The Solution: The Abstraction Layer&lt;/h3&gt;
&lt;p&gt;The solution is an intermediate layer—a &lt;strong&gt;Service&lt;/strong&gt;, &lt;strong&gt;Repository&lt;/strong&gt;, or &lt;strong&gt;Utility&lt;/strong&gt; module—that acts as the single point of entry for all library interactions.&lt;/p&gt;
&lt;h4 id=&quot;how-abstraction-works-&quot;&gt;How Abstraction Works:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Generic Interface:&lt;/strong&gt; Define functions using &lt;strong&gt;domain language&lt;/strong&gt; (e.g., &lt;code&gt;getProducts&lt;/code&gt;, &lt;code&gt;processPayment&lt;/code&gt;) that describe &lt;em&gt;what&lt;/em&gt; your app needs to do, not &lt;em&gt;how&lt;/em&gt; it's done.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Encapsulation:&lt;/strong&gt; All library-specific imports and calls are &lt;em&gt;encapsulated&lt;/em&gt; within this single module.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Standardization:&lt;/strong&gt; The layer is responsible for translating the external library's quirky response formats and error structures into the simple, predictable formats your application expects.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;1-abstracting-data-access-the-repository-pattern-&quot;&gt;1. Abstracting Data Access (The Repository Pattern)&lt;/h3&gt;
&lt;p&gt;This is the most critical area for separation. We &lt;strong&gt;separate the database library (like Supabase) from the code itself&lt;/strong&gt; by creating a &lt;strong&gt;Service&lt;/strong&gt; or &lt;strong&gt;Repository&lt;/strong&gt; layer with &lt;strong&gt;generic names&lt;/strong&gt;.&lt;/p&gt;
&lt;h4 id=&quot;example-decoupling-data-fetching-supabase-vs-custom-rest-api-&quot;&gt;Example: Decoupling Data Fetching (Supabase vs. Custom REST API)&lt;/h4&gt;
&lt;p&gt;Let's assume our application needs to fetch product data.&lt;/p&gt;
&lt;h5 id=&quot;tightly-coupled-bad-&quot;&gt;&lt;strong&gt;Tightly Coupled (Bad)&lt;/strong&gt;&lt;/h5&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// src/components/ProductList.jsx

import { supabase } from '../lib/supabaseClient'; // Directly importing the library

const fetchProducts = async () =&amp;gt; {
  // Hard dependency on Supabase API structure (from, select, etc.)
  const { data: products, error } = await supabase.from('products').select(`
    id,
    name,
    price_cents,
    inventory:product_inventory(stock_count)
  `);

  if (error) throw new Error(error.message);

  // Data requires complex, specific formatting here
  return products.map(p =&amp;gt; ({
      ...p,
      price: p.price_cents / 100,
      stock: p.inventory[0]?.stock_count || 0
  }));
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h5 id=&quot;decoupled-good-with-a-service&quot;&gt;&lt;strong&gt;Decoupled (Good) with a Service&lt;/strong&gt;&lt;/h5&gt;
&lt;p&gt;First, the abstraction layer handles the library call and data standardization:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// src/services/productService.js (The Abstraction Layer)

import { supabase } from '../lib/supabaseClient'; // Encapsulates the library dependency

// Defines a predictable structure for the rest of the application
const formatProduct = (productData) =&amp;gt; ({
    id: productData.id,
    name: productData.name,
    price: productData.price_cents / 100, // Standardize cents to dollars
    stock: productData.product_inventory[0]?.stock_count || 0,
});

export const productService = {
  // Generic function name: getProducts
  getProducts: async () =&amp;gt; {
    // 1. Library-specific call is confined here
    const { data, error } = await supabase.from('products').select(`
      id,
      name,
      price_cents,
      product_inventory(stock_count)
    `);

    if (error) {
      // 2. Standardize error handling
      throw new Error(&quot;Failed to retrieve products from data source.&quot;);
    }
    // 3. Standardize and return clean, formatted data
    return data.map(formatProduct);
  },
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Second, the component uses the clean interface:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// src/components/ProductList.jsx

import { productService } from '../services/productService'; // Generic interface

const fetchProducts = async () =&amp;gt; {
  // Clean, generic call. The component doesn't know (or care) about Supabase
  const products = await productService.getProducts();
  // ... use products
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;The Flexibility:&lt;/strong&gt; If you replace Supabase with a custom &lt;strong&gt;REST API&lt;/strong&gt; using &lt;strong&gt;Axios&lt;/strong&gt;, you only change &lt;code&gt;productService.js&lt;/code&gt;. The component code remains &lt;strong&gt;untouched&lt;/strong&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// src/services/productService.js (Rewritten for Custom REST API via Axios)

import axios from 'axios'; // Encapsulates the new dependency

// formatProduct function remains the same!

export const productService = {
  getProducts: async () =&amp;gt; {
    try {
      // New library-specific call
      const response = await axios.get('/api/v1/products');
      // Assume the API returns raw data that still needs to be formatted
      return response.data.map(formatProduct);
    } catch (error) {
      throw new Error(&quot;Failed to retrieve products from REST API.&quot;);
    }
  },
};
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;2-abstraction-for-all-external-dependencies&quot;&gt;2. Abstraction for All External Dependencies&lt;/h3&gt;
&lt;p&gt;This principle should be applied to every external dependency to ensure your application is &lt;strong&gt;future-proof&lt;/strong&gt; and &lt;strong&gt;agile&lt;/strong&gt; when a library becomes obsolete or unsuitable.&lt;/p&gt;
&lt;h4 id=&quot;a-payment-sdks-e-g-stripe-&quot;&gt;A. Payment SDKs (e.g., Stripe)&lt;/h4&gt;
&lt;p&gt;Your checkout flow must be protected from library changes.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;Component's Need &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Tightly Coupled (Bad) &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Decoupled (Good)&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Payment:&lt;/strong&gt; Redirect to checkout screen. &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Component calls: &lt;code&gt;window.Stripe.redirectToCheckout({ /*... config */ })&lt;/code&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Component calls: &lt;code&gt;paymentService.initiateCheckout(cartId)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Abstraction Layer:&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;None &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;paymentService.js&lt;/code&gt; handles all Stripe SDK imports, configurations, and API calls.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Flexibility:&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Switching to PayPal or Square requires modifying every component that touches payment. &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Only &lt;code&gt;paymentService.js&lt;/code&gt; needs to be rewritten. The &lt;code&gt;initiateCheckout&lt;/code&gt; function signature is preserved.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 id=&quot;b-internationalization-i18n-libraries-e-g-formatjs-vs-react-i18next-&quot;&gt;B. Internationalization (i18n) Libraries (e.g., FormatJS vs. react-i18next)&lt;/h4&gt;
&lt;p&gt;Components should only request a translated string using a generic key.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;Component's Need &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Tightly Coupled (Bad) &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Decoupled (Good)&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Translation:&lt;/strong&gt; Get a welcome message. &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Component uses: &lt;code&gt;const { formatMessage } = useIntl(); return formatMessage(messages.welcome)&lt;/code&gt; (FormatJS-specific) &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Component uses: &lt;code&gt;const { T } = useAppTranslation(); return T('welcome_message', { name: user.name })&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Abstraction Layer:&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;None &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;useAppTranslation.js&lt;/code&gt; wraps the specific library's translation hook/functionality.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Flexibility:&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Switching to a different i18n library means changing hook imports and function names in every component. &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Only the implementation inside &lt;code&gt;useAppTranslation.js&lt;/code&gt; changes, maintaining the generic &lt;code&gt;T&lt;/code&gt; interface.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 id=&quot;c-client-side-authentication-e-g-auth0-vs-firebase-auth-&quot;&gt;C. Client-Side Authentication (e.g., Auth0 vs. Firebase Auth)&lt;/h4&gt;
&lt;p&gt;Decoupling auth ensures your routing and UI logic are stable regardless of the provider.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;Component's Need &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Tightly Coupled (Bad) &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Decoupled (Good)&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Auth Status:&lt;/strong&gt; Check if a user is logged in. &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Router guard calls: &lt;code&gt;Auth0Client.isAuthenticated()&lt;/code&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Router guard calls: &lt;code&gt;authService.isLoggedIn()&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Abstraction Layer:&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;None &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;authService.js&lt;/code&gt; or &lt;code&gt;useAuth&lt;/code&gt; hook handles token storage, session checks, and specific library methods.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Flexibility:&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;If you migrate from Auth0 to Firebase Auth, you need to update logic everywhere Auth0's specific methods are called. &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Only &lt;code&gt;authService.js&lt;/code&gt; is rewritten to use Firebase SDK methods, keeping the generic interface intact.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h3 id=&quot;key-takeaways-for-robust-frontend-architecture&quot;&gt;Key Takeaways for Robust Frontend Architecture&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;Practice &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Principle &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Rationale&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Name Generically&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Domain Language Over Technology:&lt;/strong&gt; Use names like &lt;code&gt;userService&lt;/code&gt; or &lt;code&gt;paymentService&lt;/code&gt;, not &lt;code&gt;clerkAuthService&lt;/code&gt; or &lt;code&gt;supabasePosts&lt;/code&gt;. &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;The name reflects the &lt;em&gt;domain concept&lt;/em&gt;, not the &lt;em&gt;implementation detail&lt;/em&gt;.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Standardize Data&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;ACL Responsibility:&lt;/strong&gt; The service layer must convert the library's output (e.g., a complex object with metadata) into the exact clean object your app needs. &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;The rest of your application code deals with a single, predictable data format.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Error Handling&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Translate Errors:&lt;/strong&gt; Catch library-specific errors and re-throw them as simple, standardized application-level errors (e.g., a &lt;code&gt;DataFetchError&lt;/code&gt;). &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Components only need to handle a few generic error types, simplifying UI feedback.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Apply Universally&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Systematic Abstraction:&lt;/strong&gt; If you use a library, abstract it. This applies to data access, state management, auth, analytics, utility functions, and more. &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Maximizes code agility, testability, and reduces maintenance complexity across the board.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;By committing to this principle of &lt;strong&gt;universal abstraction&lt;/strong&gt;, you build a frontend application that is architecturally sound, easier to test, and perfectly positioned to adapt to the technological shifts of tomorrow.&lt;/p&gt;
</description>
<category>Time Management</category>
<guid isPermaLink="true">https://coderlegion.com/6656/indispensable-practice-abstraction-decoupling-your-frontend-logic-external-libraries</guid>
<pubDate>Mon, 20 Oct 2025 11:14:55 +0000</pubDate>
</item>
<item>
<title>SOLID for Rails Developers, real-life tour (with code)</title>
<link>https://coderlegion.com/6507/solid-for-rails-developers-real-life-tour-with-code</link>
<description>&lt;p&gt;You’ve heard “keep controllers skinny, models thin”  but &lt;strong&gt;how&lt;/strong&gt;? The SOLID principles are a compact set of habits that keep Rails apps change-friendly as they grow. Here’s a practical, with bite-size Rails examples you can use today.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-is-solid-&quot;&gt;What is SOLID?&lt;/h3&gt;
&lt;p&gt;Before we jump into code, let’s meet the SOLID squad  five principles that save your codebase from chaos, confusion, and late-night debugging sessions.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;S&lt;/strong&gt; ingle Responsibility Principle (SRP): Every class should have one job. Not two. Not three. Just one.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;O&lt;/strong&gt; pen/Closed Principle (OCP): Your code should be open for extension, but closed for modification.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;L&lt;/strong&gt; iskov Substitution Principle (LSP): If your code says it’s a duck, it should quack like a duck.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;I&lt;/strong&gt; nterface Segregation Principle (ISP): Don’t force your classes to implement methods they don’t need.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;D&lt;/strong&gt; ependency Inversion Principle (DIP): Depend on abstractions, not concrete things.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ready to &lt;strong&gt;SOLIDify&lt;/strong&gt; your Rails code? Let’s go!&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;a-running-scenario&quot;&gt;A running scenario&lt;/h3&gt;
&lt;p&gt;Imagine a small trading API for “Tomato” prices:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We fetch intraday &lt;code&gt;PriceTick&lt;/code&gt;s.&lt;/li&gt;
&lt;li&gt;We compute the best daily gain (buy low, sell high once).&lt;/li&gt;
&lt;li&gt;We expose endpoints for analytics.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We’ll use this scenario to illustrate each principle.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;s-single-responsibility-principle-srp-&quot;&gt;S  Single Responsibility Principle (SRP)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;A class should have one reason to change.&lt;/strong&gt;&lt;/p&gt;
&lt;h4 id=&quot;bad-a-god-controller-&quot;&gt;Bad (a God Controller)&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-ruby&quot;&gt;class AnalyticsController &amp;lt; ApplicationController
  def show
    date = Date.iso8601(params[:date])
    ticks = PriceTick.where(time: date.all_day).order(:time)
    # validation, parsing, domain logic, formatting… all here 
    min = ticks.first.value_cents
    best = 0
    ticks.drop(1).each do |t|
      best = [best, t.value_cents - min].max
      min = [min, t.value_cents].min
    end
    render json: { date: date, best_gain_cents: best * 100 }
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;good-split-responsibilities-&quot;&gt;Good (split responsibilities)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Query&lt;/strong&gt; loads just what’s needed&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Service&lt;/strong&gt; computes the gain&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Controller&lt;/strong&gt; orchestrates and renders&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;app/services/best_gain_calculator.rb:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ruby&quot;&gt;class BestGainCalculator
  def self.call(ticks, quantity: 100)
    enum = ticks.each
    first = enum.next rescue (return 0)
    min   = first.value_cents
    best  = 0
    enum.each do |t|
      diff = t.value_cents - min
      best = diff if diff &amp;gt; best
      min  = t.value_cents if t.value_cents &amp;lt; min
    end
    best * quantity
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;app/queries/price_ticks_on_day.rb:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ruby&quot;&gt;class PriceTicksOnDay
  def self.call(date)
    PriceTick.where(time: date.all_day).select(:time, :value_cents).order(:time)
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;app/controllers/api/v1/best_gains_controller.rb&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ruby&quot;&gt;class Api::V1::BestGainsController &amp;lt; ApplicationController
  def show
    ticks = PriceTicksOnDay.call(date)
    render json: { best_gain_cents: BestGainCalculator.call(ticks) }
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Win:&lt;/strong&gt; Each class changes for exactly one reason.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;o-open-closed-principle-ocp-&quot;&gt;O  Open/Closed Principle (OCP)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Open for extension, closed for modification.&lt;/strong&gt; Add behaviors without rewriting stable code.&lt;/p&gt;
&lt;h4 id=&quot;real-life-new-gain-strategies-without-editing-the-controller&quot;&gt;Real life: new gain strategies without editing the controller&lt;/h4&gt;
&lt;p&gt;app/services/gain_strategies/base.rb:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ruby&quot;&gt;module GainStrategies
  class Base
    def call(ticks) = raise NotImplementedError
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;app/services/gain_strategies/single_trade.rb&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ruby&quot;&gt;module GainStrategies
  class SingleTrade &amp;lt; Base
    def call(ticks) = BestGainCalculator.call(ticks)
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;app/services/gain_strategies/multi_trade.rb&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ruby&quot;&gt;module GainStrategies
  class MultiTrade &amp;lt; Base
    def call(ticks)
      # example: sum of all positive deltas
      prev = nil
      profit = 0
      ticks.each do |t|
        if prev
          delta = t.value_cents - prev
          profit += delta if delta.positive?
        end
        prev = t.value_cents
      end
      profit * 100 # quantity stub
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;app/controllers/api/v1/best_gains_controller.rb:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ruby&quot;&gt;def show
  ticks = PriceTicksOnDay.call(date)

  strategy = params[:mode] == &quot;multi&quot; ? GainStrategies::MultiTrade.new : GainStrategies::SingleTrade.new
  render json: { best_gain_cents: strategy.call(ticks) }
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Win:&lt;/strong&gt; Adding &lt;code&gt;WeekOverWeek&lt;/code&gt; or &lt;code&gt;TrailingStop&lt;/code&gt; strategies doesn’t touch controller code.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;l-liskov-substitution-principle-lsp-&quot;&gt;L  Liskov Substitution Principle (LSP)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Subtypes must be usable wherever their base type is expected.&lt;/strong&gt;&lt;/p&gt;
&lt;h4 id=&quot;real-life-sti-pitfalls-vs-composition&quot;&gt;Real life: STI pitfalls vs composition&lt;/h4&gt;
&lt;p&gt;If you have &lt;code&gt;PriceTick&lt;/code&gt; and you try STI for &lt;code&gt;FxPriceTick &amp;lt; PriceTick&lt;/code&gt; but change &lt;code&gt;value_cents&lt;/code&gt; semantics (e.g., store pips), substituting &lt;code&gt;FxPriceTick&lt;/code&gt; where &lt;code&gt;PriceTick&lt;/code&gt; is expected will break calculations.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Better:&lt;/strong&gt; keep interface and units consistent, or &lt;strong&gt;compose&lt;/strong&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ruby&quot;&gt;class PriceTick &amp;lt; ApplicationRecord
  # always expose value in cents
  def value_cents
    super # guaranteed integer cents
  end
end

class FxPriceValue
  def initialize(pips:, pip_value_cents:)
    @pips = pips
    @pip_value_cents = pip_value_cents
  end

  def to_cents = @pips * @pip_value_cents
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Win:&lt;/strong&gt; Anything expecting &lt;code&gt;value_cents&lt;/code&gt; continues to work.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;i-interface-segregation-principle-isp-&quot;&gt;I  Interface Segregation Principle (ISP)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Prefer small, focused interfaces over fat ones.&lt;/strong&gt; Consumers shouldn’t depend on what they don’t use.&lt;/p&gt;
&lt;h4 id=&quot;real-life-split-service-interfaces&quot;&gt;Real life: split service interfaces&lt;/h4&gt;
&lt;p&gt;Bad:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ruby&quot;&gt;class ReportService
  def generate_csv_for_day(date); end
  def generate_pdf_for_week(date); end
  def email_pdf_to(user); end
  def email_csv_to(user); end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Good:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ruby&quot;&gt;class CsvReport
  def call(date); end
end

class PdfReport
  def call(week); end
end

class ReportMailer
  def send_pdf(user, pdf); end
  def send_csv(user, csv); end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Win:&lt;/strong&gt; Each client depends on only what it needs; easier to test and swap.&lt;/p&gt;
&lt;p&gt;In Rails, ISP often means &lt;strong&gt;small POROs&lt;/strong&gt; instead of piling methods into one “service class.”&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;d-dependency-inversion-principle-dip-&quot;&gt;D  Dependency Inversion Principle (DIP)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Depend on abstractions, not concrete classes.&lt;/strong&gt;&lt;/p&gt;
&lt;h4 id=&quot;real-life-inject-gateways-http-cache-payments-&quot;&gt;Real life: inject gateways (HTTP, cache, payments)&lt;/h4&gt;
&lt;p&gt;app/services/price_sync.rb:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ruby&quot;&gt;class PriceSync
  def initialize(client: External::PricesClient.new, cache: Rails.cache)
    @client = client
    @cache  = cache
  end

  def call(date)
    @cache.fetch(&quot;prices:#{date}&quot;, expires_in: 10.minutes) do
      @client.fetch_for(date) # abstraction: responds to `fetch_for`
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;In tests&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ruby&quot;&gt;fake = Class.new { def fetch_for(date) = [{ time: Time.now, value_cents: 10000 }] }.new
result = PriceSync.new(client: fake, cache: ActiveSupport::Cache::MemoryStore.new).call(Date.today)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Win:&lt;/strong&gt; Swap implementations without changing business logic. Fewer brittle tests.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;pulling-it-together-a-tiny-solid-checklist&quot;&gt;Pulling it together: a tiny SOLID checklist&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SRP:&lt;/strong&gt; Does this class change for exactly one reason?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OCP:&lt;/strong&gt; Can I add a variation without editing existing classes?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;LSP:&lt;/strong&gt; Will any subtype respect the expected inputs/outputs/units?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ISP:&lt;/strong&gt; Is this interface minimal for its consumer?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DIP:&lt;/strong&gt; Are external details injected (e.g., clients, storage, time)?&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;a-realistic-solid-endpoint-end-to-end-&quot;&gt;A realistic, SOLID endpoint (end-to-end)&lt;/h3&gt;
&lt;p&gt; app/controllers/api/v1/best_gains_controller.rb&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ruby&quot;&gt;class Api::V1::BestGainsController &amp;lt; ApplicationController
  def show
    ticks = PriceTicksOnDay.call(date)
    strategy = strategy_for(params[:mode])
    best = strategy.call(ticks)
    return render json: { error: &quot;no valid trades&quot; }, status: :unprocessable_entity if best &amp;lt;= 0
    render json: { date: date, best_gain_cents: best }
  end

  private

  def strategy_for(mode)
    mode == &quot;multi&quot; ? GainStrategies::MultiTrade.new : GainStrategies::SingleTrade.new
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Everything above respects SOLID:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The controller just orchestrates.&lt;/li&gt;
&lt;li&gt;Strategies extend behavior (OCP).&lt;/li&gt;
&lt;li&gt;Calculators keep contracts consistent (LSP).&lt;/li&gt;
&lt;li&gt;Tiny interfaces (ISP).&lt;/li&gt;
&lt;li&gt;Dependencies injected (DIP).&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;final-thought&quot;&gt;Final thought&lt;/h3&gt;
&lt;p&gt;Rails gives you speed; SOLID gives you &lt;strong&gt;sustainable speed&lt;/strong&gt;. Start small: extract one PORO today (calculator or query), inject one dependency, and keep your interfaces tiny. Your future self  and your teammates  will thank you.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;bonus-if-solid-were-a-superhero-team-&quot;&gt;Bonus: If SOLID were a superhero team...&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SRP&lt;/strong&gt;: Captain Focus  only fights one villain at a time.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OCP&lt;/strong&gt;: The Extender  always ready for new powers, never rewrites their origin story.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;LSP&lt;/strong&gt;: Duckman  if it walks like a duck and quacks like a duck, it’s Duckman!&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ISP&lt;/strong&gt;: The Minimalist  only carries what’s needed for the mission.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DIP&lt;/strong&gt;: The Abstractionist  never depends on sidekicks, only on the idea of sidekicks.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now go forth and SOLIDify your Rails app  and remember, with great principles comes great maintainability!&lt;/p&gt;
</description>
<category>Web Dev</category>
<guid isPermaLink="true">https://coderlegion.com/6507/solid-for-rails-developers-real-life-tour-with-code</guid>
<pubDate>Thu, 16 Oct 2025 09:47:21 +0000</pubDate>
</item>
<item>
<title>Koin vs Dagger 2 vs Hilt: Which should we use?</title>
<link>https://coderlegion.com/2797/koin-vs-dagger-2-vs-hilt-which-should-we-use</link>
<description>&lt;p&gt;Have you ever heard about Dependency Injection? If you haven’t heard it before, let’s break down Dependency Injection (DI) in the simplest way possible.&lt;/p&gt;
&lt;p&gt;Imagine you’re building a coffee machine. Instead of the machine creating its own coffee beans and water, you provide them from the outside. This way, you can easily swap out different types of beans or water sources without rebuilding the entire machine.&lt;/p&gt;
&lt;p&gt;In coding, Dependency Injection (DI) works the same way. Instead of a class creating its own dependencies, they’re injected from the outside. This makes your code more flexible, testable, and easier to manage. Android frameworks like Dagger 2, Hilt, and Koin help automate this process, so you don’t have to manually pass everything around.&lt;/p&gt;
&lt;p&gt;These three are popular DI frameworks used in Android development. They help manage object creation and dependency resolution, making code cleaner, more modular, and easier to maintain. Let’s deep dive into among of these three and compare them by performance, usability, and community support.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Performance&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When comparing Koin, Dagger 2, and Hilt for dependency injection in Android development, performance plays a crucial role. Dagger 2 is a compile-time dependency injection framework, meaning it generates code at build time, leading to optimal runtime performance with minimal overhead. Hilt, built on top of Dagger 2, maintains the same compile-time benefits while simplifying its setup. Koin, on the other hand, is a runtime dependency injection framework that uses reflection and service locators, making it slightly slower than Dagger 2 and Hilt, especially in large-scale applications. While the difference may not be significant for smaller apps, it can impact performance in more complex dependency graphs.&lt;/p&gt;
&lt;p&gt;Let say we demonstrate a “hello world” app using among of them and this is the result:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=15284996762236585071&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;from the illustration above we can conclude:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Koin: Koin’s compile-time is the fastest because it primarily relies on Kotlin’s reflection and doesn’t involve extensive code generation.&lt;/li&gt;
&lt;li&gt;Dagger 2: Dagger 2’s compile-time is significantly longer due to the code generation process, even for a simple app.&lt;/li&gt;
&lt;li&gt;Hilt: Hilt, being built on Dagger 2, also involves code generation, but it’s often more optimized for Android and might have slightly faster compile times than raw Dagger 2 in a very small project.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Usability&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In terms of usability, Koin is the easiest to implement, requiring minimal boilerplate code with a simple DSL for defining dependencies. Dagger 2 is more complex due to its annotation-based approach, requiring knowledge of components, modules, and generated code. Hilt simplifies Dagger 2’s complexity by introducing annotations like @HiltAndroidApp and @Inject, making dependency injection easier to manage while still leveraging compile-time validation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Community Support and Documentation&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Regarding community support and documentation, Dagger 2 has been widely adopted in the Android ecosystem for years and has extensive documentation. Hilt, as an official AndroidX library, benefits from strong Google backing and comprehensive guides. Koin, while popular among Kotlin developers, has a smaller but growing community. Its documentation is clear but not as extensive as Google’s offerings. Ultimately, Dagger 2 and Hilt are preferred for performance-sensitive and large-scale projects, while Koin is an excellent choice for developers seeking simplicity and faster integration.&lt;/p&gt;
&lt;p&gt;Below are example implementations of Koin, Dagger 2, and Hilt:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Koin Implementation&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Define a module
val appModule = module {
    single { UserRepository() }
}

// Start Koin
class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        startKoin {
            modules(appModule)
        }
    }
}

// Inject dependency
class MyViewModel(private val repository: UserRepository)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Dagger 2 Implementation&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Define a module
@Module
class AppModule {
    @Provides
    fun provideUserRepository(): UserRepository {
        return UserRepository()
    }
}

// Define a component
@Component(modules = [AppModule::class])
interface AppComponent {
    fun inject(activity: MainActivity)
}

// Inject dependency
class MainActivity : AppCompatActivity() {
    @Inject lateinit var repository: UserRepository
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Hilt Implementation&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Define a module
@InstallIn(SingletonComponent::class)
@Module
object AppModule {
    @Provides
    fun provideUserRepository(): UserRepository {
        return UserRepository()
    }
}

// Inject dependency
@HiltAndroidApp
class MyApplication : Application()

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    @Inject lateinit var repository: UserRepository
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Choose wisely what framework you want to use, these trio have &lt;strong&gt;pros&lt;/strong&gt; and &lt;strong&gt;cons&lt;/strong&gt;. You can choose and implement it based on your need. Happy coding :)&lt;/p&gt;
&lt;p&gt;If you want to know more about Koin framework setup, refer this&lt;/p&gt;
&lt;p&gt;Documentation:&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://dagger.dev/&quot;&gt;Dagger2&lt;/a&gt;,&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://dagger.dev/hilt/&quot;&gt;Hilt&lt;/a&gt;,&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://insert-koin.io/docs/quickstart/android/&quot;&gt;Koin&lt;/a&gt;&lt;/p&gt;
</description>
<category>Mobile Dev</category>
<guid isPermaLink="true">https://coderlegion.com/2797/koin-vs-dagger-2-vs-hilt-which-should-we-use</guid>
<pubDate>Wed, 23 Apr 2025 04:26:52 +0000</pubDate>
</item>
<item>
<title>Koin vs Hilt</title>
<link>https://coderlegion.com/1506/koin-vs-hilt</link>
<description>&lt;p&gt;In &lt;strong&gt;object-oriented programming (OOP)&lt;/strong&gt;, especially in &lt;em&gt;Android Development&lt;/em&gt;, to make our program code cleaner and maintainable, one of the principles we are used to apply is &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://simple.wikipedia.org/wiki/SOLID_(object-oriented_design)&quot;&gt;SOLID&lt;/a&gt;&lt;/strong&gt;. one of the points in the SOLID principle is &lt;strong&gt;Dependency Inversion&lt;/strong&gt;.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;One should depend upon abstractions, not concretions.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://en.wikipedia.org/wiki/Inversion_of_control&quot;&gt;Inversion of Control (IoC)&lt;/a&gt;&lt;/strong&gt; is a design pattern used to represent Dependency Inversion in real software.&lt;/p&gt;
&lt;p&gt;That makes Dependency Injection popular.&lt;/p&gt;
&lt;h3 id=&quot;what-is-dependency-injection-&quot;&gt;What is Dependency Injection?&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;Dependency injection&lt;/em&gt; (DI) is a software engineering technique that involves making the interactions between objects as thin as possible through specific dependencies. This allows for loosely coupled code — or code that only depends on the required portion of a separate class — to run. And it reduces hard-coded dependencies and allows for cleaner, more flexible code.&lt;/p&gt;
&lt;p&gt;some of most popular Dependency Injection frameworks are &lt;strong&gt;Koin&lt;/strong&gt; and &lt;strong&gt;Hilt&lt;/strong&gt;. &lt;/p&gt;
&lt;h3 id=&quot;then-what-s-different-between-koin-and-hilt-&quot;&gt;Then what's different between Koin and Hilt?&lt;/h3&gt;
&lt;p&gt;Let’s start by talking about Koin first.&lt;/p&gt;
&lt;p&gt;Koin stands out for its simplicity and flexibility. It is a simple DI framework that does not require annotations or code generation. Instead, it makes use of the Domain Specific Language (DSL) provided by Kotlin to specify dependencies and modules.&lt;/p&gt;
&lt;p&gt;Koin’s ease of use is one of its benefits. Its minimum setup requirements and its ability to create dependencies with a clear, readable syntax are the aspects that make it stand out. Therefore, Koin is easy for developers who are new to DI.&lt;/p&gt;
&lt;p&gt;On the other hand, this features of Koin might come with trade-offs in certain scenarios. Its runtime resolution might lead to potential issues during compile-time if dependencies are not properly configured. Additionally, Koin might not offer the same level of performance optimizations that some other DI frameworks, like Hilt, provide.&lt;/p&gt;
&lt;h3 id=&quot;how-to-use-koin-&quot;&gt;How to use Koin?&lt;/h3&gt;
&lt;p&gt;First, add dependencies to our project’s &lt;em&gt;build.gradle&lt;/em&gt; file,&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dependencies {
    // Koin core features
    implementation 'org.koin:koin-core:3.2.0'

    // Koin Android features
    implementation 'org.koin:koin-android:3.2.0'
    implementation 'org.koin:koin-androidx-viewmodel:3.2.0'
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, create Koin modules,&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import org.koin.dsl.module

val myKoinModule = module {
    single { UserRepository() } 
    factory { UserViewModel(get()) }
    // Define other dependencies...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;single&lt;/strong&gt;: create an object that persistent with the entire container lifetime. It does not create a new instance with each call.&lt;br&gt;
&lt;strong&gt;factory&lt;/strong&gt;: create a new object each time. Short live. No persistence in the container&lt;/p&gt;
&lt;p&gt;Then in Application class, initialize Koin by loading the modules,&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class MyKoinApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        startKoin {
            androidContext(this@MyKoinApplication )
            modules(myKoinModule )
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is how we call at Activity&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class HomeActivity : AppCompatActivity() {
    private val viewModel by viewModel&amp;lt;UserViewModel&amp;gt;()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // Use viewModel or other dependencies...
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;how about Hilt?&lt;/p&gt;
&lt;p&gt;Hilt, developed by Google and built on top of Dagger, is a powerful dependency injection (DI) framework tailored for Android. It simplifies DI by leveraging annotations and compile-time checks to ensure correctness and performance. Additionally, it integrates seamlessly with Jetpack libraries like ViewModel and WorkManager, making it easier to manage dependencies across different Android components.&lt;/p&gt;
&lt;p&gt;One of Hilt’s biggest advantages is its strong alignment with the Android development ecosystem, making it a preferred choice for larger projects following architectural patterns like MVVM or Clean Architecture. However, its learning curve can be steep due to its reliance on Dagger and the specific annotations required. Compared to Koin, Hilt may also involve a more complex setup, but it offers better compile-time safety and performance benefits.&lt;/p&gt;
&lt;h3 id=&quot;how-to-use-hilt&quot;&gt;How to use Hilt&lt;/h3&gt;
&lt;p&gt;Add Hilt dependency at our project’s &lt;em&gt;build.gradle&lt;/em&gt; file,&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dependencies {
    // Other dependencies...
    implementation &quot;com.google.dagger:hilt-android:$hilt_version&quot;
    kapt &quot;com.google.dagger:hilt-compiler:$hilt_version&quot;
    implementation &quot;androidx.hilt:hilt-lifecycle-viewmodel:$hilt_lifecycle_version&quot;
    kapt &quot;androidx.hilt:hilt-compiler:$hilt_lifecycle_version&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Enable Hilt to our Application&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@HiltAndroidApp
class MyHiltApplication : Application()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Creating Hilt modules&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@Module
@InstallIn(ApplicationComponent::class)
object MyModule {
    @Provides
    fun provideMyRepository(): MyRepository {
        return MyRepository()
    }
    
    @Provides
    fun provideMyViewModel(repository: MyRepository): MyViewModel {
        return MyViewModel(repository)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Using dependencies in Activity/Fragment,&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@AndroidEntryPoint
class MyActivity : AppCompatActivity() {
    private val viewModel: MyViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;There is 3 different characteristics between Koin and Hilt:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Syntax and Configuration:&lt;/strong&gt; Koin offers a simple and readable syntax without relying on annotations or code generation, while Hilt heavily depends on annotations like @Inject and @Module for dependency injection.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Learning Curve and Complexity:&lt;/strong&gt; Koin is beginner-friendly with minimal setup, making it easier to adopt. In contrast, Hilt has a steeper learning curve due to its integration with Dagger and required annotations.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Performance and Optimization:&lt;/strong&gt; Koin has faster build times and a smaller APK size since it doesn’t generate code, but it is slightly slower at runtime due to resolving dependencies dynamically. Hilt, using code generation, ensures better runtime performance but can lead to longer build times and a larger APK.&lt;/p&gt;
&lt;h3 id=&quot;so-when-to-use-koin-and-hilt-&quot;&gt;So when to use Koin and Hilt?&lt;/h3&gt;
&lt;p&gt;_Koin _is ideal for smaller projects due to its simplicity and flexibility, making code more modular and maintainable. However, for larger projects requiring deep integration with Android components, _Hilt _is preferable for its performance optimizations and alignment with Android architecture.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;references: &lt;br&gt;
_&lt;a rel=&quot;nofollow&quot; href=&quot;https://medium.com/ibtech/hilt-vs-koin-f3532b5796eb&quot;&gt;https://medium.com/ibtech/hilt-vs-koin-f3532b5796eb&lt;/a&gt;&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://en.wikipedia.org/wiki/Dependency_injection&quot;&gt;https://en.wikipedia.org/wiki/Dependency_injection&lt;/a&gt;&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://simple.wikipedia.org/wiki/SOLID_(object-oriented_design)_&quot;&gt;https://simple.wikipedia.org/wiki/SOLID_(object-oriented_design)_&lt;/a&gt;&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://medium.com/@srimanikandan92/hilt-vs-koin-a-simple-guide-to-android-dependency-injection-ef77d00e7cf6&quot;&gt;https://medium.com/@srimanikandan92/hilt-vs-koin-a-simple-guide-to-android-dependency-injection-ef77d00e7cf6&lt;/a&gt;&lt;/p&gt;
</description>
<category>Mobile Dev</category>
<guid isPermaLink="true">https://coderlegion.com/1506/koin-vs-hilt</guid>
<pubDate>Sun, 16 Mar 2025 07:37:25 +0000</pubDate>
</item>
</channel>
</rss>